14
14
* limitations under the License.
15
15
*/
16
16
17
+ var events = require ( 'events' ) ,
18
+ nodeutil = require ( 'util' ) ;
19
+
17
20
var conn = require ( '../common/connection.js' ) ,
18
21
util = require ( '../common/util.js' ) ;
19
22
@@ -31,15 +34,18 @@ var SCOPES = [
31
34
'https://www.googleapis.com/auth/cloud-platform'
32
35
] ;
33
36
34
- // TODO(jbd): Emit message and error events if in polled-mode.
35
- // sub.on('meessage', console.log)
36
- // sub.on('error')
37
-
38
37
function Subscription ( conn , name ) {
38
+ var that = this ;
39
39
this . conn = conn ;
40
40
this . name = name ;
41
+
42
+ this . autoAck = false ;
43
+ this . pullIntervalInMs = 10 ;
44
+ this . closed = false ;
41
45
}
42
46
47
+ nodeutil . inherits ( Subscription , events . EventEmitter ) ;
48
+
43
49
/**
44
50
* Acknowledges the backend that message is retrieved.
45
51
* @param {Array<String> } ids A list of message IDs.
@@ -56,44 +62,104 @@ Subscription.prototype.ack = function(ids, callback) {
56
62
57
63
/**
58
64
* Pulls from the subscribed topic.
59
- * @param {Boolean } opts.returnImmediately If set, the system will respond immediately.
60
- * Otherwise, wait until new messages are
61
- * available. Returns if timeout is reached.
62
- * @param {Boolean } opts.autoAck Automatically acknowledges the
63
- * message once it's pulled.
64
- * @param {Function } callback Callback function.
65
+ * @param {Boolean } opts.returnImmediately If set, the system will respond immediately.
66
+ * Otherwise, wait until new messages are
67
+ * available. Returns if timeout is reached.
68
+ * @param {Function } callback Callback.
65
69
*/
66
70
Subscription . prototype . pull = function ( opts , callback ) {
67
- // TODO(jbd): Make opts optional.
68
71
var that = this ;
69
- var autoAck = ! ! opts . autoAck ;
72
+ // TODO(jbd): Should not be racing with other pull.
73
+ // TOOD(jbd): Make opts optional.
70
74
var body = {
71
75
subscription : this . name ,
72
76
returnImmediately : ! ! opts . returnImmediately
73
77
} ;
74
78
this . conn . makeReq ( 'POST' , 'subscriptions/pull' , null , body , function ( err , message ) {
75
- if ( err ) { return callback ( err ) ; }
76
- if ( ! autoAck ) {
77
- return callback ( null , message ) ;
79
+ // TODO(jbd): Fix API to return a list of messages.
80
+ if ( err ) {
81
+ callback ( err ) ;
82
+ return ;
83
+ }
84
+ if ( ! that . autoAck ) {
85
+ that . emitMessage_ ( message ) ;
86
+ callback ( ) ;
87
+ return ;
78
88
}
79
89
that . ack ( message . ackId , function ( err ) {
80
- if ( err ) { return callback ( err ) ; }
81
- callback ( null , message ) ;
90
+ if ( err ) {
91
+ callback ( err ) ;
92
+ return ;
93
+ }
94
+ that . emitMessage_ ( message ) ;
95
+ callback ( ) ;
82
96
} ) ;
83
97
} ) ;
84
98
} ;
85
99
86
100
/**
87
- * Unsubscribes the current subscription. Pull requests from the current
101
+ * Polls the backend for new messages.
102
+ */
103
+ Subscription . prototype . startPulling_ = function ( ) {
104
+ var that = this ;
105
+ var pullFn = function ( ) {
106
+ if ( that . closed ) {
107
+ return ;
108
+ }
109
+ that . pull ( { returnImmediately : false } , function ( err ) {
110
+ // TODO(jbd): Fix API to return a more explicit error code or message.
111
+ if ( err && err . message . indexOf ( 'has no more messages' ) < 0 ) {
112
+ that . emitError_ ( err ) ;
113
+ }
114
+ setTimeout ( function ( ) {
115
+ pullFn ( ) ;
116
+ } , that . pullIntervalInMs ) ;
117
+ } ) ;
118
+ } ;
119
+ pullFn ( ) ;
120
+ }
121
+
122
+ /**
123
+ * Deletes the current subscription. Pull requests from the current
88
124
* subscription will be errored once unsubscription is done.
89
125
* @param {Function } opt_callback Optional callback.
90
126
*/
91
- Subscription . prototype . unsubscribe = function ( opt_callback ) {
127
+ Subscription . prototype . del = function ( opt_callback ) {
128
+ var that = this ;
92
129
cb = opt_callback || util . noop ;
93
130
var path = util . format ( 'subscriptions/{fullName}' , {
94
131
fullName : this . name
95
132
} ) ;
96
- this . conn . makeReq ( 'DELETE' , path , null , true , cb ) ;
133
+ this . conn . makeReq ( 'DELETE' , path , null , true , function ( err ) {
134
+ if ( err ) return cb ( err ) ;
135
+ that . closed = true ;
136
+ cb ( err ) ;
137
+ } ) ;
138
+ } ;
139
+
140
+ /**
141
+ * Closes the subscription.
142
+ */
143
+ Subscription . prototype . close = function ( ) {
144
+ this . closed = true ;
145
+ } ;
146
+
147
+ /**
148
+ * Emits a 'message' event with the provided message.
149
+ */
150
+ Subscription . prototype . emitMessage_ = function ( msg ) {
151
+ if ( msg . pubsubEvent && msg . pubsubEvent . message ) {
152
+ var data = msg . pubsubEvent . message . data ;
153
+ msg . pubsubEvent . message . data = new Buffer ( data , 'base64' ) . toString ( 'utf-8' ) ;
154
+ }
155
+ this . emit ( 'message' , msg ) ;
156
+ } ;
157
+
158
+ /**
159
+ * Emits an error with the provided error.
160
+ */
161
+ Subscription . prototype . emitError_ = function ( err ) {
162
+ this . emit ( 'error' , err ) ;
97
163
} ;
98
164
99
165
/**
@@ -112,7 +178,12 @@ function Topic(conn, name) {
112
178
* @param {Function } opt_callback Optional callback.
113
179
*/
114
180
Topic . prototype . publish = function ( data , opt_callback ) {
115
- this . publishMessage ( { topic : this . name , data : data } , opt_callback ) ;
181
+ this . publishMessage ( {
182
+ topic : this . name ,
183
+ message : {
184
+ data : new Buffer ( data ) . toString ( 'base64' )
185
+ }
186
+ } , opt_callback ) ;
116
187
} ;
117
188
118
189
/**
@@ -132,7 +203,7 @@ Topic.prototype.publishMessage = function(message, opt_callback) {
132
203
*/
133
204
Topic . prototype . del = function ( opt_callback ) {
134
205
var path = 'topics/' + this . name ;
135
- this . conn . makeReq ( 'DELETE' , path , null , true , cb ) ;
206
+ this . conn . makeReq ( 'DELETE' , path , null , true , opt_callback ) ;
136
207
} ;
137
208
138
209
/**
@@ -192,25 +263,62 @@ Connection.prototype.listSubscriptions = function(query, callback) {
192
263
} ;
193
264
194
265
/**
195
- * Subscribe with the provided options.
196
- * @param {string } opts.name Name of the subscription.
197
- * @param {string } opts.topicName Name of the topic to subscribe.
198
- * @param {Function } opt_callback Optional callback.
266
+ * Gets a subscription.
267
+ * @param {string } name Name of the subscription.
268
+ * @param {Function } callback Callback.
199
269
*/
200
- Connection . prototype . subscribe = function ( opts , opt_callback ) {
270
+ Connection . prototype . getSubscription = function ( name , callback ) {
201
271
var that = this ;
202
- var cb = opt_callback || util . noop ;
203
- var body = {
204
- topic :'/topics/' + this . id + '/' + opts . topicName ,
272
+ var fullName = '/subscriptions/' + this . id + '/' + name ;
273
+ this . makeReq ( 'GET' , 'subscriptions/' + fullName , null , true , function ( err ) {
274
+ if ( err ) {
275
+ callback ( err ) ;
276
+ return ;
277
+ }
278
+ callback ( null , new Subscription ( that , fullName ) ) ;
279
+ } ) ;
280
+ } ;
281
+
282
+ Connection . prototype . createSubscription = function ( opts , callback ) {
283
+ var that = this ;
284
+ var subscription = {
285
+ topic :'/topics/' + this . id + '/' + opts . topic ,
205
286
name : '/subscriptions/' + this . id + '/' + opts . name ,
206
287
ackDeadlineSeconds : opts . ackDeadlineSeconds
207
288
} ;
208
- this . makeReq ( 'POST' , 'subscriptions' , null , body , function ( err , item ) {
209
- // TODO(jbd): maybe init a subscription instance if http 200 or 409.
210
- cb ( err , new Subscription ( that , body . name ) ) ;
289
+ this . makeReq ( 'POST' , 'subscriptions' , null , subscription , function ( err ) {
290
+ if ( err ) {
291
+ callback ( err ) ;
292
+ return ;
293
+ }
294
+ callback ( null , new Subscription ( that , subscription . name ) ) ;
211
295
} ) ;
212
296
} ;
213
297
298
+ /**
299
+ * Subscribe with the provided options.
300
+ * @param {string } name Name of the subscription.
301
+ * @param {Boolean } opts.autoAck Automatically acknowledges the
302
+ * message once it's pulled.
303
+ * @return {Subscription }
304
+ */
305
+ Connection . prototype . subscribe = function ( name , opts ) {
306
+ opts = opts || { } ;
307
+
308
+ var fullName = '/subscriptions/' + this . id + '/' + name ;
309
+ var sub = new Subscription ( this , fullName ) ;
310
+ sub . autoAck = ! ! opts . autoAck ;
311
+ this . getSubscription ( name , function ( err ) {
312
+ if ( err ) {
313
+ sub . emitError_ ( err ) ;
314
+ return ;
315
+ }
316
+ sub . emit ( 'ready' ) ;
317
+ sub . startPulling_ ( ) ;
318
+ } ) ;
319
+ return sub ;
320
+ } ;
321
+
214
322
/**
215
323
* Lists topics.
216
324
* @param {string } query.pageToken Page token.
@@ -306,16 +414,26 @@ Connection.prototype.makeReq = function(method, path, q, body, callback) {
306
414
}
307
415
this . conn . req ( reqOpts , function ( err , res , body ) {
308
416
if ( body && body . error ) {
309
- return callback ( body . error ) ;
417
+ callback ( new util . ApiError ( body . error ) ) ; return ;
310
418
}
311
419
if ( res && ( res . statusCode < 200 || res . statusCode > 299 ) ) {
312
- return callback ( new Error ( 'error during request, statusCode: ' + res . statusCode ) ) ;
420
+ callback ( new Error ( 'error during request, statusCode: ' + res . statusCode ) ) ; return ;
313
421
}
314
- callback ( err , body ) ;
422
+ callback ( null , body ) ;
315
423
} ) ;
316
424
} ;
317
425
318
426
/**
319
427
* Exports Connection.
320
428
*/
321
429
module . exports . Connection = Connection ;
430
+
431
+ /**
432
+ * Exports Topic.
433
+ */
434
+ module . exports . Topic = Topic ;
435
+
436
+ /**
437
+ * Exports Subscription.
438
+ */
439
+ module . exports . Subscription = Subscription ;
0 commit comments