hubot 3.3.2 → 3.5.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.
- package/.github/workflows/nodejs-macos.yml +26 -0
- package/.github/workflows/nodejs-ubuntu.yml +28 -0
- package/.github/workflows/nodejs-windows.yml +26 -0
- package/.github/workflows/release.yml +36 -0
- package/.node-version +1 -0
- package/README.md +4 -3
- package/bin/hubot +1 -1
- package/bin/hubot.js +1 -5
- package/docs/adapters/development.md +35 -36
- package/docs/deploying/windows.md +5 -5
- package/docs/implementation.md +1 -3
- package/docs/index.md +5 -10
- package/docs/patterns.md +146 -101
- package/docs/scripting.md +493 -411
- package/es2015.js +1 -1
- package/package.json +22 -21
- package/src/adapters/campfire.js +18 -18
- package/src/adapters/shell.js +11 -14
- package/src/brain.js +8 -8
- package/src/datastore.js +3 -3
- package/src/httpclient.js +312 -0
- package/src/robot.js +19 -16
- package/src/user.js +2 -2
- package/test/adapter_test.js +1 -1
- package/test/brain_test.js +9 -9
- package/test/datastore_test.js +20 -16
- package/test/es2015_test.js +1 -1
- package/test/fixtures/mock-adapter.js +5 -0
- package/test/listener_test.js +2 -2
- package/test/middleware_test.js +18 -17
- package/test/robot_test.js +28 -13
- package/test/shell_test.js +72 -0
- package/test/user_test.js +2 -2
- package/.travis.yml +0 -27
- package/ROADMAP.md +0 -37
package/es2015.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hubot",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.5.0",
|
|
4
4
|
"author": "hubot",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"github",
|
|
@@ -15,27 +15,24 @@
|
|
|
15
15
|
"url": "https://github.com/hubotio/hubot.git"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"async": "
|
|
19
|
-
"chalk": "^1.0.0",
|
|
18
|
+
"async": "^3.2.4",
|
|
20
19
|
"cline": "^0.8.2",
|
|
21
|
-
"coffeescript": "
|
|
22
|
-
"connect-multiparty": "^2.
|
|
23
|
-
"express": "^4.
|
|
20
|
+
"coffeescript": "^2.7.0",
|
|
21
|
+
"connect-multiparty": "^2.2.0",
|
|
22
|
+
"express": "^4.18.2",
|
|
23
|
+
"express-basic-auth": "^1.2.1",
|
|
24
24
|
"log": "1.4.0",
|
|
25
|
-
"optparse": "1.0.
|
|
26
|
-
"scoped-http-client": "0.11.0"
|
|
25
|
+
"optparse": "^1.0.5"
|
|
27
26
|
},
|
|
28
27
|
"devDependencies": {
|
|
29
|
-
"chai": "
|
|
30
|
-
"coveralls": "^3.0.2",
|
|
28
|
+
"chai": "^4.3.7",
|
|
31
29
|
"is-circular": "^1.0.2",
|
|
32
|
-
"mocha": "^
|
|
33
|
-
"mockery": "^1.
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"sinon": "
|
|
37
|
-
"
|
|
38
|
-
"standard": "^10.0.2"
|
|
30
|
+
"mocha": "^10.2.0",
|
|
31
|
+
"mockery": "^2.1.0",
|
|
32
|
+
"semantic-release": "^21.0.1",
|
|
33
|
+
"sinon": "^15.0.4",
|
|
34
|
+
"sinon-chai": "^3.7.0",
|
|
35
|
+
"standard": "^17.0.0"
|
|
39
36
|
},
|
|
40
37
|
"engines": {
|
|
41
38
|
"node": "> 4.0.0",
|
|
@@ -48,9 +45,13 @@
|
|
|
48
45
|
"scripts": {
|
|
49
46
|
"start": "bin/hubot",
|
|
50
47
|
"pretest": "standard",
|
|
51
|
-
"test": "
|
|
52
|
-
"
|
|
53
|
-
|
|
54
|
-
|
|
48
|
+
"test": "mocha --exit",
|
|
49
|
+
"test:smoke": "node src/**/*.js"
|
|
50
|
+
},
|
|
51
|
+
"release": {
|
|
52
|
+
"branches": [
|
|
53
|
+
"master"
|
|
54
|
+
],
|
|
55
|
+
"dryRun": false
|
|
55
56
|
}
|
|
56
57
|
}
|
package/src/adapters/campfire.js
CHANGED
|
@@ -229,30 +229,30 @@ class CampfireStreaming extends EventEmitter {
|
|
|
229
229
|
},
|
|
230
230
|
|
|
231
231
|
speak (text, callback) {
|
|
232
|
-
const body = { message: {
|
|
232
|
+
const body = { message: { body: text } }
|
|
233
233
|
return self.post(`/room/${id}/speak`, body, callback)
|
|
234
234
|
},
|
|
235
235
|
|
|
236
236
|
message (text, type, callback) {
|
|
237
|
-
const body = { message: {
|
|
237
|
+
const body = { message: { body: text, type } }
|
|
238
238
|
return self.post(`/room/${id}/speak`, body, callback)
|
|
239
239
|
},
|
|
240
240
|
|
|
241
241
|
// listen for activity in channels
|
|
242
242
|
listen () {
|
|
243
243
|
const headers = {
|
|
244
|
-
|
|
245
|
-
|
|
244
|
+
Host: 'streaming.campfirenow.com',
|
|
245
|
+
Authorization: self.authorization,
|
|
246
246
|
'User-Agent': `Hubot/${this.robot != null ? this.robot.version : undefined} (${this.robot != null ? this.robot.name : undefined})`
|
|
247
247
|
}
|
|
248
248
|
|
|
249
249
|
const options = {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
250
|
+
agent: false,
|
|
251
|
+
host: 'streaming.campfirenow.com',
|
|
252
|
+
port: 443,
|
|
253
|
+
path: `/room/${id}/live.json`,
|
|
254
|
+
method: 'GET',
|
|
255
|
+
headers
|
|
256
256
|
}
|
|
257
257
|
|
|
258
258
|
const request = HTTPS.request(options, function (response) {
|
|
@@ -326,19 +326,19 @@ class CampfireStreaming extends EventEmitter {
|
|
|
326
326
|
const logger = this.robot.logger
|
|
327
327
|
|
|
328
328
|
const headers = {
|
|
329
|
-
|
|
330
|
-
|
|
329
|
+
Authorization: this.authorization,
|
|
330
|
+
Host: this.host,
|
|
331
331
|
'Content-Type': 'application/json',
|
|
332
332
|
'User-Agent': `Hubot/${this.robot != null ? this.robot.version : undefined} (${this.robot != null ? this.robot.name : undefined})`
|
|
333
333
|
}
|
|
334
334
|
|
|
335
335
|
const options = {
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
336
|
+
agent: false,
|
|
337
|
+
host: this.host,
|
|
338
|
+
port: 443,
|
|
339
|
+
path,
|
|
340
|
+
method,
|
|
341
|
+
headers
|
|
342
342
|
}
|
|
343
343
|
|
|
344
344
|
if (method === 'POST' || method === 'PUT') {
|
package/src/adapters/shell.js
CHANGED
|
@@ -4,23 +4,23 @@ const fs = require('fs')
|
|
|
4
4
|
const readline = require('readline')
|
|
5
5
|
const Stream = require('stream')
|
|
6
6
|
const cline = require('cline')
|
|
7
|
-
const chalk = require('chalk')
|
|
8
7
|
|
|
9
8
|
const Adapter = require('../adapter')
|
|
10
9
|
|
|
11
|
-
|
|
10
|
+
const _require = require('../message')
|
|
12
11
|
|
|
13
12
|
const TextMessage = _require.TextMessage
|
|
14
13
|
|
|
15
14
|
const historySize = process.env.HUBOT_SHELL_HISTSIZE != null ? parseInt(process.env.HUBOT_SHELL_HISTSIZE) : 1024
|
|
16
15
|
|
|
17
16
|
const historyPath = '.hubot_history'
|
|
17
|
+
const bold = str => `\x1b[1m${str}\x1b[22m`
|
|
18
18
|
|
|
19
19
|
class Shell extends Adapter {
|
|
20
20
|
send (envelope/* , ...strings */) {
|
|
21
21
|
const strings = [].slice.call(arguments, 1)
|
|
22
22
|
|
|
23
|
-
Array.from(strings).forEach(str => console.log(
|
|
23
|
+
Array.from(strings).forEach(str => console.log(bold(str)))
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
emote (envelope/* , ...strings */) {
|
|
@@ -36,15 +36,13 @@ class Shell extends Adapter {
|
|
|
36
36
|
|
|
37
37
|
run () {
|
|
38
38
|
this.buildCli()
|
|
39
|
-
|
|
40
39
|
loadHistory((error, history) => {
|
|
41
40
|
if (error) {
|
|
42
41
|
console.log(error.message)
|
|
43
42
|
}
|
|
44
|
-
|
|
45
43
|
this.cli.history(history)
|
|
46
44
|
this.cli.interact(`${this.robot.name}> `)
|
|
47
|
-
return this.emit('connected')
|
|
45
|
+
return this.emit('connected', this)
|
|
48
46
|
})
|
|
49
47
|
}
|
|
50
48
|
|
|
@@ -82,7 +80,7 @@ class Shell extends Adapter {
|
|
|
82
80
|
})
|
|
83
81
|
|
|
84
82
|
this.cli.on('close', () => {
|
|
85
|
-
let
|
|
83
|
+
let history, i, item, len
|
|
86
84
|
|
|
87
85
|
history = this.cli.history()
|
|
88
86
|
|
|
@@ -90,25 +88,24 @@ class Shell extends Adapter {
|
|
|
90
88
|
return this.shutdown()
|
|
91
89
|
}
|
|
92
90
|
|
|
93
|
-
startIndex = history.length - historySize
|
|
91
|
+
const startIndex = history.length - historySize
|
|
94
92
|
history = history.reverse().splice(startIndex, historySize)
|
|
95
|
-
fileOpts = {
|
|
93
|
+
const fileOpts = {
|
|
96
94
|
mode: 0x180
|
|
97
95
|
}
|
|
98
96
|
|
|
99
|
-
outstream = fs.createWriteStream(historyPath, fileOpts)
|
|
100
|
-
outstream.on('
|
|
101
|
-
|
|
97
|
+
const outstream = fs.createWriteStream(historyPath, fileOpts)
|
|
98
|
+
outstream.on('end', this.shutdown.bind(this))
|
|
102
99
|
for (i = 0, len = history.length; i < len; i++) {
|
|
103
100
|
item = history[i]
|
|
104
101
|
outstream.write(item + '\n')
|
|
105
102
|
}
|
|
106
|
-
|
|
107
|
-
outstream.end(this.shutdown.bind(this))
|
|
108
103
|
})
|
|
109
104
|
}
|
|
110
105
|
}
|
|
111
106
|
|
|
107
|
+
// Prevent output buffer "swallowing" every other character on OSX / Node version > 16.19.0.
|
|
108
|
+
process.stdout._handle.setBlocking(false)
|
|
112
109
|
exports.use = robot => new Shell(robot)
|
|
113
110
|
|
|
114
111
|
// load history from .hubot_history.
|
package/src/brain.js
CHANGED
|
@@ -10,19 +10,19 @@ const User = require('./user')
|
|
|
10
10
|
// 2. If the original object was a User object, the original object
|
|
11
11
|
// 3. If the original object was a plain JavaScript object, return
|
|
12
12
|
// a User object with all of the original object's properties.
|
|
13
|
-
|
|
13
|
+
const reconstructUserIfNecessary = function (user, robot) {
|
|
14
14
|
if (!user) {
|
|
15
15
|
return null
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
if (!user.constructor || (user.constructor && user.constructor.name !== 'User')) {
|
|
19
|
-
|
|
19
|
+
const id = user.id
|
|
20
20
|
delete user.id
|
|
21
21
|
// Use the old user as the "options" object,
|
|
22
22
|
// populating the new user with its values.
|
|
23
23
|
// Also add the `robot` field so it gets a reference.
|
|
24
24
|
user.robot = robot
|
|
25
|
-
|
|
25
|
+
const newUser = new User(id, user)
|
|
26
26
|
delete user.robot
|
|
27
27
|
|
|
28
28
|
return newUser
|
|
@@ -142,14 +142,14 @@ class Brain extends EventEmitter {
|
|
|
142
142
|
//
|
|
143
143
|
// Caveats: Deeply nested structures don't merge well.
|
|
144
144
|
mergeData (data) {
|
|
145
|
-
for (
|
|
145
|
+
for (const k in data || {}) {
|
|
146
146
|
this.data[k] = data[k]
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
// Ensure users in the brain are still User objects.
|
|
150
150
|
if (data && data.users) {
|
|
151
|
-
for (
|
|
152
|
-
|
|
151
|
+
for (const k in data.users) {
|
|
152
|
+
const user = this.data.users[k]
|
|
153
153
|
this.data.users[k] = reconstructUserIfNecessary(user, this.getRobot())
|
|
154
154
|
}
|
|
155
155
|
}
|
|
@@ -195,8 +195,8 @@ class Brain extends EventEmitter {
|
|
|
195
195
|
let result = null
|
|
196
196
|
const lowerName = name.toLowerCase()
|
|
197
197
|
|
|
198
|
-
for (
|
|
199
|
-
const userName = this.data.users[k]
|
|
198
|
+
for (const k in this.data.users || {}) {
|
|
199
|
+
const userName = this.data.users[k].name
|
|
200
200
|
if (userName != null && userName.toString().toLowerCase() === lowerName) {
|
|
201
201
|
result = this.data.users[k]
|
|
202
202
|
}
|
package/src/datastore.js
CHANGED
|
@@ -22,7 +22,7 @@ class DataStore {
|
|
|
22
22
|
// present, it's instantiated as an empty object.
|
|
23
23
|
setObject (key, objectKey, value) {
|
|
24
24
|
return this.get(key).then((object) => {
|
|
25
|
-
|
|
25
|
+
const target = object || {}
|
|
26
26
|
target[objectKey] = value
|
|
27
27
|
return this.set(key, target)
|
|
28
28
|
})
|
|
@@ -33,7 +33,7 @@ class DataStore {
|
|
|
33
33
|
// present, it's instantiated as an empty array.
|
|
34
34
|
setArray (key, value) {
|
|
35
35
|
return this.get(key).then((object) => {
|
|
36
|
-
|
|
36
|
+
const target = object || []
|
|
37
37
|
// Extend the array if the value is also an array, otherwise
|
|
38
38
|
// push the single value on the end.
|
|
39
39
|
if (Array.isArray(value)) {
|
|
@@ -56,7 +56,7 @@ class DataStore {
|
|
|
56
56
|
// contain an `objectKey`, returns `undefined`.
|
|
57
57
|
getObject (key, objectKey) {
|
|
58
58
|
return this.get(key).then((object) => {
|
|
59
|
-
|
|
59
|
+
const target = object || {}
|
|
60
60
|
return target[objectKey]
|
|
61
61
|
})
|
|
62
62
|
}
|
|
@@ -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)
|