UNPKG

@google-cloud/pubsub

Version:
656 lines (589 loc) 19.3 kB
/*! * Copyright 2014 Google Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /*! * @module pubsub */ 'use strict'; var arrify = require('arrify'); var common = require('@google-cloud/common'); var commonGrpc = require('@google-cloud/common-grpc'); var extend = require('extend'); var is = require('is'); var util = require('util'); /** * @type {module:pubsub/subscription} * @private */ var Subscription = require('./subscription.js'); /** * @type {module:pubsub/topic} * @private */ var Topic = require('./topic.js'); /** * [Cloud Pub/Sub](https://developers.google.com/pubsub/overview) is a * reliable, many-to-many, asynchronous messaging service from Cloud * Platform. * * The `PUBSUB_EMULATOR_HOST` environment variable from the gcloud SDK is * honored, otherwise the actual API endpoint will be used. * * @constructor * @alias module:pubsub * * @resource [Cloud Pub/Sub overview]{@link https://developers.google.com/pubsub/overview} * * @param {object} options - [Configuration object](#/docs). */ function PubSub(options) { if (!(this instanceof PubSub)) { options = common.util.normalizeArguments(this, options); return new PubSub(options); } this.defaultBaseUrl_ = 'pubsub.googleapis.com'; this.determineBaseUrl_(); var config = { baseUrl: this.baseUrl_, customEndpoint: this.customEndpoint_, service: 'pubsub', apiVersion: 'v1', scopes: [ 'https://www.googleapis.com/auth/pubsub', 'https://www.googleapis.com/auth/cloud-platform' ], packageJson: require('../package.json') }; this.options = options; commonGrpc.Service.call(this, config, options); } util.inherits(PubSub, commonGrpc.Service); /** * Create a topic with the given name. * * @resource [Topics: create API Documentation]{@link https://cloud.google.com/pubsub/docs/reference/rest/v1/projects.topics/create} * * @param {string} name - Name of the topic. * @param {function=} callback - The callback function. * @param {?error} callback.err - An error from the API call, may be null. * @param {module:pubsub/topic} callback.topic - The newly created topic. * @param {object} callback.apiResponse - The full API response from the * service. * * @example * pubsub.createTopic('my-new-topic', function(err, topic, apiResponse) { * if (!err) { * // The topic was created successfully. * } * }); * * //- * // If the callback is omitted, we'll return a Promise. * //- * pubsub.createTopic('my-new-topic').then(function(data) { * var topic = data[0]; * var apiResponse = data[1]; * }); */ PubSub.prototype.createTopic = function(name, callback) { var self = this; callback = callback || common.util.noop; var protoOpts = { service: 'Publisher', method: 'createTopic' }; var reqOpts = { name: Topic.formatName_(this.projectId, name) }; this.request(protoOpts, reqOpts, function(err, resp) { if (err) { callback(err, null, resp); return; } var topic = self.topic(name); topic.metadata = resp; callback(null, topic, resp); }); }; /** * Get a list of the subscriptions registered to all of your project's topics. * You may optionally provide a query object as the first argument to customize * the response. * * Your provided callback will be invoked with an error object if an API error * occurred or an array of {module:pubsub/subscription} objects. * * To get subscriptions for a topic, see {module:pubsub/topic}. * * @resource [Subscriptions: list API Documentation]{@link https://cloud.google.com/pubsub/docs/reference/rest/v1/projects.subscriptions/list} * * @param {object=} options - Configuration object. * @param {boolean} options.autoPaginate - Have pagination handled * automatically. Default: true. * @param {number} options.maxApiCalls - Maximum number of API calls to make. * @param {number} options.maxResults - Maximum number of results to return. * @param {number} options.pageSize - Maximum number of results to return. * @param {string} options.pageToken - Page token. * @param {string|module:pubsub/topic} options.topic - The name of the topic to * list subscriptions from. * @param {function} callback - The callback function. * @param {?error} callback.err - An error from the API call, may be null. * @param {module:pubsub/subscription[]} callback.subscriptions - The list of * subscriptions in your project. * @param {object} callback.apiResponse - The full API response from the * service. * * @example * pubsub.getSubscriptions(function(err, subscriptions) { * if (!err) { * // subscriptions is an array of Subscription objects. * } * }); * * //- * // To control how many API requests are made and page through the results * // manually, set `autoPaginate` to `false`. * //- * var callback = function(err, subscriptions, nextQuery, apiResponse) { * if (nextQuery) { * // More results exist. * pubsub.getSubscriptions(nextQuery, callback); * } * }; * * pubsub.getSubscriptions({ * autoPaginate: false * }, callback); * * //- * // If the callback is omitted, we'll return a Promise. * //- * pubsub.getSubscriptions().then(function(data) { * var subscriptions = data[0]; * }); */ PubSub.prototype.getSubscriptions = function(options, callback) { var self = this; if (is.fn(options)) { callback = options; options = {}; } var protoOpts = {}; var reqOpts = extend({}, options); if (options.topic) { protoOpts = { service: 'Publisher', method: 'listTopicSubscriptions' }; if (options.topic instanceof Topic) { reqOpts.topic = options.topic.name; } else { reqOpts.topic = options.topic; } } else { protoOpts = { service: 'Subscriber', method: 'listSubscriptions' }; reqOpts.project = 'projects/' + this.projectId; } this.request(protoOpts, reqOpts, function(err, resp) { if (err) { callback(err, null, null, resp); return; } var subscriptions = arrify(resp.subscriptions).map(function(sub) { // Depending on if we're using a subscriptions.list or // topics.subscriptions.list API endpoint, we will get back a // Subscription resource or just the name of the subscription. var subscriptionInstance = self.subscription(sub.name || sub); if (sub.name) { subscriptionInstance.metadata = sub; } return subscriptionInstance; }); var nextQuery = null; if (resp.nextPageToken) { nextQuery = options; nextQuery.pageToken = resp.nextPageToken; } callback(null, subscriptions, nextQuery, resp); }); }; /** * Get a list of the {module:pubsub/subscription} objects registered to all of * your project's topics as a readable object stream. * * @param {object=} options - Configuration object. See * {module:pubsub#getSubscriptions} for a complete list of options. * @return {stream} * * @example * pubsub.getSubscriptionsStream() * .on('error', console.error) * .on('data', function(subscription) { * // subscription is a Subscription object. * }) * .on('end', function() { * // All subscriptions retrieved. * }); * * //- * // If you anticipate many results, you can end a stream early to prevent * // unnecessary processing and API requests. * //- * pubsub.getSubscriptionsStream() * .on('data', function(topic) { * this.end(); * }); */ PubSub.prototype.getSubscriptionsStream = common.paginator.streamify('getSubscriptions'); /** * Get a list of the topics registered to your project. You may optionally * provide a query object as the first argument to customize the response. * * @resource [Topics: list API Documentation]{@link https://cloud.google.com/pubsub/docs/reference/rest/v1/projects.topics/list} * * @param {object=} query - Query object. * @param {boolean} options.autoPaginate - Have pagination handled * automatically. Default: true. * @param {number} options.maxApiCalls - Maximum number of API calls to make. * @param {number} options.maxResults - Maximum number of results to return. * @param {number} query.pageSize - Max number of results to return. * @param {string} query.pageToken - Page token. * @param {function} callback - The callback function. * @param {?error} callback.err - An error from the API call, may be null. * @param {module:pubsub/topic[]} callback.topics - The list of topics returned. * @param {object} callback.apiResponse - The full API response from the * service. * * @example * pubsub.getTopics(function(err, topics) { * if (!err) { * // topics is an array of Topic objects. * } * }); * * //- * // Customize the query. * //- * pubsub.getTopics({ * pageSize: 3 * }, function(err, topics) {}); * * //- * // To control how many API requests are made and page through the results * // manually, set `autoPaginate` to `false`. * //- * var callback = function(err, rows, nextQuery, apiResponse) { * if (nextQuery) { * // More results exist. * pubsub.getTopics(nextQuery, callback); * } * }; * * pubsub.getTopics({ * autoPaginate: false * }, callback); * * //- * // If the callback is omitted, we'll return a Promise. * //- * pubsub.getTopics().then(function(data) { * var topics = data[0]; * }); */ PubSub.prototype.getTopics = function(query, callback) { var self = this; if (!callback) { callback = query; query = {}; } var protoOpts = { service: 'Publisher', method: 'listTopics' }; var reqOpts = extend({ project: 'projects/' + this.projectId }, query); this.request(protoOpts, reqOpts, function(err, result) { if (err) { callback(err, null, result); return; } var topics = arrify(result.topics).map(function(topic) { var topicInstance = self.topic(topic.name); topicInstance.metadata = topic; return topicInstance; }); var nextQuery = null; if (result.nextPageToken) { nextQuery = query; nextQuery.pageToken = result.nextPageToken; } callback(null, topics, nextQuery, result); }); }; /** * Get a list of the {module:pubsub/topic} objects registered to your project as * a readable object stream. * * @param {object=} query - Configuration object. See * {module:pubsub#getTopics} for a complete list of options. * @return {stream} * * @example * pubsub.getTopicsStream() * .on('error', console.error) * .on('data', function(topic) { * // topic is a Topic object. * }) * .on('end', function() { * // All topics retrieved. * }); * * //- * // If you anticipate many results, you can end a stream early to prevent * // unnecessary processing and API requests. * //- * pubsub.getTopicsStream() * .on('data', function(topic) { * this.end(); * }); */ PubSub.prototype.getTopicsStream = common.paginator.streamify('getTopics'); /** * Create a subscription to a topic. * * All generated subscription names share a common prefix, `autogenerated-`. * * @resource [Subscriptions: create API Documentation]{@link https://cloud.google.com/pubsub/docs/reference/rest/v1/projects.subscriptions/create} * * @throws {Error} If a Topic instance or topic name is not provided. * @throws {Error} If a subName is not provided. * * @param {module:pubsub/topic|string} topic - The Topic to create a * subscription to. * @param {string=} subName - The name of the subscription. If a name is not * provided, a random subscription name will be generated and created. * @param {object=} options - See a * [Subscription resource](https://cloud.google.com/pubsub/docs/reference/rest/v1/projects.subscriptions) * @param {number} options.ackDeadlineSeconds - The maximum time after receiving * a message that you must ack a message before it is redelivered. * @param {boolean} options.autoAck - Automatically acknowledge the message once * it's pulled. (default: false) * @param {string} options.encoding - When pulling for messages, this type is * used when converting a message's data to a string. (default: 'utf-8') * @param {number} options.interval - Interval in milliseconds to check for new * messages. (default: 10) * @param {number} options.maxInProgress - Maximum messages to consume * simultaneously. * @param {string} options.pushEndpoint - A URL to a custom endpoint that * messages should be pushed to. * @param {number} options.timeout - Set a maximum amount of time in * milliseconds on an HTTP request to pull new messages to wait for a * response before the connection is broken. * @param {function} callback - The callback function. * @param {?error} callback.err - An error returned while making this request * @param {module:pubsub/subscription} callback.subscription - The subscription. * @param {object} callback.apiResponse - The full API response. * * @example * //- * // Subscribe to a topic. (Also see {module:pubsub/topic#subscribe}). * //- * var topic = 'messageCenter'; * var name = 'newMessages'; * * pubsub.subscribe(topic, name, function(err, subscription, apiResponse) {}); * * //- * // Omit the name to have one generated automatically. All generated names * // share a common prefix, `autogenerated-`. * //- * pubsub.subscribe(topic, function(err, subscription, apiResponse) { * // subscription.name = The generated name. * }); * * //- * // Customize the subscription. * //- * pubsub.subscribe(topic, name, { * ackDeadlineSeconds: 90, * autoAck: true, * interval: 30 * }, function(err, subscription, apiResponse) {}); * * //- * // If the callback is omitted, we'll return a Promise. * //- * pubsub.subscribe(topic, name).then(function(data) { * var subscription = data[0]; * var apiResponse = data[1]; * }); */ PubSub.prototype.subscribe = function(topic, subName, options, callback) { if (!is.string(topic) && !(topic instanceof Topic)) { throw new Error('A Topic is required for a new subscription.'); } if (is.string(topic)) { topic = this.topic(topic); } if (is.object(subName)) { callback = options; options = subName; subName = ''; } if (is.fn(subName)) { callback = subName; subName = ''; } if (is.fn(options)) { callback = options; options = {}; } options = options || {}; var subscription = this.subscription(subName, options); var protoOpts = { service: 'Subscriber', method: 'createSubscription', timeout: options.timeout }; var reqOpts = extend(true, {}, options, { topic: topic.name, name: subscription.name }); if (reqOpts.pushEndpoint) { reqOpts.pushConfig = { pushEndpoint: reqOpts.pushEndpoint }; } delete reqOpts.autoAck; delete reqOpts.encoding; delete reqOpts.interval; delete reqOpts.maxInProgress; delete reqOpts.pushEndpoint; delete reqOpts.timeout; this.request(protoOpts, reqOpts, function(err, resp) { if (err && err.code !== 409) { callback(err, null, resp); return; } callback(null, subscription, resp); }); }; /** * Create a Subscription object. This command by itself will not run any API * requests. You will receive a {module:pubsub/subscription} object, * which will allow you to interact with a subscription. * * All generated names share a common prefix, `autogenerated-`. * * @param {string=} name - The name of the subscription. If a name is not * provided, a random subscription name will be generated. * @param {object=} options - Configuration object. * @param {boolean} options.autoAck - Automatically acknowledge the message once * it's pulled. (default: false) * @param {string} options.encoding - When pulling for messages, this type is * used when converting a message's data to a string. (default: 'utf-8') * @param {number} options.interval - Interval in milliseconds to check for new * messages. (default: 10) * @param {number} options.maxInProgress - Maximum messages to consume * simultaneously. * @param {number} options.timeout - Set a maximum amount of time in * milliseconds on an HTTP request to pull new messages to wait for a * response before the connection is broken. * @return {module:pubsub/subscription} * * @example * var subscription = pubsub.subscription('my-subscription'); * * // Register a listener for `message` events. * subscription.on('message', function(message) { * // Called every time a message is received. * // message.id = ID of the message. * // message.ackId = ID used to acknowledge the message receival. * // message.data = Contents of the message. * // message.attributes = Attributes of the message. * // message.timestamp = Timestamp when Pub/Sub received the message. * }); */ PubSub.prototype.subscription = function(name, options) { if (is.object(name)) { options = name; name = undefined; } options = options || {}; options.name = name; return new Subscription(this, options); }; /** * Create a Topic object. See {module:pubsub/createTopic} to create a topic. * * @throws {Error} If a name is not provided. * * @param {string} name - The name of the topic. * @return {module:pubsub/topic} * * @example * var topic = pubsub.topic('my-topic'); * * topic.publish({ * data: 'New message!' * }, function(err) {}); */ PubSub.prototype.topic = function(name) { if (!name) { throw new Error('A name must be specified for a new topic.'); } return new Topic(this, name); }; /** * Determine the appropriate endpoint to use for API requests, first trying the * local Pub/Sub emulator environment variable (PUBSUB_EMULATOR_HOST), otherwise * the default JSON API. * * @private */ PubSub.prototype.determineBaseUrl_ = function() { var baseUrl = this.defaultBaseUrl_; var leadingProtocol = new RegExp('^https*://'); var trailingSlashes = new RegExp('/*$'); if (process.env.PUBSUB_EMULATOR_HOST) { this.customEndpoint_ = true; baseUrl = process.env.PUBSUB_EMULATOR_HOST; } this.baseUrl_ = baseUrl .replace(leadingProtocol, '') .replace(trailingSlashes, ''); }; /*! Developer Documentation * * These methods can be auto-paginated. */ common.paginator.extend(PubSub, ['getSubscriptions', 'getTopics']); /*! Developer Documentation * * All async methods (except for streams) will return a Promise in the event * that a callback is omitted. */ common.util.promisifyAll(PubSub, { exclude: ['subscription', 'topic'] }); PubSub.Subscription = Subscription; PubSub.Topic = Topic; module.exports = PubSub; module.exports.v1 = require('./v1');