UNPKG

27.1 kBJavaScriptView Raw
1var AWS = require('./core');
2var Api = require('./model/api');
3var regionConfig = require('./region_config');
4
5var inherit = AWS.util.inherit;
6var clientCount = 0;
7
8/**
9 * The service class representing an AWS service.
10 *
11 * @class_abstract This class is an abstract class.
12 *
13 * @!attribute apiVersions
14 * @return [Array<String>] the list of API versions supported by this service.
15 * @readonly
16 */
17AWS.Service = inherit({
18 /**
19 * Create a new service object with a configuration object
20 *
21 * @param config [map] a map of configuration options
22 */
23 constructor: function Service(config) {
24 if (!this.loadServiceClass) {
25 throw AWS.util.error(new Error(),
26 'Service must be constructed with `new\' operator');
27 }
28 var ServiceClass = this.loadServiceClass(config || {});
29 if (ServiceClass) {
30 var originalConfig = AWS.util.copy(config);
31 var svc = new ServiceClass(config);
32 Object.defineProperty(svc, '_originalConfig', {
33 get: function() { return originalConfig; },
34 enumerable: false,
35 configurable: true
36 });
37 svc._clientId = ++clientCount;
38 return svc;
39 }
40 this.initialize(config);
41 },
42
43 /**
44 * @api private
45 */
46 initialize: function initialize(config) {
47 var svcConfig = AWS.config[this.serviceIdentifier];
48 this.config = new AWS.Config(AWS.config);
49 if (svcConfig) this.config.update(svcConfig, true);
50 if (config) this.config.update(config, true);
51
52 this.validateService();
53 if (!this.config.endpoint) regionConfig(this);
54
55 this.config.endpoint = this.endpointFromTemplate(this.config.endpoint);
56 this.setEndpoint(this.config.endpoint);
57 //enable attaching listeners to service client
58 AWS.SequentialExecutor.call(this);
59 AWS.Service.addDefaultMonitoringListeners(this);
60 if ((this.config.clientSideMonitoring || AWS.Service._clientSideMonitoring) && this.publisher) {
61 var publisher = this.publisher;
62 this.addNamedListener('PUBLISH_API_CALL', 'apiCall', function PUBLISH_API_CALL(event) {
63 process.nextTick(function() {publisher.eventHandler(event);});
64 });
65 this.addNamedListener('PUBLISH_API_ATTEMPT', 'apiCallAttempt', function PUBLISH_API_ATTEMPT(event) {
66 process.nextTick(function() {publisher.eventHandler(event);});
67 });
68 }
69 },
70
71 /**
72 * @api private
73 */
74 validateService: function validateService() {
75 },
76
77 /**
78 * @api private
79 */
80 loadServiceClass: function loadServiceClass(serviceConfig) {
81 var config = serviceConfig;
82 if (!AWS.util.isEmpty(this.api)) {
83 return null;
84 } else if (config.apiConfig) {
85 return AWS.Service.defineServiceApi(this.constructor, config.apiConfig);
86 } else if (!this.constructor.services) {
87 return null;
88 } else {
89 config = new AWS.Config(AWS.config);
90 config.update(serviceConfig, true);
91 var version = config.apiVersions[this.constructor.serviceIdentifier];
92 version = version || config.apiVersion;
93 return this.getLatestServiceClass(version);
94 }
95 },
96
97 /**
98 * @api private
99 */
100 getLatestServiceClass: function getLatestServiceClass(version) {
101 version = this.getLatestServiceVersion(version);
102 if (this.constructor.services[version] === null) {
103 AWS.Service.defineServiceApi(this.constructor, version);
104 }
105
106 return this.constructor.services[version];
107 },
108
109 /**
110 * @api private
111 */
112 getLatestServiceVersion: function getLatestServiceVersion(version) {
113 if (!this.constructor.services || this.constructor.services.length === 0) {
114 throw new Error('No services defined on ' +
115 this.constructor.serviceIdentifier);
116 }
117
118 if (!version) {
119 version = 'latest';
120 } else if (AWS.util.isType(version, Date)) {
121 version = AWS.util.date.iso8601(version).split('T')[0];
122 }
123
124 if (Object.hasOwnProperty(this.constructor.services, version)) {
125 return version;
126 }
127
128 var keys = Object.keys(this.constructor.services).sort();
129 var selectedVersion = null;
130 for (var i = keys.length - 1; i >= 0; i--) {
131 // versions that end in "*" are not available on disk and can be
132 // skipped, so do not choose these as selectedVersions
133 if (keys[i][keys[i].length - 1] !== '*') {
134 selectedVersion = keys[i];
135 }
136 if (keys[i].substr(0, 10) <= version) {
137 return selectedVersion;
138 }
139 }
140
141 throw new Error('Could not find ' + this.constructor.serviceIdentifier +
142 ' API to satisfy version constraint `' + version + '\'');
143 },
144
145 /**
146 * @api private
147 */
148 api: {},
149
150 /**
151 * @api private
152 */
153 defaultRetryCount: 3,
154
155 /**
156 * @api private
157 */
158 customizeRequests: function customizeRequests(callback) {
159 if (!callback) {
160 this.customRequestHandler = null;
161 } else if (typeof callback === 'function') {
162 this.customRequestHandler = callback;
163 } else {
164 throw new Error('Invalid callback type \'' + typeof callback + '\' provided in customizeRequests');
165 }
166 },
167
168 /**
169 * Calls an operation on a service with the given input parameters.
170 *
171 * @param operation [String] the name of the operation to call on the service.
172 * @param params [map] a map of input options for the operation
173 * @callback callback function(err, data)
174 * If a callback is supplied, it is called when a response is returned
175 * from the service.
176 * @param err [Error] the error object returned from the request.
177 * Set to `null` if the request is successful.
178 * @param data [Object] the de-serialized data returned from
179 * the request. Set to `null` if a request error occurs.
180 */
181 makeRequest: function makeRequest(operation, params, callback) {
182 if (typeof params === 'function') {
183 callback = params;
184 params = null;
185 }
186
187 params = params || {};
188 if (this.config.params) { // copy only toplevel bound params
189 var rules = this.api.operations[operation];
190 if (rules) {
191 params = AWS.util.copy(params);
192 AWS.util.each(this.config.params, function(key, value) {
193 if (rules.input.members[key]) {
194 if (params[key] === undefined || params[key] === null) {
195 params[key] = value;
196 }
197 }
198 });
199 }
200 }
201
202 var request = new AWS.Request(this, operation, params);
203 this.addAllRequestListeners(request);
204 this.attachMonitoringEmitter(request);
205 if (callback) request.send(callback);
206 return request;
207 },
208
209 /**
210 * Calls an operation on a service with the given input parameters, without
211 * any authentication data. This method is useful for "public" API operations.
212 *
213 * @param operation [String] the name of the operation to call on the service.
214 * @param params [map] a map of input options for the operation
215 * @callback callback function(err, data)
216 * If a callback is supplied, it is called when a response is returned
217 * from the service.
218 * @param err [Error] the error object returned from the request.
219 * Set to `null` if the request is successful.
220 * @param data [Object] the de-serialized data returned from
221 * the request. Set to `null` if a request error occurs.
222 */
223 makeUnauthenticatedRequest: function makeUnauthenticatedRequest(operation, params, callback) {
224 if (typeof params === 'function') {
225 callback = params;
226 params = {};
227 }
228
229 var request = this.makeRequest(operation, params).toUnauthenticated();
230 return callback ? request.send(callback) : request;
231 },
232
233 /**
234 * Waits for a given state
235 *
236 * @param state [String] the state on the service to wait for
237 * @param params [map] a map of parameters to pass with each request
238 * @option params $waiter [map] a map of configuration options for the waiter
239 * @option params $waiter.delay [Number] The number of seconds to wait between
240 * requests
241 * @option params $waiter.maxAttempts [Number] The maximum number of requests
242 * to send while waiting
243 * @callback callback function(err, data)
244 * If a callback is supplied, it is called when a response is returned
245 * from the service.
246 * @param err [Error] the error object returned from the request.
247 * Set to `null` if the request is successful.
248 * @param data [Object] the de-serialized data returned from
249 * the request. Set to `null` if a request error occurs.
250 */
251 waitFor: function waitFor(state, params, callback) {
252 var waiter = new AWS.ResourceWaiter(this, state);
253 return waiter.wait(params, callback);
254 },
255
256 /**
257 * @api private
258 */
259 addAllRequestListeners: function addAllRequestListeners(request) {
260 var list = [AWS.events, AWS.EventListeners.Core, this.serviceInterface(),
261 AWS.EventListeners.CorePost];
262 for (var i = 0; i < list.length; i++) {
263 if (list[i]) request.addListeners(list[i]);
264 }
265
266 // disable parameter validation
267 if (!this.config.paramValidation) {
268 request.removeListener('validate',
269 AWS.EventListeners.Core.VALIDATE_PARAMETERS);
270 }
271
272 if (this.config.logger) { // add logging events
273 request.addListeners(AWS.EventListeners.Logger);
274 }
275
276 this.setupRequestListeners(request);
277 // call prototype's customRequestHandler
278 if (typeof this.constructor.prototype.customRequestHandler === 'function') {
279 this.constructor.prototype.customRequestHandler(request);
280 }
281 // call instance's customRequestHandler
282 if (Object.prototype.hasOwnProperty.call(this, 'customRequestHandler') && typeof this.customRequestHandler === 'function') {
283 this.customRequestHandler(request);
284 }
285 },
286
287 /**
288 * Event recording metrics for a whole API call.
289 * @returns {object} a subset of api call metrics
290 * @api private
291 */
292 apiCallEvent: function apiCallEvent(request) {
293 var api = request.service.api.operations[request.operation];
294 var monitoringEvent = {
295 Type: 'ApiCall',
296 Api: api ? api.name : request.operation,
297 Version: 1,
298 Service: request.service.api.serviceId || request.service.api.endpointPrefix,
299 Region: request.httpRequest.region,
300 MaxRetriesExceeded: 0,
301 UserAgent: request.httpRequest.getUserAgent(),
302 };
303 var response = request.response;
304 if (response.httpResponse.statusCode) {
305 monitoringEvent.FinalHttpStatusCode = response.httpResponse.statusCode;
306 }
307 if (response.error) {
308 var error = response.error;
309 var statusCode = response.httpResponse.statusCode;
310 if (statusCode > 299) {
311 if (error.code) monitoringEvent.FinalAwsException = error.code;
312 if (error.message) monitoringEvent.FinalAwsExceptionMessage = error.message;
313 } else {
314 if (error.code || error.name) monitoringEvent.FinalSdkException = error.code || error.name;
315 if (error.message) monitoringEvent.FinalSdkExceptionMessage = error.message;
316 }
317 }
318 return monitoringEvent;
319 },
320
321 /**
322 * Event recording metrics for an API call attempt.
323 * @returns {object} a subset of api call attempt metrics
324 * @api private
325 */
326 apiAttemptEvent: function apiAttemptEvent(request) {
327 var api = request.service.api.operations[request.operation];
328 var monitoringEvent = {
329 Type: 'ApiCallAttempt',
330 Api: api ? api.name : request.operation,
331 Version: 1,
332 Service: request.service.api.serviceId || request.service.api.endpointPrefix,
333 Fqdn: request.httpRequest.endpoint.hostname,
334 UserAgent: request.httpRequest.getUserAgent(),
335 };
336 var response = request.response;
337 if (response.httpResponse.statusCode) {
338 monitoringEvent.HttpStatusCode = response.httpResponse.statusCode;
339 }
340 if (
341 !request._unAuthenticated &&
342 request.service.config.credentials &&
343 request.service.config.credentials.accessKeyId
344 ) {
345 monitoringEvent.AccessKey = request.service.config.credentials.accessKeyId;
346 }
347 if (!response.httpResponse.headers) return monitoringEvent;
348 if (request.httpRequest.headers['x-amz-security-token']) {
349 monitoringEvent.SessionToken = request.httpRequest.headers['x-amz-security-token'];
350 }
351 if (response.httpResponse.headers['x-amzn-requestid']) {
352 monitoringEvent.XAmznRequestId = response.httpResponse.headers['x-amzn-requestid'];
353 }
354 if (response.httpResponse.headers['x-amz-request-id']) {
355 monitoringEvent.XAmzRequestId = response.httpResponse.headers['x-amz-request-id'];
356 }
357 if (response.httpResponse.headers['x-amz-id-2']) {
358 monitoringEvent.XAmzId2 = response.httpResponse.headers['x-amz-id-2'];
359 }
360 return monitoringEvent;
361 },
362
363 /**
364 * Add metrics of failed request.
365 * @api private
366 */
367 attemptFailEvent: function attemptFailEvent(request) {
368 var monitoringEvent = this.apiAttemptEvent(request);
369 var response = request.response;
370 var error = response.error;
371 if (response.httpResponse.statusCode > 299 ) {
372 if (error.code) monitoringEvent.AwsException = error.code;
373 if (error.message) monitoringEvent.AwsExceptionMessage = error.message;
374 } else {
375 if (error.code || error.name) monitoringEvent.SdkException = error.code || error.name;
376 if (error.message) monitoringEvent.SdkExceptionMessage = error.message;
377 }
378 return monitoringEvent;
379 },
380
381 /**
382 * Attach listeners to request object to fetch metrics of each request
383 * and emit data object through \'ApiCall\' and \'ApiCallAttempt\' events.
384 * @api private
385 */
386 attachMonitoringEmitter: function attachMonitoringEmitter(request) {
387 var attemptTimestamp; //timestamp marking the beginning of a request attempt
388 var attemptStartRealTime; //Start time of request attempt. Used to calculating attemptLatency
389 var attemptLatency; //latency from request sent out to http response reaching SDK
390 var callStartRealTime; //Start time of API call. Used to calculating API call latency
391 var attemptCount = 0; //request.retryCount is not reliable here
392 var region; //region cache region for each attempt since it can be updated in plase (e.g. s3)
393 var callTimestamp; //timestamp when the request is created
394 var self = this;
395 var addToHead = true;
396
397 request.on('validate', function () {
398 callStartRealTime = AWS.util.realClock.now();
399 callTimestamp = Date.now();
400 }, addToHead);
401 request.on('sign', function () {
402 attemptStartRealTime = AWS.util.realClock.now();
403 attemptTimestamp = Date.now();
404 region = request.httpRequest.region;
405 attemptCount++;
406 }, addToHead);
407 request.on('validateResponse', function() {
408 attemptLatency = Math.round(AWS.util.realClock.now() - attemptStartRealTime);
409 });
410 request.addNamedListener('API_CALL_ATTEMPT', 'success', function API_CALL_ATTEMPT() {
411 var apiAttemptEvent = self.apiAttemptEvent(request);
412 apiAttemptEvent.Timestamp = attemptTimestamp;
413 apiAttemptEvent.AttemptLatency = attemptLatency >= 0 ? attemptLatency : 0;
414 apiAttemptEvent.Region = region;
415 self.emit('apiCallAttempt', [apiAttemptEvent]);
416 });
417 request.addNamedListener('API_CALL_ATTEMPT_RETRY', 'retry', function API_CALL_ATTEMPT_RETRY() {
418 var apiAttemptEvent = self.attemptFailEvent(request);
419 apiAttemptEvent.Timestamp = attemptTimestamp;
420 //attemptLatency may not be available if fail before response
421 attemptLatency = attemptLatency ||
422 Math.round(AWS.util.realClock.now() - attemptStartRealTime);
423 apiAttemptEvent.AttemptLatency = attemptLatency >= 0 ? attemptLatency : 0;
424 apiAttemptEvent.Region = region;
425 self.emit('apiCallAttempt', [apiAttemptEvent]);
426 });
427 request.addNamedListener('API_CALL', 'complete', function API_CALL() {
428 var apiCallEvent = self.apiCallEvent(request);
429 apiCallEvent.AttemptCount = attemptCount;
430 if (apiCallEvent.AttemptCount <= 0) return;
431 apiCallEvent.Timestamp = callTimestamp;
432 var latency = Math.round(AWS.util.realClock.now() - callStartRealTime);
433 apiCallEvent.Latency = latency >= 0 ? latency : 0;
434 var response = request.response;
435 if (
436 typeof response.retryCount === 'number' &&
437 typeof response.maxRetries === 'number' &&
438 (response.retryCount >= response.maxRetries)
439 ) {
440 apiCallEvent.MaxRetriesExceeded = 1;
441 }
442 self.emit('apiCall', [apiCallEvent]);
443 });
444 },
445
446 /**
447 * Override this method to setup any custom request listeners for each
448 * new request to the service.
449 *
450 * @method_abstract This is an abstract method.
451 */
452 setupRequestListeners: function setupRequestListeners(request) {
453 },
454
455 /**
456 * Gets the signer class for a given request
457 * @api private
458 */
459 getSignerClass: function getSignerClass(request) {
460 var version;
461 // get operation authtype if present
462 var operation = null;
463 var authtype = '';
464 if (request) {
465 var operations = request.service.api.operations || {};
466 operation = operations[request.operation] || null;
467 authtype = operation ? operation.authtype : '';
468 }
469 if (this.config.signatureVersion) {
470 version = this.config.signatureVersion;
471 } else if (authtype === 'v4' || authtype === 'v4-unsigned-body') {
472 version = 'v4';
473 } else {
474 version = this.api.signatureVersion;
475 }
476 return AWS.Signers.RequestSigner.getVersion(version);
477 },
478
479 /**
480 * @api private
481 */
482 serviceInterface: function serviceInterface() {
483 switch (this.api.protocol) {
484 case 'ec2': return AWS.EventListeners.Query;
485 case 'query': return AWS.EventListeners.Query;
486 case 'json': return AWS.EventListeners.Json;
487 case 'rest-json': return AWS.EventListeners.RestJson;
488 case 'rest-xml': return AWS.EventListeners.RestXml;
489 }
490 if (this.api.protocol) {
491 throw new Error('Invalid service `protocol\' ' +
492 this.api.protocol + ' in API config');
493 }
494 },
495
496 /**
497 * @api private
498 */
499 successfulResponse: function successfulResponse(resp) {
500 return resp.httpResponse.statusCode < 300;
501 },
502
503 /**
504 * How many times a failed request should be retried before giving up.
505 * the defaultRetryCount can be overriden by service classes.
506 *
507 * @api private
508 */
509 numRetries: function numRetries() {
510 if (this.config.maxRetries !== undefined) {
511 return this.config.maxRetries;
512 } else {
513 return this.defaultRetryCount;
514 }
515 },
516
517 /**
518 * @api private
519 */
520 retryDelays: function retryDelays(retryCount) {
521 return AWS.util.calculateRetryDelay(retryCount, this.config.retryDelayOptions);
522 },
523
524 /**
525 * @api private
526 */
527 retryableError: function retryableError(error) {
528 if (this.timeoutError(error)) return true;
529 if (this.networkingError(error)) return true;
530 if (this.expiredCredentialsError(error)) return true;
531 if (this.throttledError(error)) return true;
532 if (error.statusCode >= 500) return true;
533 return false;
534 },
535
536 /**
537 * @api private
538 */
539 networkingError: function networkingError(error) {
540 return error.code === 'NetworkingError';
541 },
542
543 /**
544 * @api private
545 */
546 timeoutError: function timeoutError(error) {
547 return error.code === 'TimeoutError';
548 },
549
550 /**
551 * @api private
552 */
553 expiredCredentialsError: function expiredCredentialsError(error) {
554 // TODO : this only handles *one* of the expired credential codes
555 return (error.code === 'ExpiredTokenException');
556 },
557
558 /**
559 * @api private
560 */
561 clockSkewError: function clockSkewError(error) {
562 switch (error.code) {
563 case 'RequestTimeTooSkewed':
564 case 'RequestExpired':
565 case 'InvalidSignatureException':
566 case 'SignatureDoesNotMatch':
567 case 'AuthFailure':
568 case 'RequestInTheFuture':
569 return true;
570 default: return false;
571 }
572 },
573
574 /**
575 * @api private
576 */
577 getSkewCorrectedDate: function getSkewCorrectedDate() {
578 return new Date(Date.now() + this.config.systemClockOffset);
579 },
580
581 /**
582 * @api private
583 */
584 applyClockOffset: function applyClockOffset(newServerTime) {
585 if (newServerTime) {
586 this.config.systemClockOffset = newServerTime - Date.now();
587 }
588 },
589
590 /**
591 * @api private
592 */
593 isClockSkewed: function isClockSkewed(newServerTime) {
594 if (newServerTime) {
595 return Math.abs(this.getSkewCorrectedDate().getTime() - newServerTime) >= 30000;
596 }
597 },
598
599 /**
600 * @api private
601 */
602 throttledError: function throttledError(error) {
603 // this logic varies between services
604 switch (error.code) {
605 case 'ProvisionedThroughputExceededException':
606 case 'Throttling':
607 case 'ThrottlingException':
608 case 'RequestLimitExceeded':
609 case 'RequestThrottled':
610 case 'RequestThrottledException':
611 case 'TooManyRequestsException':
612 case 'TransactionInProgressException': //dynamodb
613 return true;
614 default:
615 return false;
616 }
617 },
618
619 /**
620 * @api private
621 */
622 endpointFromTemplate: function endpointFromTemplate(endpoint) {
623 if (typeof endpoint !== 'string') return endpoint;
624
625 var e = endpoint;
626 e = e.replace(/\{service\}/g, this.api.endpointPrefix);
627 e = e.replace(/\{region\}/g, this.config.region);
628 e = e.replace(/\{scheme\}/g, this.config.sslEnabled ? 'https' : 'http');
629 return e;
630 },
631
632 /**
633 * @api private
634 */
635 setEndpoint: function setEndpoint(endpoint) {
636 this.endpoint = new AWS.Endpoint(endpoint, this.config);
637 },
638
639 /**
640 * @api private
641 */
642 paginationConfig: function paginationConfig(operation, throwException) {
643 var paginator = this.api.operations[operation].paginator;
644 if (!paginator) {
645 if (throwException) {
646 var e = new Error();
647 throw AWS.util.error(e, 'No pagination configuration for ' + operation);
648 }
649 return null;
650 }
651
652 return paginator;
653 }
654});
655
656AWS.util.update(AWS.Service, {
657
658 /**
659 * Adds one method for each operation described in the api configuration
660 *
661 * @api private
662 */
663 defineMethods: function defineMethods(svc) {
664 AWS.util.each(svc.prototype.api.operations, function iterator(method) {
665 if (svc.prototype[method]) return;
666 var operation = svc.prototype.api.operations[method];
667 if (operation.authtype === 'none') {
668 svc.prototype[method] = function (params, callback) {
669 return this.makeUnauthenticatedRequest(method, params, callback);
670 };
671 } else {
672 svc.prototype[method] = function (params, callback) {
673 return this.makeRequest(method, params, callback);
674 };
675 }
676 });
677 },
678
679 /**
680 * Defines a new Service class using a service identifier and list of versions
681 * including an optional set of features (functions) to apply to the class
682 * prototype.
683 *
684 * @param serviceIdentifier [String] the identifier for the service
685 * @param versions [Array<String>] a list of versions that work with this
686 * service
687 * @param features [Object] an object to attach to the prototype
688 * @return [Class<Service>] the service class defined by this function.
689 */
690 defineService: function defineService(serviceIdentifier, versions, features) {
691 AWS.Service._serviceMap[serviceIdentifier] = true;
692 if (!Array.isArray(versions)) {
693 features = versions;
694 versions = [];
695 }
696
697 var svc = inherit(AWS.Service, features || {});
698
699 if (typeof serviceIdentifier === 'string') {
700 AWS.Service.addVersions(svc, versions);
701
702 var identifier = svc.serviceIdentifier || serviceIdentifier;
703 svc.serviceIdentifier = identifier;
704 } else { // defineService called with an API
705 svc.prototype.api = serviceIdentifier;
706 AWS.Service.defineMethods(svc);
707 }
708 AWS.SequentialExecutor.call(this.prototype);
709 //util.clientSideMonitoring is only available in node
710 if (!this.prototype.publisher && AWS.util.clientSideMonitoring) {
711 var Publisher = AWS.util.clientSideMonitoring.Publisher;
712 var configProvider = AWS.util.clientSideMonitoring.configProvider;
713 var publisherConfig = configProvider();
714 this.prototype.publisher = new Publisher(publisherConfig);
715 if (publisherConfig.enabled) {
716 //if csm is enabled in environment, SDK should send all metrics
717 AWS.Service._clientSideMonitoring = true;
718 }
719 }
720 AWS.SequentialExecutor.call(svc.prototype);
721 AWS.Service.addDefaultMonitoringListeners(svc.prototype);
722 return svc;
723 },
724
725 /**
726 * @api private
727 */
728 addVersions: function addVersions(svc, versions) {
729 if (!Array.isArray(versions)) versions = [versions];
730
731 svc.services = svc.services || {};
732 for (var i = 0; i < versions.length; i++) {
733 if (svc.services[versions[i]] === undefined) {
734 svc.services[versions[i]] = null;
735 }
736 }
737
738 svc.apiVersions = Object.keys(svc.services).sort();
739 },
740
741 /**
742 * @api private
743 */
744 defineServiceApi: function defineServiceApi(superclass, version, apiConfig) {
745 var svc = inherit(superclass, {
746 serviceIdentifier: superclass.serviceIdentifier
747 });
748
749 function setApi(api) {
750 if (api.isApi) {
751 svc.prototype.api = api;
752 } else {
753 svc.prototype.api = new Api(api);
754 }
755 }
756
757 if (typeof version === 'string') {
758 if (apiConfig) {
759 setApi(apiConfig);
760 } else {
761 try {
762 setApi(AWS.apiLoader(superclass.serviceIdentifier, version));
763 } catch (err) {
764 throw AWS.util.error(err, {
765 message: 'Could not find API configuration ' +
766 superclass.serviceIdentifier + '-' + version
767 });
768 }
769 }
770 if (!Object.prototype.hasOwnProperty.call(superclass.services, version)) {
771 superclass.apiVersions = superclass.apiVersions.concat(version).sort();
772 }
773 superclass.services[version] = svc;
774 } else {
775 setApi(version);
776 }
777
778 AWS.Service.defineMethods(svc);
779 return svc;
780 },
781
782 /**
783 * @api private
784 */
785 hasService: function(identifier) {
786 return Object.prototype.hasOwnProperty.call(AWS.Service._serviceMap, identifier);
787 },
788
789 /**
790 * @param attachOn attach default monitoring listeners to object
791 *
792 * Each monitoring event should be emitted from service client to service constructor prototype and then
793 * to global service prototype like bubbling up. These default monitoring events listener will transfer
794 * the monitoring events to the upper layer.
795 * @api private
796 */
797 addDefaultMonitoringListeners: function addDefaultMonitoringListeners(attachOn) {
798 attachOn.addNamedListener('MONITOR_EVENTS_BUBBLE', 'apiCallAttempt', function EVENTS_BUBBLE(event) {
799 var baseClass = Object.getPrototypeOf(attachOn);
800 if (baseClass._events) baseClass.emit('apiCallAttempt', [event]);
801 });
802 attachOn.addNamedListener('CALL_EVENTS_BUBBLE', 'apiCall', function CALL_EVENTS_BUBBLE(event) {
803 var baseClass = Object.getPrototypeOf(attachOn);
804 if (baseClass._events) baseClass.emit('apiCall', [event]);
805 });
806 },
807
808 /**
809 * @api private
810 */
811 _serviceMap: {}
812});
813
814AWS.util.mixin(AWS.Service, AWS.SequentialExecutor);
815
816/**
817 * @api private
818 */
819module.exports = AWS.Service;