Skip to content

Commit

Permalink
Add AWS.S3.getSignedUrlPromise (#2827)
Browse files Browse the repository at this point in the history
* Add promise support for AWS.S3.getSignedUrl

* Add tests for AWS.S3.getSignedUrlPromise

* Update documentation for AWS.S3.getSignedURL

* Updated documentation for getSignedUrlPromise

* Fixed spelling mistake in getSignedUrlPromise()

* Improvements for AWS.S3.getSignedUrlPromise

- Added TypeScript typings
- Fixed faulty tests
- Improved AWS.util.promisifyMethod to support arguments

* fix s3 type definition

* update documentation for promisifyMethod()

* fix unit tests
  • Loading branch information
AllanZhengYP authored Aug 29, 2019
1 parent 2496683 commit a203c0e
Show file tree
Hide file tree
Showing 8 changed files with 167 additions and 4 deletions.
5 changes: 5 additions & 0 deletions .changes/next-release/feature-S3-989eccff.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"type": "feature",
"category": "S3",
"description": "add support for AWS.S3.getSignedUrlPromise which returns a promise for S3 presigned url"
}
7 changes: 6 additions & 1 deletion lib/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -553,7 +553,12 @@ AWS.Config = AWS.util.inherit({
PromisesDependency = Promise;
}
var constructors = [AWS.Request, AWS.Credentials, AWS.CredentialProviderChain];
if (AWS.S3 && AWS.S3.ManagedUpload) constructors.push(AWS.S3.ManagedUpload);
if (AWS.S3) {
constructors.push(AWS.S3);
if (AWS.S3.ManagedUpload) {
constructors.push(AWS.S3.ManagedUpload);
}
}
AWS.util.addPromises(constructors, PromisesDependency);
},

Expand Down
5 changes: 5 additions & 0 deletions lib/services/s3.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ export class S3Customizations extends Service {
*/
getSignedUrl(operation: string, params: any): string;

/**
* Returns a 'thenable' promise that will be resolved with a pre-signed URL for a given operation name.
*/
getSignedUrlPromise(operation: string, params: any): Promise<string>;

/**
* Get the form fields and target URL for direct POST uploading.
*/
Expand Down
62 changes: 62 additions & 0 deletions lib/services/s3.js
Original file line number Diff line number Diff line change
Expand Up @@ -803,6 +803,52 @@ AWS.util.update(AWS.S3.prototype, {
}
},

/**
* @!method getSignedUrlPromise()
* Returns a 'thenable' promise that will be resolved with a pre-signed URL
* for a given operation name.
*
* Two callbacks can be provided to the `then` method on the returned promise.
* The first callback will be called if the promise is fulfilled, and the second
* callback will be called if the promise is rejected.
* @note Not all operation parameters are supported when using pre-signed
* URLs. Certain parameters, such as `SSECustomerKey`, `ACL`, `Expires`,
* `ContentLength`, or `Tagging` must be provided as headers when sending a
* request. If you are using pre-signed URLs to upload from a browser and
* need to use these fields, see {createPresignedPost}.
* @param operation [String] the name of the operation to call
* @param params [map] parameters to pass to the operation. See the given
* operation for the expected operation parameters. In addition, you can
* also pass the "Expires" parameter to inform S3 how long the URL should
* work for.
* @option params Expires [Integer] (900) the number of seconds to expire
* the pre-signed URL operation in. Defaults to 15 minutes.
* @callback fulfilledCallback function(url)
* Called if the promise is fulfilled.
* @param url [String] the signed url
* @callback rejectedCallback function(err)
* Called if the promise is rejected.
* @param err [Error] if an error occurred, this value will be filled
* @return [Promise] A promise that represents the state of the `refresh` call.
* @example Pre-signing a getObject operation
* var params = {Bucket: 'bucket', Key: 'key'};
* var promise = s3.getSignedUrlPromise('getObject', params);
* promise.then(function(url) {
* console.log('The URL is', url);
* }, function(err) { ... });
* @example Pre-signing a putObject operation with a specific payload
* var params = {Bucket: 'bucket', Key: 'key', Body: 'body'};
* var promise = s3.getSignedUrlPromise('putObject', params);
* promise.then(function(url) {
* console.log('The URL is', url);
* }, function(err) { ... });
* @example Passing in a 1-minute expiry time for a pre-signed URL
* var params = {Bucket: 'bucket', Key: 'key', Expires: 60};
* var promise = s3.getSignedUrlPromise('getObject', params);
* promise.then(function(url) {
* console.log('The URL is', url);
* }, function(err) { ... });
*/

/**
* Get a pre-signed POST policy to support uploading to S3 directly from an
Expand Down Expand Up @@ -1089,3 +1135,19 @@ AWS.util.update(AWS.S3.prototype, {
return uploader;
}
});

/**
* @api private
*/
AWS.S3.addPromisesToClass = function addPromisesToClass(PromiseDependency) {
this.prototype.getSignedUrlPromise = AWS.util.promisifyMethod('getSignedUrl', PromiseDependency);
};

/**
* @api private
*/
AWS.S3.deletePromisesFromClass = function deletePromisesFromClass() {
delete this.prototype.getSignedUrlPromise;
};

AWS.util.addPromises(AWS.S3);
9 changes: 8 additions & 1 deletion lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -813,18 +813,25 @@ var util = {

/**
* @api private
* Return a function that will return a promise whose fate is decided by the
* callback behavior of the given method with `methodName`. The method to be
* promisified should conform to node.js convention of accepting a callback as
* last argument and calling that callback with error as the first argument
* and success value on the second argument.
*/
promisifyMethod: function promisifyMethod(methodName, PromiseDependency) {
return function promise() {
var self = this;
var args = Array.prototype.slice.call(arguments);
return new PromiseDependency(function(resolve, reject) {
self[methodName](function(err, data) {
args.push(function(err, data) {
if (err) {
reject(err);
} else {
resolve(data);
}
});
self[methodName].apply(self, args);
});
};
},
Expand Down
2 changes: 1 addition & 1 deletion test/config.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -540,7 +540,7 @@ describe('AWS.config', function() {
AWS.config.setPromisesDependency(function() {});
expect(utilSpy.calls.length).to.equal(1);
expect(Array.isArray(utilSpy.calls[0]['arguments'][0])).to.be['true'];
expect(utilSpy.calls[0]['arguments'][0].length).to.equal(4);
expect(utilSpy.calls[0]['arguments'][0].length).to.equal(5);
});

if (typeof Promise !== 'undefined') {
Expand Down
73 changes: 72 additions & 1 deletion test/services/s3.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2926,10 +2926,81 @@ describe('AWS.S3', function() {
);
done();
});

invocationDeferred = true;
});
});
describe('getSignedUrlPromise', function() {
var catchFunction, resolveFunction, err, url, date;
err = null;
url = null;
date = null;

catchFunction = function(e) {
err = e;
};

resolveFunction = function(u) {
url = u;
};

beforeEach(function(done) {
err = null;
url = null;
originalDate = s3.getSkewCorrectedDate;
s3.getSkewCorrectedDate = function() {
return new Date(0);
};
AWS.config.setPromisesDependency();
return done();
});

afterEach(function(done) {
s3.getSkewCorrectedDate = originalDate;
done();
});

it('exists if Promises are available', function() {
if (typeof Promise === 'undefined') {
expect(typeof s3.getSignedUrlPromise).to.equal('undefined');
} else {
expect(typeof s3.getSignedUrlPromise).to.equal('function');
}
});

it('returns a promise when called', function() {
var P = function() {};
AWS.config.setPromisesDependency(P);
var operation = s3.getSignedUrlPromise;
expect(typeof operation).to.equal('function');
var urlPromise = s3.getSignedUrlPromise('getObject', {
Bucket: 'bucket',
Key: 'key'
});
expect(urlPromise instanceof P).to.equal(true);
});

if (typeof Promise === 'function') {
it('resolves when getSignedUrl is successful', function() {
s3.getSignedUrlPromise('getObject', {
Bucket: 'bucket',
Key: 'key'
}).then(resolveFunction).catch(catchFunction).then(function() {
expect(url).to.equal('https://bucket.s3.amazonaws.com/key?AWSAccessKeyId=akid&Expires=900&Signature=4mlYnRmz%2BBFEPrgYz5tXcl9Wc4w%3D&x-amz-security-token=session');
expect(err).to.be['null'];
});
});

it('rejects when getSignedUrl is unsuccessful', function() {
s3.getSignedUrlPromise('invalidOperation', {
Bucket: 'bucket',
Key: 'key',
}).then(resolveFunction).catch(catchFunction).then(function() {
expect(url).to.be['null'];
expect(err).to.not.be['null'];
});
});
}
});

describe('createPresignedPost', function() {
it('should include a url and a hash of form fields', function(done) {
Expand Down
8 changes: 8 additions & 0 deletions ts/s3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,14 @@ s3.putObject({
Body: fs.createReadStream('/fake/path')
});

const printUrl = (url: string)=> {
console.log(url);
}
s3.getSignedUrlPromise('getObject', {
Bucket: 'bucket',
Key: 'key'
}).then(printUrl);

const upload = s3.upload(
{
Bucket: 'BUCKET',
Expand Down

0 comments on commit a203c0e

Please sign in to comment.