From 71a8b1717a108d7b175b4aa7327d46885d771f0c Mon Sep 17 00:00:00 2001 From: Abel Toledano Date: Sun, 13 Nov 2016 23:16:38 +0100 Subject: [PATCH] bots, plugins, disconnect --- bot-api.js | 85 ++++++++ bots/echo-bot.js | 10 + bots/todo-bot.js | 53 +++++ chat-client.js | 110 ++++++++++ index.js | 62 ++++-- package.json | 7 +- plugins/metadata-plugin.js | 43 ++++ yarn.lock | 408 ++++++++++++++++++++++++++++++++++++- 8 files changed, 759 insertions(+), 19 deletions(-) create mode 100644 bot-api.js create mode 100644 bots/echo-bot.js create mode 100644 bots/todo-bot.js create mode 100644 chat-client.js create mode 100644 plugins/metadata-plugin.js diff --git a/bot-api.js b/bot-api.js new file mode 100644 index 0000000..e9dc708 --- /dev/null +++ b/bot-api.js @@ -0,0 +1,85 @@ +const createClient = require('./chat-client'); +const md5 = require('md5'); +const {v4: createId} = require('uuid'); + +const getAvatar = (email) => + `http://www.gravatar.com/avatar/${md5(email)}?s=48&d=identicon&r=PG`; + +const createBotUser = (name) => { + const id = createId(); + const email = `bot_${id}@chatbot.com`; + const avatar = getAvatar(email); + return { + name, + id, + email, + avatar, + fullName: name, + familyName: name, + }; +}; + +const createBot = (name) => { + const botUser = createBotUser(name); + const client = createClient('BOT/' + encodeURIComponent(JSON.stringify(botUser))); + + const sendMessage = (...args) => + setTimeout(() => client.sendMessage(...args), 200); + + const respondWith = (getResponse) => (action, ...rest) => { + const text = typeof getResponse === 'function' + ? getResponse(action, ...rest) + : getResponse; + const {receiver, sender} = action; + sendMessage(text, receiver === botUser.id ? sender : receiver); + }; + + const whenOneToOne = (fn) => (action, ...rest) => { + if (action.receiver === botUser.id) { + fn(action, ...rest); + } + }; + + return { + data: botUser, + init: client.init, + respondWith, + whenOneToOne, + matches, + sendMessage, + on: client.on, + }; +}; + +const escapeRegExp = (string) => + string.replace(/([.*+?^${}()|\[\]\/\\])/g, "\\$1"); + +const textMatchesSelector = (text, selector) => { + const matches = text.startsWith(selector); + const parsedText = matches + ? text.replace(new RegExp(escapeRegExp(selector) + '\s*'), '') + : null; + return [matches, parsedText]; +}; + +const matches = (selectors) => (action) => { + const handled = Object.keys(selectors) + .filter(selector => selector !== 'default') + .some(selector => { + const {text} = action.payload; + const [matches, parsedText] = textMatchesSelector(text, selector); + if (matches) { + selectors[selector](action, parsedText); + } + return matches; + }); + + if (!handled && 'default' in selectors) { + selectors['default'](action); + } +}; + +module.exports = { + createBot, + matches, +}; \ No newline at end of file diff --git a/bots/echo-bot.js b/bots/echo-bot.js new file mode 100644 index 0000000..4ac4f57 --- /dev/null +++ b/bots/echo-bot.js @@ -0,0 +1,10 @@ +const {createBot} = require('../bot-api'); + +const createEchoBot = (name) => { + const bot = createBot(name); + const {whenOneToOne, respondWith} = bot; + bot.on('message', whenOneToOne(respondWith(({payload}) => payload.text))); + return bot; +}; + +module.exports = createEchoBot; \ No newline at end of file diff --git a/bots/todo-bot.js b/bots/todo-bot.js new file mode 100644 index 0000000..152f7a4 --- /dev/null +++ b/bots/todo-bot.js @@ -0,0 +1,53 @@ +const {createBot, matches} = require('../bot-api'); + +const createTodoBot = (name) => { + const todos = {}; + const bot = createBot(name); + const {whenOneToOne, respondWith} = bot; + + const printTodos = todosForUser => + todosForUser.map((todo, idx) => `${idx + 1}. ${todo}`).join(',\n'); + + bot.on('message', matches({ + + '/todos': respondWith(({sender}) => { + const todosForUser = todos[sender]; + if (todosForUser) { + return 'These are your todos: \n' + printTodos(todosForUser); + } else { + return "You don't have pending todos!"; + } + }), + + '/todo': respondWith(({sender}, newTodo) => { + const todosForUser = todos[sender] || []; + todos[sender] = [...todosForUser, newTodo] + return `New todo saved: "${newTodo}" \nPending: \n${printTodos(todos[sender])}`; + }), + + '/done': respondWith(({sender}, idx) => { + const doneTodoIdx = parseInt(idx) - 1; + const todosForUser = todos[sender] || []; + const doneTodo = todosForUser[doneTodoIdx]; + const pendingTodos = todosForUser.filter((todo, idx) => idx !== doneTodoIdx); + todos[sender] = pendingTodos; + return doneTodo + ? `"${doneTodo}" marked as done \nPending: \n${printTodos(pendingTodos)}` + : todosForUser.length + ? `You only have ${todosForUser.length} todos` + : "You don't have pending todos!"; + }), + + '/help': respondWith(() => [ + '/todos lists the todos', + '/todo adds a new todo', + '/done marks a todo as done' + ].join(',\n')), + + default: whenOneToOne(respondWith('Type /help')), + })); + + return bot; +}; + +module.exports = createTodoBot; \ No newline at end of file diff --git a/chat-client.js b/chat-client.js new file mode 100644 index 0000000..429de25 --- /dev/null +++ b/chat-client.js @@ -0,0 +1,110 @@ +const WebSocket = require('ws'); + +const CONNECTING = 0; +const OPEN = 1; + +const dev = true; +const PRODUCTION_CHAT_SERVER = 'wss://react-chat-server.herokuapp.com'; +const DEV_CHAT_SERVER = 'ws://localhost:8080'; +const chatServerUrl = dev ? DEV_CHAT_SERVER : PRODUCTION_CHAT_SERVER; + +const createClient = (token) => { + let ws; + const listeners = {}; + + const handleMessage = m => { + const action = JSON.parse(m.data); + (listeners[action.type] || []).forEach(l => l(action)); + }; + + return { + /** + * Inits the chat connection with the server + * @param {string} token the google auth session token + */ + init() { + if (ws) { + throw new Error('Chat client already initialized'); + } + ws = new WebSocket(`${chatServerUrl}/${token}`); + ws.addEventListener('message', handleMessage); + }, + + /** + * Fetch the users list from server. + * @return {Promise} resolves to an array of users: + * { + * fullName: string, + * avatar: string, + * name: string, + * familyName: string, + * email: string, + * id: string + * } + */ + getUsers() { + this.send({type: 'getUsers', receiver: 'server'}); + }, + + /** + * Sends a chat message to a recipient + * @param {string} messageText + * @param {string} receiver userId of the receiver. When not provided, + * the message is sent to all the users + */ + sendMessage(messageText, receiver = 'all') { + this.send({type: 'message', receiver, payload: {text: messageText}}); + }, + + /** + * Sends an action to the chat server + * @param {object} action an action has the following form: + * { + * type: string, // for example 'message' + * receiver: string, // the userId of the recipient or 'all' + * [sender]: string, // not needed, the server knows you + * [time]: number, // automatically set to current timestamp. + * payload: object, // any data associated with the event + * } + * @return {[type]} [description] + */ + send(action) { + action.time = action.time || Date.now(); + if (ws.readyState === CONNECTING) { + setTimeout(() => this.send(action), 100); + } else if (ws.readyState === OPEN) { + ws.send(JSON.stringify(action)); + } + }, + + /** + * Registers a listener for a given event type + * @param {string} event + * @param {Function} listener will be called with every + * chat event of the specified type received from the sever + * @return {Function} dettach function to remove the event + * listener. + */ + on(event, listener) { + if (event in listeners) { + listeners[event].push(listener); + } else { + listeners[event] = [listener]; + } + return () => this.off(event, listener); + }, + + /** + * Dettached a previously registered listener + * @param {string} event the event type + * @param {Function} listenerToRemove + */ + off(event, listenerToRemove) { + if (event in listeners) { + listeners[event] = listeners[event].filter(l => l !== listenerToRemove); + } + }, + }; +}; + +module.exports = createClient; \ No newline at end of file diff --git a/index.js b/index.js index df60078..971c2b6 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,6 @@ -const url = require('url'); const WebSocketServer = require('ws').Server; const createGoogleTokenVerifier = require('./google-auth/google-id-token-verifier'); + const GOOGLE_CLIENT_ID = process.env.GOOGLE_CLIENT_ID || require('./config').GOOGLE_CLIENT_ID; const SERVER_PORT = process.env.PORT || 8080; @@ -15,27 +15,54 @@ const createUser = ({name, picture, given_name, email, family_name, sub}) => ({ id: sub }); -const users = []; +const getUsersList = () => + wss.clients.map(({upgradeReq: {user}}) => user); + +const isUserAlreadyLoggedIn = user => + getUsersList().some(({id}) => id === user.id); const wss = new WebSocketServer({ port: SERVER_PORT, verifyClient(info, cb) { const token = info.req.url.substr(1); - verifyAuthToken(token).then(tokenInfo => { - info.req.user = createUser(tokenInfo); - users.push(info.req.user); - cb(true); - }).catch(e => { - cb(false, 401, `Unauthorized: ${e.message}`); - }); + if (token.startsWith('BOT/')) { + const userJson = decodeURIComponent(token.substr('BOT/'.length)); + console.log('register new bot: ', userJson); + try { + const user = JSON.parse(userJson) + info.req.user = user; + cb(true); + } catch (e) { + cb(false, 401, 'Bad bot encoding'); + } + } else { + verifyAuthToken(token).then(tokenInfo => { + const user = createUser(tokenInfo); + if (isUserAlreadyLoggedIn(user)) { + cb(false, 401, `Unauthorized: already logged!`); + } else { + info.req.user = user; + } + cb(true); + }).catch(e => { + cb(false, 401, `Unauthorized: ${e.message}`); + }); + } }, }); +const plugins = [ + require('./plugins/metadata-plugin'), +]; + +const applyPlugins = event => + plugins.reduce((event, plugin) => Promise.resolve(plugin(event)), event); + const send = event => { const {receiver, sender} = event; const receivers = receiver === 'all' - ? wss.clients.filter(({upgradeReq: {user}}) => user.id !== sender) - : wss.clients.filter(({upgradeReq: {user}}) => user.id === receiver); + ? wss.clients + : wss.clients.filter(({upgradeReq: {user}}) => user.id === receiver || user.id === sender); receivers.forEach(ws => { console.log(`> ${ws.upgradeReq.user.email}`, event); @@ -45,7 +72,7 @@ const send = event => { const handleInServer = event => { if (event.type === 'getUsers') { - users.forEach(user => { + getUsersList().forEach(user => { send({type: 'user', sender: 'server', receiver: event.sender, payload: user}); }); } @@ -58,14 +85,21 @@ const handleEvent = fromUser => eventJson => { if (event.receiver === 'server') { handleInServer(event); } else { - send(event); + applyPlugins(event).then(send); } }; +const notyfyDisconnection = user => () => { + console.log(`Offline: ${user.email} | #users: ${wss.clients.length}`); + send({type: 'disconnect', sender: 'server', receiver: 'all', payload: user.id}); +} + wss.on('connection', ws => { const {user} = ws.upgradeReq; - console.log(`New user: ${user.name} (${user.email}), num users: ${wss.clients.length}`); + console.log(`Connect: ${user.email} | #users: ${wss.clients.length}`); + ws.on('message', handleEvent(user)); + ws.on('close', notyfyDisconnection(user)) send({type: 'user', sender: user.id, receiver: 'all', payload: user}); }); diff --git a/package.json b/package.json index 10deb11..e8b200c 100644 --- a/package.json +++ b/package.json @@ -8,10 +8,15 @@ "node": ">=6.0" }, "scripts": { - "start": "pm2 start index.js --watch" + "start": "pm2 start index.js --watch", + "log": "pm2 log index" }, "dependencies": { + "get-urls": "^5.0.1", + "md5": "^2.2.1", + "metaphor": "^3.8.1", "node-fetch": "^1.6.3", + "uuid": "^2.0.3", "ws": "^1.1.1" }, "devDependencies": { diff --git a/plugins/metadata-plugin.js b/plugins/metadata-plugin.js new file mode 100644 index 0000000..79dfd9c --- /dev/null +++ b/plugins/metadata-plugin.js @@ -0,0 +1,43 @@ +const getUrls = require('get-urls'); +const metaphor = require('metaphor'); + +const merge = (...objs) => Object.assign({}, ...objs); + +const metadataEngine = new metaphor.Engine(); + +const fetchMetadata = url => + new Promise((resolve, reject) => { + metadataEngine.describe(url, data => { + if (data) { + resolve({ + url: data.url || url, + title: data.title, + description: data.description, + image: data.image, + embed: data.embed, + }); + } else { + reject(); + } + }); + }); + +module.exports = fetchMetadata; + +const addMetadata = (event) => { + if (event.type !== 'message') { + return Promise.resolve(event); + } + + const urls = getUrls(event.payload.text); + + if (urls.length === 0) { + return Promise.resolve(event); + } + + return fetchMetadata(urls[0]) + .then((media) => merge(event, {payload: merge(event.payload, {media})})) + .catch(() => event); +}; + +module.exports = addMetadata; \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index f20ad16..2694a51 100644 --- a/yarn.lock +++ b/yarn.lock @@ -56,6 +56,14 @@ arr-flatten@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.0.1.tgz#e5ffe54d45e19f32f216e91eb99c8ce892bb604b" +array-find-index@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" + +array-uniq@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" + array-unique@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53" @@ -122,6 +130,18 @@ boom@2.x.x: dependencies: hoek "2.x.x" +boom@3.x.x: + version "3.2.2" + resolved "https://registry.yarnpkg.com/boom/-/boom-3.2.2.tgz#0f0cc5d04adc5003b8c7d71f42cca7271fef0e78" + dependencies: + hoek "4.x.x" + +boom@4.x.x: + version "4.2.0" + resolved "https://registry.yarnpkg.com/boom/-/boom-4.2.0.tgz#c1a74174b11fbba223f6162d4fd8851a1b82a536" + dependencies: + hoek "4.x.x" + brace-expansion@^1.0.0: version "1.1.6" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.6.tgz#7197d7eaa9b87e648390ea61fc66c84427420df9" @@ -141,6 +161,21 @@ buffer-shims@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/buffer-shims/-/buffer-shims-1.0.0.tgz#9978ce317388c649ad8793028c3477ef044a8b51" +builtin-modules@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" + +camelcase-keys@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7" + dependencies: + camelcase "^2.0.0" + map-obj "^1.0.0" + +camelcase@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" + caseless@~0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.11.0.tgz#715b96ea9841593cc33067923f5ec60ebda4f7d7" @@ -155,6 +190,10 @@ chalk@^1.1, chalk@^1.1.1: strip-ansi "^3.0.0" supports-color "^2.0.0" +charenc@~0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.1.tgz#004cff9feaf102382ed12db58dd6f962796d6e88" + charm@~0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/charm/-/charm-0.1.2.tgz#06c21eed1a1b06aeb67553cdc53e23274bac2296" @@ -213,6 +252,12 @@ console-control-strings@^1.0.0, console-control-strings@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" +content@3.x.x: + version "3.0.3" + resolved "https://registry.yarnpkg.com/content/-/content-3.0.3.tgz#000f8a01371b95c66afe99be9390fa6cb91aa87a" + dependencies: + boom "4.x.x" + core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -223,12 +268,22 @@ cron@1.1.0: dependencies: moment-timezone "~0.3.0" +crypt@~0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.1.tgz#5f11b21a6c05ef1b5e79708366da6374ece1e6a2" + cryptiles@2.x.x: version "2.0.5" resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" dependencies: boom "2.x.x" +currently-unhandled@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" + dependencies: + array-find-index "^1.0.1" + dashdash@^1.12.0: version "1.14.0" resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.0.tgz#29e486c5418bf0f356034a993d51686a33e84141" @@ -241,6 +296,10 @@ debug@*, debug@^2.2, debug@~2.2.0, debug@2.2.0: dependencies: ms "0.7.1" +decamelize@^1.1.2: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + deep-extend@~0.4.0: version "0.4.1" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.1.tgz#efe4113d08085f4e6f9687759810f807469e2253" @@ -253,6 +312,34 @@ delegates@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" +dom-serializer@0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82" + dependencies: + domelementtype "~1.1.1" + entities "~1.1.1" + +domelementtype@^1.3.0, domelementtype@1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.0.tgz#b17aed82e8ab59e52dd9c19b1756e0fc187204c2" + +domelementtype@~1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b" + +domhandler@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.3.0.tgz#2de59a0822d5027fabff6f032c2b25a2a8abe738" + dependencies: + domelementtype "1" + +domutils@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" + dependencies: + dom-serializer "0" + domelementtype "1" + ecc-jsbn@~0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505" @@ -265,6 +352,16 @@ encoding@^0.1.11: dependencies: iconv-lite "~0.4.13" +entities@^1.1.1, entities@~1.1.1, entities@1.x.x: + version "1.1.1" + resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" + +error-ex@^1.2.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.0.tgz#e67b43f3e82c96ea3a584ffee0b9fc3325d802d9" + dependencies: + is-arrayish "^0.2.1" + escape-regexp@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/escape-regexp/-/escape-regexp-0.0.1.tgz#f44bda12d45bbdf9cb7f862ee7e4827b3dd32254" @@ -329,6 +426,13 @@ fill-range@^2.1.0: repeat-element "^1.1.2" repeat-string "^1.5.2" +find-up@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" + dependencies: + path-exists "^2.0.0" + pinkie-promise "^2.0.0" + for-in@^0.1.5: version "0.1.6" resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.6.tgz#c9f96e89bfad18a545af5ec3ed352a1d9e5b4dc8" @@ -403,6 +507,20 @@ generate-object-property@^1.1.0: dependencies: is-property "^1.0.0" +get-stdin@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" + +get-urls: + version "5.0.1" + resolved "https://registry.yarnpkg.com/get-urls/-/get-urls-5.0.1.tgz#0f993e25884da4e59bcf1d3780dd91469693d3f5" + dependencies: + array-uniq "^1.0.0" + get-stdin "^4.0.1" + meow "^3.3.0" + normalize-url "^1.3.0" + url-regex "^3.2.0" + getpass@^0.1.1: version "0.1.6" resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.6.tgz#283ffd9fc1256840875311c1b60e8c40187110e6" @@ -481,6 +599,25 @@ hoek@2.x.x: version "2.16.3" resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" +hoek@4.x.x: + version "4.1.0" + resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.1.0.tgz#4a4557460f69842ed463aa00628cc26d2683afa7" + +hosted-git-info@^2.1.4: + version "2.1.5" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.1.5.tgz#0ba81d90da2e25ab34a332e6ec77936e1598118b" + +htmlparser2@3.x.x: + version "3.9.2" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.9.2.tgz#1bdf87acca0f3f9e53fa4fcceb0f4b4cbb00b338" + dependencies: + domelementtype "^1.3.0" + domhandler "^2.3.0" + domutils "^1.5.1" + entities "^1.1.1" + inherits "^2.0.1" + readable-stream "^2.0.2" + http-signature@~1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" @@ -493,6 +630,12 @@ iconv-lite@~0.4.13: version "0.4.13" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.13.tgz#1f88aba4ab0b1508e8312acc39345f36e992e2f2" +indent-string@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" + dependencies: + repeating "^2.0.0" + inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -512,16 +655,30 @@ interpret@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.1.tgz#d579fb7f693b858004947af39fa0db49f795602c" +ip-regex@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-1.0.3.tgz#dc589076f659f419c222039a33316f1c7387effd" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + is-binary-path@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" dependencies: binary-extensions "^1.0.0" -is-buffer@^1.0.2: +is-buffer@^1.0.2, is-buffer@~1.1.1: version "1.1.4" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.4.tgz#cfc86ccd5dc5a52fa80489111c6920c457e2d98b" +is-builtin-module@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe" + dependencies: + builtin-modules "^1.0.0" + is-dotfile@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.2.tgz#2c132383f39199f8edc268ca01b9b007d205cc4d" @@ -540,6 +697,12 @@ is-extglob@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0" +is-finite@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa" + dependencies: + number-is-nan "^1.0.0" + is-fullwidth-code-point@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" @@ -567,6 +730,10 @@ is-number@^2.0.2, is-number@^2.1.0: dependencies: kind-of "^3.0.2" +is-plain-obj@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" + is-posix-bracket@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4" @@ -587,10 +754,18 @@ is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" +is-utf8@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" + isarray@~1.0.0, isarray@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" +isemail@2.x.x: + version "2.2.1" + resolved "https://registry.yarnpkg.com/isemail/-/isemail-2.2.1.tgz#0353d3d9a62951080c262c2aa0a42b8ea8e9e2a6" + isobject@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" @@ -601,12 +776,26 @@ isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" +items@2.x.x: + version "2.1.1" + resolved "https://registry.yarnpkg.com/items/-/items-2.1.1.tgz#8bd16d9c83b19529de5aea321acaada78364a198" + jodid25519@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/jodid25519/-/jodid25519-1.0.2.tgz#06d4912255093419477d425633606e0e90782967" dependencies: jsbn "~0.1.0" +joi@9.x.x: + version "9.2.0" + resolved "https://registry.yarnpkg.com/joi/-/joi-9.2.0.tgz#3385ac790192130cbe230e802ec02c9215bbfeda" + dependencies: + hoek "4.x.x" + isemail "2.x.x" + items "2.x.x" + moment "2.x.x" + topo "2.x.x" + jsbn@~0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.0.tgz#650987da0dd74f4ebf5a11377a2aa2d273e97dfd" @@ -641,10 +830,66 @@ lazy@~1.0.11: version "1.0.11" resolved "https://registry.yarnpkg.com/lazy/-/lazy-1.0.11.tgz#daa068206282542c088288e975c297c1ae77b690" +load-json-file@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + pinkie-promise "^2.0.0" + strip-bom "^2.0.0" + lodash@^3.10.1: version "3.10.1" resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" +loud-rejection@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" + dependencies: + currently-unhandled "^0.4.1" + signal-exit "^3.0.0" + +map-obj@^1.0.0, map-obj@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" + +md5: + version "2.2.1" + resolved "https://registry.yarnpkg.com/md5/-/md5-2.2.1.tgz#53ab38d5fe3c8891ba465329ea23fac0540126f9" + dependencies: + charenc "~0.0.1" + crypt "~0.0.1" + is-buffer "~1.1.1" + +meow@^3.3.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" + dependencies: + camelcase-keys "^2.0.0" + decamelize "^1.1.2" + loud-rejection "^1.0.0" + map-obj "^1.0.1" + minimist "^1.1.3" + normalize-package-data "^2.3.4" + object-assign "^4.0.1" + read-pkg-up "^1.0.1" + redent "^1.0.0" + trim-newlines "^1.0.0" + +metaphor: + version "3.8.1" + resolved "https://registry.yarnpkg.com/metaphor/-/metaphor-3.8.1.tgz#2cd09a6b6682fa37dbc6ea43f1b0d9bd8803bf79" + dependencies: + content "3.x.x" + entities "1.x.x" + hoek "4.x.x" + htmlparser2 "3.x.x" + items "2.x.x" + joi "9.x.x" + wreck "9.x.x" + micromatch@^2.1.5: version "2.3.11" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565" @@ -679,7 +924,7 @@ minimatch@^3.0.0, minimatch@^3.0.2: dependencies: brace-expansion "^1.0.0" -minimist@^1.2.0: +minimist@^1.1.3, minimist@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" @@ -703,6 +948,10 @@ moment@^2.14, "moment@>= 2.6.0": version "2.15.2" resolved "https://registry.yarnpkg.com/moment/-/moment-2.15.2.tgz#1bfdedf6a6e345f322fe956d5df5bd08a8ce84dc" +moment@2.x.x: + version "2.16.0" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.16.0.tgz#f38f2c97c9889b0ee18fc6cc392e1e443ad2da8e" + ms@0.7.1: version "0.7.1" resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098" @@ -742,10 +991,28 @@ nopt@~3.0.6: dependencies: abbrev "1" +normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: + version "2.3.5" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.3.5.tgz#8d924f142960e1777e7ffe170543631cc7cb02df" + dependencies: + hosted-git-info "^2.1.4" + is-builtin-module "^1.0.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + normalize-path@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.0.1.tgz#47886ac1662760d4261b7d979d241709d3ce3f7a" +normalize-url@^1.3.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-1.8.0.tgz#a9550b079aa3523c85d78df24eef1959fce359ab" + dependencies: + object-assign "^4.0.1" + prepend-http "^1.0.0" + query-string "^4.1.0" + sort-keys "^1.0.0" + npmlog@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.0.0.tgz#e094503961c70c1774eb76692080e8d578a9f88f" @@ -770,7 +1037,7 @@ oauth-sign@~0.8.1: version "0.8.2" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" -object-assign@^4.1.0: +object-assign@^4.0.1, object-assign@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.0.tgz#7a3b3d0e98063d43f4c03f2e8ae6cd51a86883a0" @@ -806,14 +1073,38 @@ parse-glob@^3.0.4: is-extglob "^1.0.0" is-glob "^2.0.0" +parse-json@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" + dependencies: + error-ex "^1.2.0" + +path-exists@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" + dependencies: + pinkie-promise "^2.0.0" + path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" +path-type@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" + dependencies: + graceful-fs "^4.1.2" + pify "^2.0.0" + pinkie-promise "^2.0.0" + pidusage@^1.0.8: version "1.1.0" resolved "https://registry.yarnpkg.com/pidusage/-/pidusage-1.1.0.tgz#ffd58339a97aec470cff7cfe1a9853ec5cd415f8" +pify@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + pinkie-promise@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" @@ -890,6 +1181,10 @@ pmx@^0.6: debug "^2.2" json-stringify-safe "^5.0" +prepend-http@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" + preserve@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" @@ -906,6 +1201,13 @@ qs@~6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.0.tgz#f403b264f23bc01228c74131b407f18d5ea5d442" +query-string@^4.1.0: + version "4.2.3" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.2.3.tgz#9f27273d207a25a8ee4c7b8c74dcd45d556db822" + dependencies: + object-assign "^4.1.0" + strict-uri-encode "^1.0.0" + randomatic@^1.1.3: version "1.1.5" resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.5.tgz#5e9ef5f2d573c67bd2b8124ae90b5156e457840b" @@ -922,6 +1224,21 @@ rc@~1.1.6: minimist "^1.2.0" strip-json-comments "~1.0.4" +read-pkg-up@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" + dependencies: + find-up "^1.0.0" + read-pkg "^1.0.0" + +read-pkg@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28" + dependencies: + load-json-file "^1.0.0" + normalize-package-data "^2.3.2" + path-type "^1.0.0" + "readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.0.2, readable-stream@~2.1.4: version "2.1.5" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.1.5.tgz#66fa8b720e1438b364681f2ad1a63c618448c9d0" @@ -949,6 +1266,13 @@ rechoir@^0.6.2: dependencies: resolve "^1.1.6" +redent@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" + dependencies: + indent-string "^2.1.0" + strip-indent "^1.0.1" + regex-cache@^0.4.2: version "0.4.3" resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.3.tgz#9b1a6c35d4d0dfcef5711ae651e8e9d3d7114145" @@ -964,6 +1288,12 @@ repeat-string@^1.5.2: version "1.6.1" resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" +repeating@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" + dependencies: + is-finite "^1.0.0" + request@^2.75.0: version "2.76.0" resolved "https://registry.yarnpkg.com/request/-/request-2.76.0.tgz#be44505afef70360a0436955106be3945d95560e" @@ -999,7 +1329,7 @@ rimraf@~2.5.1, rimraf@~2.5.4, rimraf@2: dependencies: glob "^7.0.5" -semver@^5.2, semver@~5.3.0: +semver@^5.2, semver@~5.3.0, "semver@2 || 3 || 4 || 5": version "5.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" @@ -1029,6 +1359,12 @@ sntp@1.x.x: dependencies: hoek "2.x.x" +sort-keys@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad" + dependencies: + is-plain-obj "^1.0.0" + source-map-support@^0.4: version "0.4.6" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.6.tgz#32552aa64b458392a85eab3b0b5ee61527167aeb" @@ -1039,6 +1375,20 @@ source-map@^0.5.3: version "0.5.6" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" +spdx-correct@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40" + dependencies: + spdx-license-ids "^1.0.2" + +spdx-expression-parse@~1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz#9bdf2f20e1f40ed447fbe273266191fced51626c" + +spdx-license-ids@^1.0.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz#c9df7a3424594ade6bd11900d596696dc06bac57" + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -1058,6 +1408,10 @@ sshpk@^1.7.0: jsbn "~0.1.0" tweetnacl "~0.14.0" +strict-uri-encode@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" + string_decoder@~0.10.x: version "0.10.31" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" @@ -1080,6 +1434,18 @@ strip-ansi@^3.0.0, strip-ansi@^3.0.1: dependencies: ansi-regex "^2.0.0" +strip-bom@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" + dependencies: + is-utf8 "^0.2.0" + +strip-indent@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2" + dependencies: + get-stdin "^4.0.1" + strip-json-comments@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91" @@ -1109,12 +1475,22 @@ tar@~2.2.1: fstream "^1.0.2" inherits "2" +topo@2.x.x: + version "2.0.2" + resolved "https://registry.yarnpkg.com/topo/-/topo-2.0.2.tgz#cd5615752539057c0dc0491a621c3bc6fbe1d182" + dependencies: + hoek "4.x.x" + tough-cookie@~2.3.0: version "2.3.2" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.2.tgz#f081f76e4c85720e6c37a5faced737150d84072a" dependencies: punycode "^1.4.1" +trim-newlines@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" + tunnel-agent@~0.4.1: version "0.4.3" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb" @@ -1135,10 +1511,27 @@ ultron@1.0.x: version "1.0.2" resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.0.2.tgz#ace116ab557cd197386a4e88f4685378c8b2e4fa" +url-regex@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/url-regex/-/url-regex-3.2.0.tgz#dbad1e0c9e29e105dd0b1f09f6862f7fdb482724" + dependencies: + ip-regex "^1.0.1" + util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" +uuid: + version "2.0.3" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a" + +validate-npm-package-license@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz#2804babe712ad3379459acfbe24746ab2c303fbc" + dependencies: + spdx-correct "~1.0.0" + spdx-expression-parse "~1.0.0" + verror@1.3.6: version "1.3.6" resolved "https://registry.yarnpkg.com/verror/-/verror-1.3.6.tgz#cff5df12946d297d2baaefaa2689e25be01c005c" @@ -1161,6 +1554,13 @@ wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" +wreck@9.x.x: + version "9.0.0" + resolved "https://registry.yarnpkg.com/wreck/-/wreck-9.0.0.tgz#1de63d49bb07b94fe718864b8be63176e63331ec" + dependencies: + boom "3.x.x" + hoek "4.x.x" + ws: version "1.1.1" resolved "https://registry.yarnpkg.com/ws/-/ws-1.1.1.tgz#082ddb6c641e85d4bb451f03d52f06eabdb1f018"