mirror of
https://github.com/amark/gun.git
synced 2025-06-09 07:36:44 +00:00
520 lines
15 KiB
JavaScript
520 lines
15 KiB
JavaScript
var AWS = require('./core');
|
|
var Translator = require('aws-sdk-apis/lib/translator');
|
|
var inherit = AWS.util.inherit;
|
|
|
|
/**
|
|
* The service class representing an AWS service.
|
|
*
|
|
* @abstract
|
|
*
|
|
* @!attribute apiVersions
|
|
* @return [Array<String>] the list of API versions supported by this service.
|
|
* @readonly
|
|
*/
|
|
AWS.Service = inherit({
|
|
/**
|
|
* Create a new service object with a configuration object
|
|
*
|
|
* @param config [map] a map of configuration options
|
|
*/
|
|
constructor: function Service(config) {
|
|
if (!this.loadServiceClass) {
|
|
throw AWS.util.error(new Error(),
|
|
'Service must be constructed with `new\' operator');
|
|
}
|
|
var ServiceClass = this.loadServiceClass(config || {});
|
|
if (ServiceClass) return new ServiceClass(config);
|
|
this.initialize(config);
|
|
},
|
|
|
|
/**
|
|
* @api private
|
|
*/
|
|
initialize: function initialize(config) {
|
|
AWS.util.hideProperties(this, ['client']);
|
|
this.client = this; // backward compatibility with client property
|
|
this.config = new AWS.Config(AWS.config);
|
|
if (config) this.config.update(config, true);
|
|
this.setEndpoint(this.config.endpoint);
|
|
},
|
|
|
|
/**
|
|
* @api private
|
|
*/
|
|
loadServiceClass: function loadServiceClass(serviceConfig) {
|
|
var config = serviceConfig;
|
|
if (!AWS.util.isEmpty(this.api)) {
|
|
return null;
|
|
} else if (config.apiConfig) {
|
|
return AWS.Service.defineServiceApi(this.constructor, config.apiConfig);
|
|
} else if (!this.constructor.services) {
|
|
return null;
|
|
} else {
|
|
config = new AWS.Config(AWS.config);
|
|
config.update(serviceConfig, true);
|
|
var version = config.apiVersions[this.constructor.serviceIdentifier];
|
|
version = version || config.apiVersion;
|
|
return this.getLatestServiceClass(version);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @api private
|
|
*/
|
|
getLatestServiceClass: function getLatestServiceClass(version) {
|
|
version = this.getLatestServiceVersion(version);
|
|
if (this.constructor.services[version] === null) {
|
|
AWS.Service.defineServiceApi(this.constructor, version);
|
|
}
|
|
|
|
return this.constructor.services[version];
|
|
},
|
|
|
|
/**
|
|
* @api private
|
|
*/
|
|
getLatestServiceVersion: function getLatestServiceVersion(version) {
|
|
if (!this.constructor.services || this.constructor.services.length === 0) {
|
|
throw new Error('No services defined on ' +
|
|
this.constructor.serviceIdentifier);
|
|
}
|
|
|
|
if (!version) {
|
|
version = 'latest';
|
|
} else if (AWS.util.isType(version, Date)) {
|
|
version = AWS.util.date.iso8601(version).split('T')[0];
|
|
}
|
|
|
|
if (Object.hasOwnProperty(this.constructor.services, version)) {
|
|
return version;
|
|
}
|
|
|
|
var keys = Object.keys(this.constructor.services).sort();
|
|
var selectedVersion = null;
|
|
for (var i = keys.length - 1; i >= 0; i--) {
|
|
// versions that end in "*" are not available on disk and can be
|
|
// skipped, so do not choose these as selectedVersions
|
|
if (keys[i][keys[i].length - 1] !== '*') {
|
|
selectedVersion = keys[i];
|
|
}
|
|
if (keys[i].substr(0, 10) <= version) {
|
|
return selectedVersion;
|
|
}
|
|
}
|
|
|
|
throw new Error('Could not find ' + this.constructor.serviceIdentifier +
|
|
' API to satisfy version constraint `' + version + '\'');
|
|
},
|
|
|
|
/**
|
|
* @api private
|
|
*/
|
|
api: {},
|
|
|
|
/**
|
|
* @api private
|
|
*/
|
|
defaultRetryCount: 3,
|
|
|
|
/**
|
|
* Calls an operation on a service with the given input parameters.
|
|
*
|
|
* @param operation [String] the name of the operation to call on the service.
|
|
* @param params [map] a map of input options for the operation
|
|
* @callback callback function(err, data)
|
|
* If a callback is supplied, it is called when a response is returned
|
|
* from the service.
|
|
* @param err [Error] the error object returned from the request.
|
|
* Set to `null` if the request is successful.
|
|
* @param data [Object] the de-serialized data returned from
|
|
* the request. Set to `null` if a request error occurs.
|
|
*/
|
|
makeRequest: function makeRequest(operation, params, callback) {
|
|
if (typeof params === 'function') {
|
|
callback = params;
|
|
params = null;
|
|
}
|
|
|
|
params = params || {};
|
|
if (this.config.params) { // copy only toplevel bound params
|
|
var rules = this.api.operations[operation];
|
|
if (rules) {
|
|
params = AWS.util.copy(params);
|
|
AWS.util.each(this.config.params, function(key, value) {
|
|
if (rules.input.members[key]) {
|
|
if (params[key] === undefined || params[key] === null) {
|
|
params[key] = value;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
var request = new AWS.Request(this, operation, params);
|
|
this.addAllRequestListeners(request);
|
|
|
|
if (callback) request.send(callback);
|
|
return request;
|
|
},
|
|
|
|
/**
|
|
* Calls an operation on a service with the given input parameters, without
|
|
* any authentication data. This method is useful for "public" API operations.
|
|
*
|
|
* @param operation [String] the name of the operation to call on the service.
|
|
* @param params [map] a map of input options for the operation
|
|
* @callback callback function(err, data)
|
|
* If a callback is supplied, it is called when a response is returned
|
|
* from the service.
|
|
* @param err [Error] the error object returned from the request.
|
|
* Set to `null` if the request is successful.
|
|
* @param data [Object] the de-serialized data returned from
|
|
* the request. Set to `null` if a request error occurs.
|
|
*/
|
|
makeUnauthenticatedRequest: function makeUnauthenticatedRequest(operation, params, callback) {
|
|
if (typeof params === 'function') {
|
|
callback = params;
|
|
params = {};
|
|
}
|
|
|
|
var request = this.makeRequest(operation, params);
|
|
request.removeListener('validate', AWS.EventListeners.Core.VALIDATE_CREDENTIALS);
|
|
request.removeListener('sign', AWS.EventListeners.Core.SIGN);
|
|
if (this.api.format === 'query') { // query services turn into GET requests
|
|
request.addListener('build', function convertToGET(request) {
|
|
request.httpRequest.method = 'GET';
|
|
request.httpRequest.path = '/?' + request.httpRequest.body;
|
|
request.httpRequest.body = '';
|
|
|
|
// don't need these headers on a GET request
|
|
delete request.httpRequest.headers['Content-Length'];
|
|
delete request.httpRequest.headers['Content-Type'];
|
|
});
|
|
}
|
|
|
|
return callback ? request.send(callback) : request;
|
|
},
|
|
|
|
/**
|
|
* Waits for a given state
|
|
*
|
|
* @param state [String] the state on the service to wait for
|
|
* @param params [map] a map of parameters to pass with each request
|
|
* @callback callback function(err, data)
|
|
* If a callback is supplied, it is called when a response is returned
|
|
* from the service.
|
|
* @param err [Error] the error object returned from the request.
|
|
* Set to `null` if the request is successful.
|
|
* @param data [Object] the de-serialized data returned from
|
|
* the request. Set to `null` if a request error occurs.
|
|
*/
|
|
waitFor: function waitFor(state, params, callback) {
|
|
var waiter = new AWS.ResourceWaiter(this, state);
|
|
return waiter.wait(params, callback);
|
|
},
|
|
|
|
/**
|
|
* @api private
|
|
*/
|
|
addAllRequestListeners: function addAllRequestListeners(request) {
|
|
var list = [AWS.events, AWS.EventListeners.Core, this.serviceInterface(),
|
|
AWS.EventListeners.CorePost];
|
|
for (var i = 0; i < list.length; i++) {
|
|
if (list[i]) request.addListeners(list[i]);
|
|
}
|
|
|
|
// disable parameter validation
|
|
if (!this.config.paramValidation) {
|
|
request.removeListener('validate',
|
|
AWS.EventListeners.Core.VALIDATE_PARAMETERS);
|
|
}
|
|
|
|
if (this.config.logger) { // add logging events
|
|
request.addListeners(AWS.EventListeners.Logger);
|
|
}
|
|
|
|
this.setupRequestListeners(request);
|
|
},
|
|
|
|
/**
|
|
* Override this method to setup any custom request listeners for each
|
|
* new request to the service.
|
|
*
|
|
* @abstract
|
|
*/
|
|
setupRequestListeners: function setupRequestListeners() {
|
|
},
|
|
|
|
/**
|
|
* Gets the signer class for a given request
|
|
* @api private
|
|
*/
|
|
getSignerClass: function getSignerClass() {
|
|
var version = this.api.signatureVersion;
|
|
if (this.config.signatureVersion) version = this.config.signatureVersion;
|
|
else if (this.isRegionV4()) version = 'v4';
|
|
return AWS.Signers.RequestSigner.getVersion(version);
|
|
},
|
|
|
|
/**
|
|
* @api private
|
|
*/
|
|
serviceInterface: function serviceInterface() {
|
|
switch (this.api.format) {
|
|
case 'query': return AWS.EventListeners.Query;
|
|
case 'json': return AWS.EventListeners.Json;
|
|
case 'rest-json': return AWS.EventListeners.RestJson;
|
|
case 'rest-xml': return AWS.EventListeners.RestXml;
|
|
}
|
|
if (this.api.format) {
|
|
throw new Error('Invalid service `format\' ' +
|
|
this.api.format + ' in API config');
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @api private
|
|
*/
|
|
successfulResponse: function successfulResponse(resp) {
|
|
return resp.httpResponse.statusCode < 300;
|
|
},
|
|
|
|
/**
|
|
* How many times a failed request should be retried before giving up.
|
|
* the defaultRetryCount can be overriden by service classes.
|
|
*
|
|
* @api private
|
|
*/
|
|
numRetries: function numRetries() {
|
|
if (this.config.maxRetries !== undefined) {
|
|
return this.config.maxRetries;
|
|
} else {
|
|
return this.defaultRetryCount;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @api private
|
|
*/
|
|
retryDelays: function retryDelays() {
|
|
var retryCount = this.numRetries();
|
|
var delays = [];
|
|
for (var i = 0; i < retryCount; ++i) {
|
|
delays[i] = Math.pow(2, i) * 30;
|
|
}
|
|
return delays;
|
|
},
|
|
|
|
/**
|
|
* @api private
|
|
*/
|
|
retryableError: function retryableError(error) {
|
|
if (this.networkingError(error)) return true;
|
|
if (this.expiredCredentialsError(error)) return true;
|
|
if (this.throttledError(error)) return true;
|
|
if (error.statusCode >= 500) return true;
|
|
return false;
|
|
},
|
|
|
|
/**
|
|
* @api private
|
|
*/
|
|
networkingError: function networkingError(error) {
|
|
return error.code === 'NetworkingError';
|
|
},
|
|
|
|
/**
|
|
* @api private
|
|
*/
|
|
expiredCredentialsError: function expiredCredentialsError(error) {
|
|
// TODO : this only handles *one* of the expired credential codes
|
|
return (error.code === 'ExpiredTokenException');
|
|
},
|
|
|
|
/**
|
|
* @api private
|
|
*/
|
|
throttledError: function throttledError(error) {
|
|
// this logic varies between services
|
|
return (error.code === 'ProvisionedThroughputExceededException');
|
|
},
|
|
|
|
/**
|
|
* @api private
|
|
*/
|
|
setEndpoint: function setEndpoint(endpoint) {
|
|
if (endpoint) {
|
|
this.endpoint = new AWS.Endpoint(endpoint, this.config);
|
|
} else if (this.hasGlobalEndpoint()) {
|
|
this.endpoint = new AWS.Endpoint(this.api.globalEndpoint, this.config);
|
|
} else {
|
|
var host = this.api.endpointPrefix + '.' + this.config.region +
|
|
this.endpointSuffix();
|
|
this.endpoint = new AWS.Endpoint(host, this.config);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @api private
|
|
*/
|
|
hasGlobalEndpoint: function hasGlobalEndpoint() {
|
|
if (this.isRegionV4()) return false;
|
|
return this.api.globalEndpoint;
|
|
},
|
|
|
|
/**
|
|
* @api private
|
|
*/
|
|
endpointSuffix: function endpointSuffix() {
|
|
var suffix = '.amazonaws.com';
|
|
if (this.isRegionCN()) return suffix + '.cn';
|
|
else return suffix;
|
|
},
|
|
|
|
/**
|
|
* @api private
|
|
*/
|
|
isRegionCN: function isRegionCN() {
|
|
if (!this.config.region) return false;
|
|
return this.config.region.match(/^cn-/) ? true : false;
|
|
},
|
|
|
|
/**
|
|
* @api private
|
|
*/
|
|
isRegionV4: function isRegionV4() {
|
|
return this.isRegionCN();
|
|
},
|
|
|
|
/**
|
|
* @api private
|
|
*/
|
|
paginationConfig: function paginationConfig(operation, throwException) {
|
|
function fail(name) {
|
|
if (throwException) {
|
|
var e = new Error();
|
|
throw AWS.util.error(e, 'No pagination configuration for ' + name);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
if (!this.api.pagination) return fail('service');
|
|
if (!this.api.pagination[operation]) return fail(operation);
|
|
return this.api.pagination[operation];
|
|
}
|
|
});
|
|
|
|
AWS.util.update(AWS.Service, {
|
|
|
|
/**
|
|
* Adds one method for each operation described in the api configuration
|
|
*
|
|
* @api private
|
|
*/
|
|
defineMethods: function defineMethods(svc) {
|
|
AWS.util.each(svc.prototype.api.operations, function iterator(method) {
|
|
if (svc.prototype[method]) return;
|
|
svc.prototype[method] = function (params, callback) {
|
|
return this.makeRequest(method, params, callback);
|
|
};
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Defines a new Service class using a service identifier and list of versions
|
|
* including an optional set of features (functions) to apply to the class
|
|
* prototype.
|
|
*
|
|
* @param serviceIdentifier [String] the identifier for the service
|
|
* @param versions [Array<String>] a list of versions that work with this
|
|
* service
|
|
* @param features [Object] an object to attach to the prototype
|
|
* @return [Class<Service>] the service class defined by this function.
|
|
*/
|
|
defineService: function defineService(serviceIdentifier, versions, features) {
|
|
if (!Array.isArray(versions)) {
|
|
features = versions;
|
|
versions = [];
|
|
}
|
|
|
|
var svc = inherit(AWS.Service, features || {});
|
|
|
|
if (typeof serviceIdentifier === 'string') {
|
|
AWS.Service.addVersions(svc, versions);
|
|
|
|
var identifier = svc.serviceIdentifier || serviceIdentifier;
|
|
svc.serviceIdentifier = identifier;
|
|
} else { // defineService called with an API
|
|
svc.prototype.api = serviceIdentifier;
|
|
AWS.Service.defineMethods(svc);
|
|
}
|
|
|
|
return svc;
|
|
},
|
|
|
|
/**
|
|
* @api private
|
|
*/
|
|
addVersions: function addVersions(svc, versions) {
|
|
if (!Array.isArray(versions)) versions = [versions];
|
|
|
|
svc.services = svc.services || {};
|
|
for (var i = 0; i < versions.length; i++) {
|
|
if (svc.services[versions[i]] === undefined) {
|
|
svc.services[versions[i]] = null;
|
|
}
|
|
}
|
|
|
|
svc.apiVersions = Object.keys(svc.services).sort();
|
|
},
|
|
|
|
/**
|
|
* @api private
|
|
*/
|
|
defineServiceApi: function defineServiceApi(superclass, version, apiConfig) {
|
|
var svc = inherit(superclass, {
|
|
serviceIdentifier: superclass.serviceIdentifier
|
|
});
|
|
|
|
function setApi(api) {
|
|
if (api.type && api.api_version) {
|
|
svc.prototype.api = new Translator(api, {documentation: false});
|
|
} else {
|
|
svc.prototype.api = api;
|
|
}
|
|
}
|
|
|
|
if (typeof version === 'string') {
|
|
if (apiConfig) {
|
|
setApi(apiConfig);
|
|
} else {
|
|
var fs = AWS.util.nodeRequire('fs');
|
|
var path = AWS.util.nodeRequire('path');
|
|
var apis = AWS.util.nodeRequire('aws-sdk-apis');
|
|
|
|
try {
|
|
var name = null;
|
|
if (apis) name = apis.serviceName(superclass.serviceIdentifier);
|
|
var file = (name || superclass.serviceIdentifier) + '-' + version;
|
|
var fullPath = path.dirname(require.resolve('aws-sdk-apis')) +
|
|
'/apis/' + file + '.json';
|
|
setApi(JSON.parse(fs.readFileSync(fullPath)));
|
|
} catch (err) {
|
|
throw AWS.util.error(err, {
|
|
message: 'Could not find API configuration ' + file
|
|
});
|
|
}
|
|
}
|
|
if (!superclass.services.hasOwnProperty(version)) {
|
|
superclass.apiVersions = superclass.apiVersions.concat(version).sort();
|
|
}
|
|
superclass.services[version] = svc;
|
|
} else {
|
|
setApi(version);
|
|
}
|
|
|
|
AWS.Service.defineMethods(svc);
|
|
return svc;
|
|
}
|
|
});
|