hubot 3.3.1 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,312 @@
1
+ /*
2
+ Copyright (c) 2014 rick
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining
5
+ a copy of this software and associated documentation files (the
6
+ "Software"), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish,
8
+ distribute, sublicense, and/or sell copies of the Software, and to
9
+ permit persons to whom the Software is furnished to do so, subject to
10
+ the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ */
23
+
24
+ /*
25
+
26
+ April 15, 2023
27
+ Reasoning:
28
+ ScopedHttpClient is no longer maintained.
29
+
30
+ Decision:
31
+ Implement a phased approach to deprecate `robot.http` all together in favor of `fetch`.
32
+ 1. Convert ScopedHttpClient to Javascript and include the module in this repo
33
+ 2. Add a deprecation warning to `robot.http`
34
+ 3. Remove `robot.http` in a future release
35
+ */
36
+ const path = require('path')
37
+ const http = require('http')
38
+ const https = require('https')
39
+ const qs = require('querystring')
40
+
41
+ const nonPassThroughOptions = [
42
+ 'headers', 'hostname', 'encoding', 'auth', 'port',
43
+ 'protocol', 'agent', 'httpAgent', 'httpsAgent', 'query', 'host', 'path',
44
+ 'pathname', 'slashes', 'hash'
45
+ ]
46
+
47
+ class ScopedClient {
48
+ constructor (url, options) {
49
+ this.options = this.buildOptions(url, options)
50
+ this.passthroughOptions = reduce(extend({}, this.options), nonPassThroughOptions)
51
+ }
52
+
53
+ request (method, reqBody, callback) {
54
+ let req
55
+ if (typeof (reqBody) === 'function') {
56
+ callback = reqBody
57
+ reqBody = null
58
+ }
59
+
60
+ try {
61
+ let requestModule
62
+ const headers = extend({}, this.options.headers)
63
+ const sendingData = reqBody && (reqBody.length > 0)
64
+ headers.Host = this.options.hostname
65
+ if (this.options.port) { headers.Host += `:${this.options.port}` }
66
+
67
+ // If `callback` is `undefined` it means the caller isn't going to stream
68
+ // the body of the request using `callback` and we can set the
69
+ // content-length header ourselves.
70
+ //
71
+ // There is no way to conveniently assert in an else clause because the
72
+ // transfer encoding could be chunked or using a newer framing mechanism.
73
+ // And this is why we should'nt be creating a wrapper around http.
74
+ // Please just use `fetch`.
75
+ if (callback === undefined) {
76
+ headers['Content-Length'] = sendingData ? Buffer.byteLength(reqBody, this.options.encoding) : 0
77
+ }
78
+
79
+ if (this.options.auth) {
80
+ headers.Authorization = 'Basic ' + Buffer.from(this.options.auth, 'base64')
81
+ }
82
+
83
+ const port = this.options.port ||
84
+ ScopedClient.defaultPort[this.options.protocol] || 80
85
+
86
+ let {
87
+ agent
88
+ } = this.options
89
+ if (this.options.protocol === 'https:') {
90
+ requestModule = https
91
+ if (this.options.httpsAgent) { agent = this.options.httpsAgent }
92
+ } else {
93
+ requestModule = http
94
+ if (this.options.httpAgent) { agent = this.options.httpAgent }
95
+ }
96
+
97
+ const requestOptions = {
98
+ port,
99
+ host: this.options.hostname,
100
+ method,
101
+ path: this.fullPath(),
102
+ headers,
103
+ agent
104
+ }
105
+
106
+ // Extends the previous request options with all remaining options
107
+ extend(requestOptions, this.passthroughOptions)
108
+
109
+ req = requestModule.request(requestOptions)
110
+
111
+ if (this.options.timeout) {
112
+ req.setTimeout(this.options.timeout, () => req.abort())
113
+ }
114
+
115
+ if (callback) {
116
+ req.on('error', callback)
117
+ }
118
+ if (sendingData) { req.write(reqBody, this.options.encoding) }
119
+ if (callback) { callback(null, req) }
120
+ } catch (err) {
121
+ if (callback) { callback(err, req) }
122
+ }
123
+
124
+ return callback => {
125
+ if (callback) {
126
+ req.on('response', res => {
127
+ res.setEncoding(this.options.encoding)
128
+ let body = ''
129
+ res.on('data', chunk => {
130
+ body += chunk
131
+ })
132
+
133
+ return res.on('end', () => callback(null, res, body))
134
+ })
135
+ req.on('error', error => callback(error, null, null))
136
+ }
137
+
138
+ req.end()
139
+ return this
140
+ }
141
+ }
142
+
143
+ // Adds the query string to the path.
144
+ fullPath (p) {
145
+ const search = qs.stringify(this.options.query)
146
+ let full = this.join(p)
147
+ if (search.length > 0) { full += `?${search}` }
148
+ return full
149
+ }
150
+
151
+ scope (url, options, callback) {
152
+ const override = this.buildOptions(url, options)
153
+ const scoped = new ScopedClient(this.options)
154
+ .protocol(override.protocol)
155
+ .host(override.hostname)
156
+ .path(override.pathname)
157
+
158
+ if (typeof (url) === 'function') {
159
+ callback = url
160
+ } else if (typeof (options) === 'function') {
161
+ callback = options
162
+ }
163
+ if (callback) { callback(scoped) }
164
+ return scoped
165
+ }
166
+
167
+ join (suffix) {
168
+ const p = this.options.pathname || '/'
169
+ if (suffix && (suffix.length > 0)) {
170
+ if (suffix.match(/^\//)) {
171
+ return suffix
172
+ } else {
173
+ return path.join(p, suffix)
174
+ }
175
+ } else {
176
+ return p
177
+ }
178
+ }
179
+
180
+ path (p) {
181
+ this.options.pathname = this.join(p)
182
+ return this
183
+ }
184
+
185
+ query (key, value) {
186
+ if (!this.options.query) { this.options.query = {} }
187
+ if (typeof (key) === 'string') {
188
+ if (value) {
189
+ this.options.query[key] = value
190
+ } else {
191
+ delete this.options.query[key]
192
+ }
193
+ } else {
194
+ extend(this.options.query, key)
195
+ }
196
+ return this
197
+ }
198
+
199
+ host (h) {
200
+ if (h && (h.length > 0)) { this.options.hostname = h }
201
+ return this
202
+ }
203
+
204
+ port (p) {
205
+ if (p && ((typeof (p) === 'number') || (p.length > 0))) {
206
+ this.options.port = p
207
+ }
208
+ return this
209
+ }
210
+
211
+ protocol (p) {
212
+ if (p && (p.length > 0)) { this.options.protocol = p }
213
+ return this
214
+ }
215
+
216
+ encoding (e) {
217
+ if (e == null) { e = 'utf-8' }
218
+ this.options.encoding = e
219
+ return this
220
+ }
221
+
222
+ timeout (time) {
223
+ this.options.timeout = time
224
+ return this
225
+ }
226
+
227
+ auth (user, pass) {
228
+ if (!user) {
229
+ this.options.auth = null
230
+ } else if (!pass && user.match(/:/)) {
231
+ this.options.auth = user
232
+ } else {
233
+ this.options.auth = `${user}:${pass}`
234
+ }
235
+ return this
236
+ }
237
+
238
+ header (name, value) {
239
+ this.options.headers[name] = value
240
+ return this
241
+ }
242
+
243
+ headers (h) {
244
+ extend(this.options.headers, h)
245
+ return this
246
+ }
247
+
248
+ buildOptions () {
249
+ const options = {}
250
+ let i = 0
251
+ while (arguments[i]) {
252
+ const ty = typeof arguments[i]
253
+ if (ty === 'string') {
254
+ const parsedUrl = new URL(arguments[i])
255
+ const query = {}
256
+ parsedUrl.searchParams.forEach((v, k) => {
257
+ query[k] = v
258
+ })
259
+
260
+ extend(options, {
261
+ href: parsedUrl.href,
262
+ origin: parsedUrl.origin,
263
+ protocol: parsedUrl.protocol,
264
+ username: parsedUrl.username,
265
+ password: parsedUrl.password,
266
+ host: parsedUrl.host,
267
+ hostname: parsedUrl.hostname,
268
+ port: parsedUrl.port,
269
+ pathname: parsedUrl.pathname,
270
+ search: parsedUrl.search,
271
+ searchParams: parsedUrl.searchParams,
272
+ query,
273
+ hash: parsedUrl.hash
274
+ })
275
+ delete options.url
276
+ delete options.href
277
+ delete options.search
278
+ } else if (ty !== 'function') {
279
+ extend(options, arguments[i])
280
+ }
281
+ i += 1
282
+ }
283
+ if (!options.headers) { options.headers = {} }
284
+ if (options.encoding == null) { options.encoding = 'utf-8' }
285
+ return options
286
+ }
287
+ }
288
+
289
+ ScopedClient.methods = ['GET', 'POST', 'PATCH', 'PUT', 'DELETE', 'HEAD']
290
+ ScopedClient.methods.forEach(method => {
291
+ ScopedClient.prototype[method.toLowerCase()] = function (body, callback) { return this.request(method, body, callback) }
292
+ })
293
+ ScopedClient.prototype.del = ScopedClient.prototype.delete
294
+
295
+ ScopedClient.defaultPort = { 'http:': 80, 'https:': 443, http: 80, https: 443 }
296
+
297
+ const extend = function (a, b) {
298
+ Object.keys(b).forEach(prop => {
299
+ a[prop] = b[prop]
300
+ })
301
+ return a
302
+ }
303
+
304
+ // Removes keys specified in second parameter from first parameter
305
+ const reduce = function (a, b) {
306
+ for (const propName of Array.from(b)) {
307
+ delete a[propName]
308
+ }
309
+ return a
310
+ }
311
+
312
+ exports.create = (url, options) => new ScopedClient(url, options)
package/src/robot.js CHANGED
@@ -6,7 +6,7 @@ const path = require('path')
6
6
 
7
7
  const async = require('async')
8
8
  const Log = require('log')
9
- const HttpClient = require('scoped-http-client')
9
+ const HttpClient = require('./httpclient')
10
10
 
11
11
  const Brain = require('./brain')
12
12
  const Response = require('./response')
@@ -132,7 +132,7 @@ class Robot {
132
132
  const name = this.name.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&')
133
133
 
134
134
  if (regexStartsWithAnchor) {
135
- this.logger.warning(`Anchors don’t work well with respond, perhaps you want to use 'hear'`)
135
+ this.logger.warning('Anchors don’t work well with respond, perhaps you want to use \'hear\'')
136
136
  this.logger.warning(`The regex in question was ${regex.toString()}`)
137
137
  }
138
138
 
@@ -204,7 +204,7 @@ class Robot {
204
204
  invokeErrorHandlers (error, res) {
205
205
  this.logger.error(error.stack)
206
206
 
207
- this.errorHandlers.map((errorHandler) => {
207
+ this.errorHandlers.forEach((errorHandler) => {
208
208
  try {
209
209
  errorHandler(error, res)
210
210
  } catch (errorHandlerError) {
@@ -352,7 +352,7 @@ class Robot {
352
352
  const full = path.join(filepath, path.basename(filename, ext))
353
353
 
354
354
  // see https://github.com/hubotio/hubot/issues/1355
355
- if (!require.extensions[ext]) { // eslint-disable-line
355
+ if (['.js', '.mjs', '.coffee'].indexOf(ext) == -1) { // eslint-disable-line
356
356
  return
357
357
  }
358
358
 
@@ -429,22 +429,25 @@ class Robot {
429
429
  const paramLimit = parseInt(process.env.EXPRESS_PARAMETER_LIMIT) || 1000
430
430
 
431
431
  const express = require('express')
432
+ const basicAuth = require('express-basic-auth')
432
433
  const multipart = require('connect-multiparty')
433
434
 
434
435
  const app = express()
435
436
 
436
437
  app.use((req, res, next) => {
437
- res.setHeader('X-Powered-By', `hubot/${this.name}`)
438
+ res.setHeader('X-Powered-By', `hubot/${encodeURI(this.name)}`)
438
439
  return next()
439
440
  })
440
441
 
441
442
  if (user && pass) {
442
- app.use(express.basicAuth(user, pass))
443
+ const authUser = {}
444
+ authUser[user] = pass
445
+ app.use(basicAuth({ users: authUser }))
443
446
  }
444
447
  app.use(express.query())
445
448
 
446
- app.use(express.json())
447
- app.use(express.urlencoded({ limit, parameterLimit: paramLimit }))
449
+ app.use(express.json({ limit }))
450
+ app.use(express.urlencoded({ limit, parameterLimit: paramLimit, extended: true }))
448
451
  // replacement for deprecated express.multipart/connect.multipart
449
452
  // limit to 100mb, as per the old behavior
450
453
  app.use(multipart({ maxFilesSize: 100 * 1024 * 1024 }))
@@ -527,7 +530,7 @@ class Robot {
527
530
 
528
531
  const useStrictHeaderRegex = /^["']use strict['"];?\s+/
529
532
  const lines = body.replace(useStrictHeaderRegex, '').split(/(?:\n|\r\n|\r)/)
530
- .reduce(toHeaderCommentBlock, {lines: [], isHeader: true}).lines
533
+ .reduce(toHeaderCommentBlock, { lines: [], isHeader: true }).lines
531
534
  .filter(Boolean) // remove empty lines
532
535
  let currentSection = null
533
536
  let nextSection
@@ -577,11 +580,11 @@ class Robot {
577
580
  // envelope - A Object with message, room and user details.
578
581
  // strings - One or more Strings for each message to send.
579
582
  //
580
- // Returns nothing.
583
+ // Returns whatever the extending adapter returns.
581
584
  send (envelope/* , ...strings */) {
582
585
  const strings = [].slice.call(arguments, 1)
583
586
 
584
- this.adapter.send.apply(this.adapter, [envelope].concat(strings))
587
+ return this.adapter.send.apply(this.adapter, [envelope].concat(strings))
585
588
  }
586
589
 
587
590
  // Public: A helper reply function which delegates to the adapter's reply
@@ -590,11 +593,11 @@ class Robot {
590
593
  // envelope - A Object with message, room and user details.
591
594
  // strings - One or more Strings for each message to send.
592
595
  //
593
- // Returns nothing.
596
+ // Returns whatever the extending adapter returns.
594
597
  reply (envelope/* , ...strings */) {
595
598
  const strings = [].slice.call(arguments, 1)
596
599
 
597
- this.adapter.reply.apply(this.adapter, [envelope].concat(strings))
600
+ return this.adapter.reply.apply(this.adapter, [envelope].concat(strings))
598
601
  }
599
602
 
600
603
  // Public: A helper send function to message a room that the robot is in.
@@ -602,12 +605,12 @@ class Robot {
602
605
  // room - String designating the room to message.
603
606
  // strings - One or more Strings for each message to send.
604
607
  //
605
- // Returns nothing.
608
+ // Returns whatever the extending adapter returns.
606
609
  messageRoom (room/* , ...strings */) {
607
610
  const strings = [].slice.call(arguments, 1)
608
611
  const envelope = { room }
609
612
 
610
- this.adapter.send.apply(this.adapter, [envelope].concat(strings))
613
+ return this.adapter.send.apply(this.adapter, [envelope].concat(strings))
611
614
  }
612
615
 
613
616
  // Public: A wrapper around the EventEmitter API to make usage
package/src/user.js CHANGED
@@ -18,7 +18,7 @@ class User {
18
18
  // robot itself on the user object, preventing it from
19
19
  // being serialized into the brain.
20
20
  if (options.robot) {
21
- let robot = options.robot
21
+ const robot = options.robot
22
22
  delete options.robot
23
23
  this._getRobot = function () { return robot }
24
24
  } else {
@@ -55,7 +55,7 @@ class User {
55
55
  }
56
56
 
57
57
  _getDatastore () {
58
- let robot = this._getRobot()
58
+ const robot = this._getRobot()
59
59
  if (robot) {
60
60
  return robot.datastore
61
61
  }
@@ -12,7 +12,7 @@ const Adapter = require('../src/adapter')
12
12
 
13
13
  describe('Adapter', function () {
14
14
  beforeEach(function () {
15
- this.robot = {receive: sinon.spy()}
15
+ this.robot = { receive: sinon.spy() }
16
16
  })
17
17
 
18
18
  // this one is hard, as it requires files
@@ -30,9 +30,9 @@ describe('Brain', function () {
30
30
 
31
31
  this.brain = new Brain(this.mockRobot)
32
32
 
33
- this.user1 = this.brain.userForId('1', {name: 'Guy One'})
34
- this.user2 = this.brain.userForId('2', {name: 'Guy One Two'})
35
- this.user3 = this.brain.userForId('3', {name: 'Girl Three'})
33
+ this.user1 = this.brain.userForId('1', { name: 'Guy One' })
34
+ this.user2 = this.brain.userForId('2', { name: 'Guy One Two' })
35
+ this.user3 = this.brain.userForId('3', { name: 'Girl Three' })
36
36
  })
37
37
 
38
38
  afterEach(function () {
@@ -47,7 +47,7 @@ describe('Brain', function () {
47
47
  2: 'old'
48
48
  }
49
49
 
50
- this.brain.mergeData({2: 'new'})
50
+ this.brain.mergeData({ 2: 'new' })
51
51
 
52
52
  expect(this.brain.data).to.deep.equal({
53
53
  1: 'old',
@@ -62,8 +62,8 @@ describe('Brain', function () {
62
62
  })
63
63
 
64
64
  it('coerces loaded data into User objects', function () {
65
- this.brain.mergeData({users: {'4': {'name': 'new', 'id': '4'}}})
66
- let user = this.brain.userForId('4')
65
+ this.brain.mergeData({ users: { 4: { name: 'new', id: '4' } } })
66
+ const user = this.brain.userForId('4')
67
67
  expect(user.constructor.name).to.equal('User')
68
68
  expect(user.id).to.equal('4')
69
69
  expect(user.name).to.equal('new')
@@ -213,7 +213,7 @@ describe('Brain', function () {
213
213
  })
214
214
 
215
215
  it('passes the provided options to the new User', function () {
216
- const newUser = this.brain.userForId('all-new-user', {name: 'All New User', prop: 'mine'})
216
+ const newUser = this.brain.userForId('all-new-user', { name: 'All New User', prop: 'mine' })
217
217
  expect(newUser.name).to.equal('All New User')
218
218
  expect(newUser.prop).to.equal('mine')
219
219
  })
@@ -322,11 +322,11 @@ describe('Brain', function () {
322
322
 
323
323
  it('returns User objects, not POJOs', function () {
324
324
  expect(this.brain.userForId('1').constructor.name).to.equal('User')
325
- for (let user of this.brain.usersForFuzzyName('Guy')) {
325
+ for (const user of this.brain.usersForFuzzyName('Guy')) {
326
326
  expect(user.constructor.name).to.equal('User')
327
327
  }
328
328
 
329
- for (let user of this.brain.usersForRawFuzzyName('Guy One')) {
329
+ for (const user of this.brain.usersForRawFuzzyName('Guy One')) {
330
330
  expect(user.constructor.name).to.equal('User')
331
331
  }
332
332
 
@@ -26,8 +26,8 @@ describe('Datastore', function () {
26
26
 
27
27
  this.robot.brain = new Brain(this.robot)
28
28
  this.robot.datastore = new InMemoryDataStore(this.robot)
29
- this.robot.brain.userForId('1', {name: 'User One'})
30
- this.robot.brain.userForId('2', {name: 'User Two'})
29
+ this.robot.brain.userForId('1', { name: 'User One' })
30
+ this.robot.brain.userForId('2', { name: 'User Two' })
31
31
  })
32
32
 
33
33
  describe('global scope', function () {
@@ -46,9 +46,9 @@ describe('Datastore', function () {
46
46
  })
47
47
 
48
48
  it('can store arbitrary JavaScript values', function () {
49
- let object = {
50
- 'name': 'test',
51
- 'data': [1, 2, 3]
49
+ const object = {
50
+ name: 'test',
51
+ data: [1, 2, 3]
52
52
  }
53
53
  return this.robot.datastore.set('key', object).then(() => {
54
54
  return this.robot.datastore.get('key').then((value) => {
@@ -59,9 +59,9 @@ describe('Datastore', function () {
59
59
  })
60
60
 
61
61
  it('can dig inside objects for values', function () {
62
- let object = {
63
- 'a': 'one',
64
- 'b': 'two'
62
+ const object = {
63
+ a: 'one',
64
+ b: 'two'
65
65
  }
66
66
  return this.robot.datastore.set('key', object).then(() => {
67
67
  return this.robot.datastore.getObject('key', 'a').then((value) => {
@@ -71,9 +71,9 @@ describe('Datastore', function () {
71
71
  })
72
72
 
73
73
  it('can set individual keys inside objects', function () {
74
- let object = {
75
- 'a': 'one',
76
- 'b': 'two'
74
+ const object = {
75
+ a: 'one',
76
+ b: 'two'
77
77
  }
78
78
  return this.robot.datastore.set('object', object).then(() => {
79
79
  return this.robot.datastore.setObject('object', 'c', 'three').then(() => {
@@ -89,7 +89,7 @@ describe('Datastore', function () {
89
89
  it('creates an object from scratch when none exists', function () {
90
90
  return this.robot.datastore.setObject('object', 'key', 'value').then(() => {
91
91
  return this.robot.datastore.get('object').then((value) => {
92
- let expected = {'key': 'value'}
92
+ const expected = { key: 'value' }
93
93
  expect(value).to.deep.equal(expected)
94
94
  })
95
95
  })
@@ -116,12 +116,12 @@ describe('Datastore', function () {
116
116
 
117
117
  describe('User scope', function () {
118
118
  it('has access to the robot object', function () {
119
- let user = this.robot.brain.userForId('1')
119
+ const user = this.robot.brain.userForId('1')
120
120
  expect(user._getRobot()).to.equal(this.robot)
121
121
  })
122
122
 
123
123
  it('can store user data which is separate from global data', function () {
124
- let user = this.robot.brain.userForId('1')
124
+ const user = this.robot.brain.userForId('1')
125
125
  return user.set('blah', 'blah').then(() => {
126
126
  return user.get('blah').then((userBlah) => {
127
127
  return this.robot.datastore.get('blah').then((datastoreBlah) => {
@@ -134,8 +134,8 @@ describe('Datastore', function () {
134
134
  })
135
135
 
136
136
  it('stores user data separate per-user', function () {
137
- let userOne = this.robot.brain.userForId('1')
138
- let userTwo = this.robot.brain.userForId('2')
137
+ const userOne = this.robot.brain.userForId('1')
138
+ const userTwo = this.robot.brain.userForId('2')
139
139
  return userOne.set('blah', 'blah').then(() => {
140
140
  return userOne.get('blah').then((valueOne) => {
141
141
  return userTwo.get('blah').then((valueTwo) => {
@@ -31,7 +31,7 @@ const loadBot = Hubot.loadBot
31
31
  describe('hubot/es2015', function () {
32
32
  it('exports User class', function () {
33
33
  class MyUser extends User {}
34
- const user = new MyUser('id123', {foo: 'bar'})
34
+ const user = new MyUser('id123', { foo: 'bar' })
35
35
 
36
36
  expect(user).to.be.an.instanceof(User)
37
37
  expect(user.id).to.equal('id123')
@@ -7,21 +7,26 @@ class MockAdapter extends Adapter {
7
7
  const strings = [].slice.call(arguments, 1)
8
8
  this.emit('send', envelope, strings)
9
9
  }
10
+
10
11
  reply (envelope/* , ...strings */) {
11
12
  const strings = [].slice.call(arguments, 1)
12
13
  this.emit('reply', envelope, strings)
13
14
  }
15
+
14
16
  topic (envelope/* , ...strings */) {
15
17
  const strings = [].slice.call(arguments, 1)
16
18
  this.emit('topic', envelope, strings)
17
19
  }
20
+
18
21
  play (envelope/* , ...strings */) {
19
22
  const strings = [].slice.call(arguments, 1)
20
23
  this.emit('play', envelope, strings)
21
24
  }
25
+
22
26
  run () {
23
27
  this.emit('connected')
24
28
  }
29
+
25
30
  close () {
26
31
  this.emit('closed')
27
32
  }
@@ -303,7 +303,7 @@ describe('Listener', function () {
303
303
  const testMessage = {}
304
304
 
305
305
  const testListener = this.createListener(function () {})
306
- const testMiddleware = {execute: sinon.spy()}
306
+ const testMiddleware = { execute: sinon.spy() }
307
307
 
308
308
  testListener.call(testMessage, result => {
309
309
  expect(testMiddleware.execute).to.not.have.been.called
@@ -334,7 +334,7 @@ describe('Listener', function () {
334
334
  const listenerCallback = sinon.spy()
335
335
  const testListener = new Listener(this.robot, testMatcher, listenerCallback)
336
336
  // slightly brittle because we are testing for the default options Object
337
- expect(testListener.options).to.deep.equal({id: null})
337
+ expect(testListener.options).to.deep.equal({ id: null })
338
338
  expect(testListener.callback).to.be.equal(listenerCallback)
339
339
  })
340
340