├── .gitignore ├── Dockerfile ├── LICENCE ├── Procfile ├── README.md ├── app.json ├── attachments ├── eth.png └── ethereum.jpg ├── bin └── console.sh ├── build.gradle ├── config ├── development.yml ├── docker.yml ├── production.yml └── staging.yml ├── currency.json ├── dependencies ├── headless-client │ └── Dockerfile └── redis │ ├── Dockerfile │ └── redis.conf ├── docker-compose.yml.sample ├── docs └── images │ └── app-architecture.png ├── examples ├── simple_bot.js └── testing_bot.js ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── package.json ├── src ├── bot.js └── lib │ ├── Bot.js │ ├── Client.js │ ├── Config.js │ ├── Console.js │ ├── FeedPoller.js │ ├── Fiat.js │ ├── Logger.js │ ├── Session.js │ ├── Thread.js │ └── constants.js ├── system.properties ├── yarn-error.log └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | state 3 | node_modules 4 | /build 5 | docker-compose.yml 6 | .gradle 7 | .idea 8 | *.swo 9 | *.swp 10 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # latest official node image 2 | FROM node:latest 3 | 4 | RUN git config --global user.email 'docker-dummy@example.com' 5 | RUN npm install -g nodemon 6 | 7 | # use cached layer for node modules 8 | ADD package.json /tmp/package.json 9 | RUN cd /tmp && npm install --unsafe-perm 10 | RUN mkdir -p /usr/src/bot && cp -a /tmp/node_modules /usr/src/bot/ 11 | 12 | # add project files 13 | ADD src /usr/src/bot/src 14 | ADD package.json /usr/src/bot/package.json 15 | WORKDIR /usr/src/bot 16 | 17 | CMD nodemon -L src/bot.js config.yml -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Token Browser, Inc 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the “Software”), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | headless-client: java $JAVA_OPTS -jar build/libs/token-headless-client-0.1.0-capsule.jar config/${STAGE:-production}.yml 2 | bot: node src/bot.js config/${STAGE:-production}.yml 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Toshi app for "Week In Ethereum Blog" 2 | 3 | http://www.weekinethereum.com/ 4 | 5 | * Lets you subscribe to get notified of new blog posts 6 | * Can donate 7 | 8 | This is a simple fork of https://github.com/toshiapp/toshi-app-js which was created during the Coinbase Hackathon April 2017. 9 | 10 | This app can be used as an example to turn any RSS feed into a Toshi app, and collect donations for it. 11 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Token Sofa App", 3 | "description": "Foundation for building a SOFA app for Token (Chat Bot / Web View)", 4 | "repository": "https://github.com/tokenbrowser/token-sofa-app", 5 | "env": { 6 | "TOKEN_APP_USERNAME": { 7 | "description": "Choose a username for your app, no spaces", 8 | "required": false 9 | }, 10 | "TOKEN_APP_ID": { 11 | "description": "Ethereum address that identifies your app" 12 | }, 13 | "TOKEN_APP_PAYMENT_ADDRESS": { 14 | "description": "Ethereum address that is used for ETH transactions" 15 | }, 16 | "TOKEN_APP_SEED": { 17 | "description": "12 word secret backup phrase" 18 | } 19 | }, 20 | "formation": { 21 | "headless-client": { 22 | "quantity": 1, 23 | "size": "free" 24 | }, 25 | "bot": { 26 | "quantity": 1, 27 | "size": "free" 28 | } 29 | }, 30 | "addons": [ 31 | { 32 | "plan": "heroku-redis:hobby-dev" 33 | },{ 34 | "plan": "heroku-postgresql:hobby-dev" 35 | } 36 | ], 37 | "buildpacks": [ 38 | { 39 | "url": "heroku/nodejs" 40 | },{ 41 | "url": "heroku/gradle" 42 | } 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /attachments/eth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barmstrong/week-in-ethereum/f4d1fc53087caa2beaeea8584fb46a79a9fee527/attachments/eth.png -------------------------------------------------------------------------------- /attachments/ethereum.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barmstrong/week-in-ethereum/f4d1fc53087caa2beaeea8584fb46a79a9fee527/attachments/ethereum.jpg -------------------------------------------------------------------------------- /bin/console.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | docker exec -it tokenappjs_bot_1 npm run-script console 3 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "us.kirchmeier.capsule" version "1.0.2" 3 | } 4 | 5 | apply plugin: 'java' 6 | 7 | repositories { 8 | jcenter() 9 | maven { url 'https://jitpack.io' } 10 | } 11 | 12 | task stage(type: FatCapsule) { 13 | applicationClass 'com.bakkenbaeck.token.headless.TokenHeadlessClient' 14 | baseName 'token-headless-client' 15 | version = '0.1.0' 16 | } 17 | 18 | dependencies { 19 | compile 'com.github.tokenbrowser:token-headless-client:v0.1.0' 20 | } 21 | -------------------------------------------------------------------------------- /config/development.yml: -------------------------------------------------------------------------------- 1 | server: https://token-chat-service-development.herokuapp.com 2 | token_ethereum_service_url: https://token-eth-service-development.herokuapp.com 3 | token_id_service_url: https://token-id-service-development.herokuapp.com 4 | store: store 5 | redis: 6 | envKey: REDIS_URL 7 | timeout: 2000 8 | postgres: 9 | envKey: DATABASE_URL -------------------------------------------------------------------------------- /config/docker.yml: -------------------------------------------------------------------------------- 1 | server: https://chat.service.tokenbrowser.com 2 | token_ethereum_service_url: https://ethereum.service.tokenbrowser.com 3 | token_id_service_url: https://identity.service.tokenbrowser.com 4 | store: /state 5 | redis: 6 | envKey: REDIS_URL 7 | timeout: 2000 8 | postgres: 9 | envKey: DATABASE_URL 10 | -------------------------------------------------------------------------------- /config/production.yml: -------------------------------------------------------------------------------- 1 | server: https://chat.service.tokenbrowser.com 2 | token_ethereum_service_url: https://ethereum.service.tokenbrowser.com 3 | token_id_service_url: https://identity.service.tokenbrowser.com 4 | store: store 5 | redis: 6 | envKey: REDIS_URL 7 | timeout: 2000 8 | postgres: 9 | envKey: DATABASE_URL 10 | -------------------------------------------------------------------------------- /config/staging.yml: -------------------------------------------------------------------------------- 1 | server: https://chat.service.tokenbrowser.com 2 | token_ethereum_service_url: https://ethereum.service.tokenbrowser.com 3 | token_id_service_url: https://identity.service.tokenbrowser.com 4 | store: store 5 | redis: 6 | envKey: REDIS_URL 7 | timeout: 2000 8 | postgres: 9 | envKey: DATABASE_URL 10 | -------------------------------------------------------------------------------- /currency.json: -------------------------------------------------------------------------------- 1 | {"data":{"currency":"ETH","rates":{"AED":"180.24","AFN":"3273.97","ALL":"6116.58","AMD":"23745.71","ANG":"87.37","AOA":"8141.28","ARS":"765.38","AUD":"64.50","AWG":"88.33","AZN":"83.66","BAM":"88.44","BBD":"98.14","BDT":"3940.00","BGN":"88.41","BHD":"18.500","BIF":"83021.53","BMD":"49.07","BND":"68.43","BOB":"339.16","BRL":"153.82","BSD":"49.07","BTC":"0.04589000","BTN":"3193.22","BWP":"500.04","BYN":"92.10","BYR":"982688","BZD":"98.65","CAD":"65.71","CDF":"65892.48","CHF":"48.29","CLF":"1.2185","CLP":"32603","CNY":"337.73","COP":"143382.54","CRC":"27277.52","CUC":"49.07","CVE":"4992.87","CZK":"1220.96","DJF":"8782.06","DKK":"336.22","DOP":"2310.78","DZD":"5361.44","EEK":"707.27","EGP":"895.81","ERN":"752.66","ETB":"1117.55","ETH":"1.00000000","EUR":"45.19","FJD":"101.22","FKP":"39.08","GBP":"39.08","GEL":"119.82","GGP":"39.08","GHS":"213.33","GIP":"39.08","GMD":"2217.72","GNF":"460517.04","GTQ":"360.39","GYD":"10210.34","HKD":"381.16","HNL":"1149.95","HRK":"336.05","HTG":"3379.17","HUF":"13966.72","IDR":"653174.46","ILS":"177.16","IMP":"39.08","INR":"3191.87","IQD":"57326.028","ISK":"5392","JEP":"39.08","JMD":"6325.55","JOD":"34.830","JPY":"5412","KES":"5052.25","KGS":"3372.11","KHR":"196056.73","KMF":"22247.53","KRW":"54611","KWD":"14.934","KYD":"40.90","KZT":"15467.11","LAK":"402570.28","LBP":"73941.13","LKR":"7458.15","LRD":"4661.85","LSL":"619.86","LTL":"156.08","LVL":"31.77","LYD":"69.417","MAD":"487.87","MDL":"954.73","MGA":"158290.0","MKD":"2782.02","MMK":"67194.00","MNT":"120281.74","MOP":"392.67","MRO":"17655.4","MTL":"33.55","MUR":"1729.77","MVR":"758.62","MWK":"35618.69","MXN":"926.42","MYR":"216.58","MZN":"3348.05","NAD":"619.86","NGN":"15448.71","NIO":"1451.86","NOK":"417.44","NPR":"5114.07","NZD":"69.84","OMR":"18.892","PAB":"49.07","PEN":"159.60","PGK":"155.80","PHP":"2461.57","PKR":"5144.90","PLN":"191.84","PYG":"277117.92","QAR":"178.69","RON":"205.75","RSD":"5598.56","RUB":"2797.90","RWF":"41187.40","SAR":"184.02","SBD":"381.41","SCR":"668.92","SEK":"430.70","SGD":"68.39","SHP":"39.08","SLL":"366931.04","SOS":"28394.11","SRD":"370.48","SSP":"913.38","STD":"1106966.54","SVC":"429.47","SZL":"620.18","THB":"1687.97","TJS":"402.04","TMT":"171.99","TND":"110.844","TOP":"112.42","TRY":"178.11","TTD":"330.32","TWD":"1482.55","TZS":"109551.23","UAH":"1331.83","UGX":"176939.06","USD":"49.07","UYU":"1379.64","UZS":"174240.21","VEF":"490.21","VND":"1116054","VUV":"5272","WST":"125.28","XAF":"29679.17","XAG":"3","XAU":"0","XCD":"132.61","XDR":"36","XOF":"29721.45","XPF":"5410.09","YER":"12281.32","ZAR":"638.61","ZMK":"257768.40","ZMW":"466.41","ZWL":"15816.38"}}} -------------------------------------------------------------------------------- /dependencies/headless-client/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:8 2 | 3 | WORKDIR /usr/src/headless-client 4 | 5 | RUN git clone https://github.com/tokenbrowser/token-headless-client.git . 6 | RUN git fetch --tags 7 | RUN git checkout tags/v0.1.0 8 | 9 | RUN ./gradlew TokenHeadlessClientCapsule 10 | 11 | CMD ["java", "-jar", "build/libs/token-headless-0.1.0-capsule.jar", "config.yml"] 12 | -------------------------------------------------------------------------------- /dependencies/redis/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM redis 2 | COPY redis.conf /usr/local/etc/redis/redis.conf 3 | CMD [ "redis-server", "/usr/local/etc/redis/redis.conf" ] -------------------------------------------------------------------------------- /dependencies/redis/redis.conf: -------------------------------------------------------------------------------- 1 | port 6379 2 | timeout 0 3 | loglevel notice 4 | databases 16 5 | requirepass rlcMWNrxXgqS0xM8aWCYuE0R 6 | -------------------------------------------------------------------------------- /docker-compose.yml.sample: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | headless-client: 4 | build: dependencies/headless-client 5 | links: 6 | - redis 7 | - postgres 8 | environment: &environment 9 | - TOKEN_APP_ID= 10 | - TOKEN_APP_PAYMENT_ADDRESS= 11 | - TOKEN_APP_USERNAME= 12 | - TOKEN_APP_NAME= 13 | - TOKEN_APP_AVATAR= 14 | - TOKEN_APP_SEED= 15 | - REDIS_URL=redis://h:rlcMWNrxXgqS0xM8aWCYuE0R@redis:6379 16 | - DATABASE_URL=postgres://token:va5uOdJBqu2dZ1@postgres:5432/token 17 | volumes: 18 | - ./config/docker.yml:/usr/src/headless-client/config.yml 19 | - ./attachments:/usr/src/headless-client/attachments 20 | bot: 21 | build: . 22 | links: 23 | - redis 24 | - postgres 25 | environment: *environment 26 | volumes: 27 | - ./config/docker.yml:/usr/src/bot/config.yml 28 | - ./src:/usr/src/bot/src 29 | redis: 30 | build: dependencies/redis 31 | ports: 32 | - 6379 33 | postgres: 34 | image: postgres:9.6 35 | ports: 36 | - 15432 37 | environment: 38 | - POSTGRES_PASSWORD=va5uOdJBqu2dZ1 39 | - POSTGRES_USER=token 40 | - POSTGRES_DB=token 41 | -------------------------------------------------------------------------------- /docs/images/app-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barmstrong/week-in-ethereum/f4d1fc53087caa2beaeea8584fb46a79a9fee527/docs/images/app-architecture.png -------------------------------------------------------------------------------- /examples/simple_bot.js: -------------------------------------------------------------------------------- 1 | const SOFA = require('sofa-js'); 2 | const Fiat = require('./lib/Fiat') 3 | const Bot = require('./lib/Bot'); 4 | 5 | let bot = new Bot(); 6 | 7 | bot.onEvent = function(session, message) { 8 | switch (message.type) { 9 | case "Message": 10 | onMessage(session, message); 11 | break; 12 | case "Command": 13 | onCommand(session, message); 14 | break; 15 | case "PaymentRequest": 16 | onPaymentRequest(session, message); 17 | break; 18 | } 19 | } 20 | 21 | 22 | function onMessage(session, message) { 23 | //if message body contains the word beg, request a payment 24 | if (message.content.body.includes('beg')) { 25 | session.requestEth(3.5, 'I need about tree fiddy') 26 | return 27 | } 28 | 29 | //if it contains the word ethlogo, send an image message 30 | if (message.content.body.includes('ethlogo')) { 31 | session.reply(SOFA.Message({ 32 | body: "Here is your logo", 33 | attachments: [{ 34 | "type": "image", 35 | "url": "ethereum.jpg" 36 | }] 37 | })) 38 | return 39 | } 40 | 41 | //if it contains a known fiat currency code, send the ETH conversion 42 | if (Object.keys(Fiat.rates).indexOf(message.content.body) > -1) { 43 | Fiat.fetch().then((toEth) => { 44 | session.reply('1 ETH is worth ' + toEth[message.content.body]() + ' ' + message.content.body) 45 | }) 46 | return 47 | } 48 | 49 | //otherwise send a default prompt 50 | sendColorPrompt(session, "I only want to talk about my favorite color. Guess what it is!"); 51 | } 52 | 53 | 54 | function sendColorPrompt(session, body) { 55 | session.reply(SOFA.Message({ 56 | body: body, 57 | controls: [ 58 | {type: "button", label: "Red", value: "red"}, 59 | {type: "button", label: "Green", value: "green"}, 60 | {type: "button", label: "Blue", value: "blue"} 61 | ], 62 | showKeyboard: false 63 | })); 64 | } 65 | 66 | 67 | function onCommand(session, command) { 68 | if (command.content.value === "red") { 69 | session.reply("Yep! Red is the best"); 70 | } else { 71 | sendColorPrompt(session, "Nope! Try again."); 72 | } 73 | } 74 | 75 | 76 | function onPaymentRequest(session, message) { 77 | //fetch fiat conversion rates 78 | Fiat.fetch().then((toEth) => { 79 | let limit = toEth.USD(100) 80 | if (message.ethValue < limit) { 81 | session.sendEth(message.ethValue, (session, error, result) => { 82 | if (error) { session.reply('I tried but there was an error') } 83 | if (result) { session.reply('Here you go!') } 84 | }) 85 | } else { 86 | session.reply('Sorry, I have a 100 USD limit.') 87 | } 88 | }) 89 | .catch((error) => { 90 | session.reply('Sorry, something went wrong while I was looking up exchange rates') 91 | }) 92 | } 93 | -------------------------------------------------------------------------------- /examples/testing_bot.js: -------------------------------------------------------------------------------- 1 | const SOFA = require('sofa-js'); 2 | const Bot = require('./lib/Bot'); 3 | 4 | let bot = new Bot(); 5 | 6 | bot.hear('SOFA::PaymentRequest:', (session, message) => { 7 | let limit = unit.toWei(5, 'ether'); 8 | 9 | if (message.value.lt(limit)) { 10 | if (session.get('human')) { 11 | session.reply("Ok! I'll send you some cash.") 12 | session.sendEth(message.value) 13 | } else { 14 | session.set('ethRequestPendingCaptcha', message.value) 15 | session.openThread('captcha') 16 | } 17 | } else { 18 | session.reply("I have a 5 ETH limit."); 19 | } 20 | }) 21 | 22 | 23 | bot 24 | .thread('captcha') 25 | .onOpen = (session) => { 26 | session.reply("I need you to prove you're human. Type the numbers below:") 27 | session.reply("123") 28 | session.set('captcha', '123') 29 | session.set('captcha_attempts', 3) 30 | session.setState('awaiting_captcha_solve') 31 | } 32 | 33 | 34 | bot 35 | .thread('captcha') 36 | .state('awaiting_captcha_solve') 37 | .hear('SOFA::Message:', (session, message) => { 38 | console.log(message.content.body) 39 | console.log(session.get('captcha')) 40 | if (message.content.body == session.get('captcha')) { 41 | session.set('human', true) 42 | session.reply('Correct!') 43 | session.sendEth(session.get('ethRequestPendingCaptcha')) 44 | session.closeThread() 45 | } else { 46 | if (session.get('captcha_attempts') > 0) { 47 | session.set('captcha_attempts', session.get('captcha_attempts')-1) 48 | session.reply('Incorrect, try again') 49 | } else { 50 | session.reply('Too many failures. #bye') 51 | session.closeThread() 52 | } 53 | } 54 | }) 55 | 56 | 57 | bot.hear('ping', (session, message) => { 58 | session.rpc({ 59 | method: "ping", 60 | params: {} 61 | }, (session, error, result) => { 62 | console.log(result); 63 | session.reply(result.message); 64 | }); 65 | }) 66 | 67 | bot.hear('reset', (session, message) => { 68 | session.reset() 69 | session.reply(SOFA.Message({body: "I've reset your state."})); 70 | }) 71 | 72 | bot.hear('SOFA::Payment:', (session, message) => { 73 | session.reply("Thanks for the loot."); 74 | }) 75 | 76 | bot.hear('initMe', (session, message) => { 77 | session.reply(SOFA.InitRequest({ 78 | values: ['paymentAddress', 'language'] 79 | })); 80 | }) 81 | 82 | bot.hear('begMe', (session, message) => { 83 | session.reply(SOFA.PaymentRequest({ 84 | body: "Thanks for the great time! Can you send your share of the tab?", 85 | value: "0xce0eb154f900000", 86 | destinationAddress: "0x056db290f8ba3250ca64a45d16284d04bc6f5fbf" 87 | })); 88 | }) 89 | 90 | bot.hear('SOFA::Command:', (session, message) => { 91 | session.reply("I was commanded: "+message.content.value); 92 | }) 93 | 94 | bot.hear('buttons', (session, message) => { 95 | session.reply(SOFA.Message({ 96 | body: "Now let’s try sending some money. Choose a charity to make a donation of $0.01.", 97 | controls: [ 98 | {type: "button", label: "Red Cross", value: "red-cross"}, 99 | {type: "button", label: "Ethereum foundation", value: "ethereum-foundation"}, 100 | {type: "button", label: "GiveWell.org", value: "givewell.org"}, 101 | {type: "button", label: "Not now, thanks", value: null} 102 | ] 103 | })); 104 | }) 105 | 106 | bot.hear('groups', (session, message) => { 107 | session.reply(SOFA.Message({ 108 | body: "What would you like me to do for you right now?", 109 | controls: [ 110 | { 111 | type: "group", 112 | label: "Trip", 113 | controls: [ 114 | {type: "button", label: "Directions", action: "Webview:/Directions"}, 115 | {type: "button", label: "Timetable", value: "timetable"}, 116 | {type: "button", label: "Exit Info", value: "exit"}, 117 | {type: "button", label: "Service Conditions", action: "Webview:/ServiceConditions"} 118 | ] 119 | },{ 120 | type: "group", 121 | label: "Services", 122 | controls: [ 123 | {type: "button", label: "Buy Ticket", action: "buy-ticket"}, 124 | {type: "button", label: "Support", value: "support"} 125 | ] 126 | }, 127 | {type: "button", label: "Nothing", value: -1} 128 | ], 129 | showKeyboard: false 130 | })); 131 | }) 132 | 133 | 134 | bot.hear('SOFA::Message:', (session, message) => { 135 | session.reply("Hello "+session.address+"! You can say these things:\n1. ping\n2. reset\n3. initMe\n4. buttons\n5. groups\n6. begMe"); 136 | }); 137 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barmstrong/week-in-ethereum/f4d1fc53087caa2beaeea8584fb46a79a9fee527/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Apr 21 13:43:17 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.1-bin.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn ( ) { 37 | echo "$*" 38 | } 39 | 40 | die ( ) { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save ( ) { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "token-app-js", 3 | "version": "0.1.2", 4 | "description": "", 5 | "main": "src/bot.js", 6 | "dependencies": { 7 | "chalk": "^1.1.3", 8 | "ethjs-unit": "^0.1.6", 9 | "feedparser": "^2.1.0", 10 | "glob": "^7.1.1", 11 | "js-yaml": "^3.7.0", 12 | "mkdirp": "^0.5.1", 13 | "pg": "^6.1.2", 14 | "redis": "^2.6.5", 15 | "request": "^2.81.0", 16 | "request-promise-native": "^1.0.3", 17 | "sofa-js": "sofaprotocol/sofa-js#34c6aa7", 18 | "word-wrap": "^1.2.1" 19 | }, 20 | "devDependencies": {}, 21 | "scripts": { 22 | "test": "echo \"Error: no test specified\" && exit 1", 23 | "console": "nodemon -L src/lib/Console.js config.yml" 24 | }, 25 | "engines": { 26 | "node": "7.4.0" 27 | }, 28 | "author": "Token", 29 | "license": "ISC" 30 | } 31 | -------------------------------------------------------------------------------- /src/bot.js: -------------------------------------------------------------------------------- 1 | const Bot = require('./lib/Bot'); 2 | const FeedPoller = require('./lib/FeedPoller'); 3 | const SOFA = require('sofa-js'); 4 | const constants = require('./lib/constants'); 5 | const Fiat = require('./lib/Fiat'); 6 | 7 | let bot = new Bot(); 8 | let poller = new FeedPoller(bot); 9 | 10 | // ROUTING 11 | bot.onEvent = function(session, message) { 12 | switch (message.type) { 13 | case 'Init': 14 | welcome(session); 15 | break; 16 | case 'Message': 17 | onMessage(session, message); 18 | break; 19 | case 'Command': 20 | onCommand(session, message); 21 | break; 22 | case 'Payment': 23 | onPayment(session); 24 | break; 25 | } 26 | }; 27 | 28 | function onMessage(session, message) { 29 | poller.isSubscribed(session.get('tokenId'), function(err, subscribed) { 30 | if (subscribed) { 31 | latest(session); 32 | } else { 33 | welcome(session); 34 | } 35 | }); 36 | }; 37 | 38 | function onCommand(session, command) { 39 | switch (command.content.value) { 40 | case 'latest': 41 | latest(session); 42 | break; 43 | case 'tip': 44 | tip(session); 45 | break; 46 | case 'about': 47 | about(session); 48 | break; 49 | case 'historical': 50 | historical(session); 51 | break; 52 | case 'amount1': 53 | case 'amount2': 54 | case 'amount3': 55 | case 'amount4': 56 | amount(session, command.content.value); 57 | break; 58 | case 'subscribe': 59 | subscribe(session) 60 | break 61 | case 'cancel': 62 | case 'latest': 63 | latest(session) 64 | break 65 | case 'unsubscribe': 66 | unsubscribe(session) 67 | break 68 | } 69 | }; 70 | 71 | // STATES 72 | 73 | function onPayment(session) { 74 | let message = `Thank you for supporting '${constants.NAME}' 🙏`; 75 | sendMessage(session, message); 76 | }; 77 | 78 | function welcome(session) { 79 | let message = `Hi! Welcome to '${constants.NAME}'. We keep you up to date with what's happening in the Ethereum community. Tap 'Subscribe' to get started.`; 80 | sendMessage(session, message); 81 | }; 82 | 83 | function latest(session) { 84 | // Default response for subscribed users 85 | let article = poller.latestArticle(); 86 | let message = `Check out the latest issue of '${constants.NAME}': ${article.link}`; 87 | sendMessage(session, message); 88 | }; 89 | 90 | function tip(session) { 91 | let controls = []; 92 | for (let key in constants.AMOUNTS) { 93 | let val = constants.AMOUNTS[key]; 94 | controls.push({type: 'button', label: `$${val}`, value: key}) 95 | } 96 | controls.push({type: 'button', label: `Cancel`, value: 'cancel'}) 97 | 98 | session.reply(SOFA.Message({ 99 | body: "Choose an amount:", 100 | controls: controls, 101 | showKeyboard: false, 102 | })); 103 | }; 104 | 105 | function amount(session, command) { 106 | // fetch exchange rates once a minute 107 | Fiat.fetch(1000 * 1).then((toEth) => { 108 | session.requestEth(toEth.USD(constants.AMOUNTS[command]), `Help support '${constants.NAME}'`) 109 | }); 110 | }; 111 | 112 | function about(session) { 113 | let message = `This Week In Ethereum is published by Evan Van Ness. It is a weekly collection of the latest news in the Ethereum community.`; 114 | sendMessage(session, message); 115 | }; 116 | 117 | function historical(session) { 118 | let message = "Here are some recent articles:\n"; 119 | let count = 0; 120 | poller.articlesList().forEach(function(article) { 121 | count = count + 1; 122 | if (count > 5) 123 | return 124 | let date = article.date.toString().split(' ').slice(0,4).join(' '); 125 | message = message + `${date}\n${article.link}\n` 126 | // message = message + `${typeof(article.date)}\n${article.link}\n` 127 | }); 128 | sendMessage(session, message); 129 | }; 130 | 131 | function subscribe(session) { 132 | poller.addUser(session); 133 | session.reply(`Thank you for subscribing 🙌. We'll notify you whenever a new article comes out (about once a week).`); 134 | latest(session); 135 | }; 136 | 137 | function unsubscribe(session) { 138 | poller.removeUser(session); 139 | let message = '😭 sorry to see you go. We hope you come back!' 140 | sendMessage(session, message); 141 | }; 142 | 143 | // HELPERS 144 | 145 | function sendMessage(session, message) { 146 | // redisClient.sismember('users', session.get('tokenId'), function(err, subscribed) { 147 | poller.isSubscribed(session.get('tokenId'), function(err, subscribed) { 148 | let controls = []; 149 | if (subscribed) { 150 | controls = constants.SUBSCRIBED_CONTROLS; 151 | } else { 152 | controls = constants.UNSUBSCRIBED_CONTROLS; 153 | } 154 | session.reply(SOFA.Message({ 155 | body: message, 156 | controls: controls, 157 | showKeyboard: false, 158 | })); 159 | }); 160 | } 161 | -------------------------------------------------------------------------------- /src/lib/Bot.js: -------------------------------------------------------------------------------- 1 | const Client = require('./Client'); 2 | const Thread = require('./Thread'); 3 | const SOFA = require('sofa-js'); 4 | 5 | class Bot { 6 | constructor() { 7 | this.client = new Client(this); 8 | this.rootThread = new Thread(this); 9 | for (let [name, schema] of Object.entries(SOFA.schemas)) { 10 | let handlerName = "on" + name; 11 | this[handlerName] = function(cb) { 12 | let pattern = "SOFA::"+name+":"; 13 | this.rootThread.hear(pattern, null, cb); 14 | }; 15 | } 16 | this.threads = {}; 17 | } 18 | 19 | thread(name) { 20 | if (!this.threads.hasOwnProperty(name)) { 21 | this.threads[name] = new Thread(this); 22 | } 23 | return this.threads[name]; 24 | } 25 | 26 | hear(pattern, cb) { 27 | this.rootThread.hear(pattern, null, cb); 28 | } 29 | 30 | onClientMessage(session, message) { 31 | if (this.onEvent) { 32 | this.onEvent(session, message); 33 | } 34 | let heard = false; 35 | if (session.thread) { 36 | heard = session.thread.onClientMessage(session, message); 37 | } 38 | if (!heard) { 39 | heard = this.rootThread.onClientMessage(session, message); 40 | } 41 | return heard; 42 | } 43 | } 44 | 45 | module.exports = Bot; 46 | -------------------------------------------------------------------------------- /src/lib/Client.js: -------------------------------------------------------------------------------- 1 | const redis = require('redis'); 2 | const SOFA = require('sofa-js'); 3 | const url = require('url') 4 | const pg = require('pg'); 5 | const Config = require('./Config'); 6 | const Session = require('./Session'); 7 | const Logger = require('./Logger'); 8 | 9 | const JSONRPC_VERSION = '2.0'; 10 | const JSONRPC_REQUEST_CHANNEL = '_rpc_request'; 11 | const JSONRPC_RESPONSE_CHANNEL = '_rpc_response'; 12 | 13 | class Client { 14 | constructor(bot) { 15 | this.bot = bot; 16 | this.rpcCalls = {}; 17 | this.nextRpcId = 0; 18 | 19 | this.config = new Config(process.argv[2]); 20 | console.log("Address: "+this.config.address); 21 | 22 | let params = url.parse(this.config.postgres.url); 23 | let auth = params.auth.split(':'); 24 | let pgConfig = { 25 | user: auth[0], 26 | password: auth[1], 27 | host: params.hostname, 28 | port: params.port, 29 | database: params.pathname.split('/')[1], 30 | max: 5, 31 | idleTimeoutMillis: 30000 32 | }; 33 | this.pgPool = new pg.Pool(pgConfig); 34 | this.pgPool.on('error', function (err, client) { 35 | console.error('idle client error', err.message, err.stack) 36 | }) 37 | 38 | let redisConfig = { 39 | host: this.config.redis.host, 40 | port: this.config.redis.port, 41 | password: this.config.redis.password 42 | } 43 | 44 | this.subscriber = redis.createClient(redisConfig); 45 | this.rpcSubscriber = redis.createClient(redisConfig); 46 | this.publisher = redis.createClient(redisConfig); 47 | 48 | this.subscriber.on("error", function (err) { 49 | console.log("Error " + err); 50 | }); 51 | this.rpcSubscriber.on("error", function (err) { 52 | console.log("Error " + err); 53 | }); 54 | this.publisher.on("error", function (err) { 55 | console.log("Error " + err); 56 | }); 57 | 58 | this.subscriber.on("message", (channel, message) => { 59 | try { 60 | let wrapped = JSON.parse(message); 61 | if (wrapped.recipient == this.config.address) { 62 | let session = new Session(this.bot, this.pgPool, this.config, wrapped.sender, () => { 63 | let sofa = SOFA.parse(wrapped.sofa); 64 | Logger.receivedMessage(sofa); 65 | 66 | if (sofa.type == "Init") { 67 | for(let k in sofa.content) { 68 | session.set(k, sofa.content[k]); 69 | } 70 | let held = session.get('heldForInit') 71 | if (held) { 72 | session.set('heldForInit', null) 73 | let heldSofa = SOFA.parse(held); 74 | this.bot.onClientMessage(session, heldSofa); 75 | } 76 | } else { 77 | if (!session.get('paymentAddress')) { 78 | console.log('User has not sent Init message, sending InitRequest') 79 | session.set('heldForInit', wrapped.sofa) 80 | session.reply(SOFA.InitRequest({ 81 | values: ['paymentAddress', 'language'] 82 | })); 83 | } else { 84 | this.bot.onClientMessage(session, sofa); 85 | } 86 | } 87 | 88 | }); 89 | } 90 | } catch(e) { 91 | console.log("On Message Error: "+e); 92 | } 93 | }); 94 | this.subscriber.subscribe(this.config.address); 95 | 96 | this.rpcSubscriber.on("message", (channel, message) => { 97 | try { 98 | message = JSON.parse(message); 99 | if (message.jsonrpc == JSONRPC_VERSION) { 100 | let stored = this.rpcCalls[message.id]; 101 | delete this.rpcCalls[message.id]; 102 | let session = new Session(this.bot, this.pgPool, this.config, stored.sessionAddress, () => { 103 | stored.callback(session, message.error, message.result); 104 | }); 105 | } 106 | } catch(e) { 107 | console.log("On RPC Message Error: "+e); 108 | } 109 | }) 110 | this.rpcSubscriber.subscribe(this.config.address+JSONRPC_RESPONSE_CHANNEL); 111 | } 112 | 113 | send(address, message) { 114 | if (typeof message === "string") { 115 | message = SOFA.Message({body: message}) 116 | } 117 | Logger.sentMessage(message); 118 | this.publisher.publish(this.config.address, JSON.stringify({ 119 | sofa: message.string, 120 | sender: this.config.address, 121 | recipient: address 122 | })); 123 | } 124 | 125 | rpc(session, rpcCall, callback) { 126 | rpcCall.id = this.getRpcId(); 127 | this.rpcCalls[rpcCall.id] = {sessionAddress: session.address, callback: callback}; 128 | this.publisher.publish(this.config.address+JSONRPC_REQUEST_CHANNEL, JSON.stringify(rpcCall)); 129 | } 130 | 131 | getRpcId() { 132 | let id = this.nextRpcId; 133 | this.nextRpcId += 1; 134 | return id.toString(); 135 | } 136 | } 137 | 138 | module.exports = Client; 139 | -------------------------------------------------------------------------------- /src/lib/Config.js: -------------------------------------------------------------------------------- 1 | const url = require('url'); 2 | const fs = require('fs'); 3 | const yaml = require('js-yaml'); 4 | 5 | class Config { 6 | constructor(path) { 7 | let config = yaml.safeLoad(fs.readFileSync(path, 'utf8')); 8 | for(let k in config) this[k]=config[k]; 9 | 10 | if (this.postgres.url) { this.postgresUrl = this.postgres.url; } 11 | if (this.postgres.envKey) { this.postgresUrl = process.env[this.postgres.envKey]; } 12 | if (this.redis.uri) { this.redisUrl = this.redis.uri; } 13 | if (this.redis.envKey) { this.redisUrl = process.env[this.redis.envKey]; } 14 | if (!this.address) { this.address = process.env['TOKEN_APP_ID'] } 15 | if (!this.paymentAddress) { this.paymentAddress = process.env['TOKEN_APP_PAYMENT_ADDRESS'] } 16 | } 17 | 18 | set postgresUrl(s) { 19 | this.postgres = {url: s}; 20 | /* 21 | let url = url.parse(s); 22 | this.username = dbUri.getUserInfo().split(":")[0]; 23 | this.password = dbUri.getUserInfo().split(":")[1]; 24 | this.jdbcUrl = "jdbc:postgresql://" + dbUri.getHost() + ':' + dbUri.getPort() + dbUri.getPath(); 25 | */ 26 | } 27 | 28 | set redisUrl(s) { 29 | let uri = url.parse(s); 30 | if (uri.protocol && uri.protocol == 'redis:') { 31 | this.redis.host = uri.hostname; 32 | this.redis.port = uri.port; 33 | if (uri.auth && uri.auth.indexOf(':') > -1) { 34 | this.redis.password = uri.auth.split(':')[1]; 35 | } 36 | } 37 | } 38 | } 39 | 40 | module.exports = Config; 41 | -------------------------------------------------------------------------------- /src/lib/Console.js: -------------------------------------------------------------------------------- 1 | const readline = require('readline'); 2 | const redis = require('redis'); 3 | const SOFA = require('sofa-js'); 4 | const Config = require('./Config'); 5 | const Writable = require('stream').Writable; 6 | 7 | const JSONRPC_VERSION = '2.0'; 8 | const JSONRPC_REQUEST_CHANNEL = '_rpc_request'; 9 | const JSONRPC_RESPONSE_CHANNEL = '_rpc_response'; 10 | 11 | let mutableStdout = new Writable({ 12 | write: function(chunk, encoding, callback) { 13 | if (!this.muted) 14 | process.stdout.write(chunk, encoding); 15 | callback(); 16 | } 17 | }); 18 | mutableStdout.muted = true; 19 | 20 | class Console { 21 | constructor(address) { 22 | this.config = new Config(process.argv[2]); 23 | this.address = address 24 | 25 | this.rl = readline.createInterface({ 26 | input: process.stdin, 27 | output: mutableStdout, 28 | terminal: true 29 | }); 30 | 31 | this.rl.setPrompt(this.address+'> '); 32 | 33 | this.channelName = this.config.address 34 | let redisConfig = { 35 | host: this.config.redis.host, 36 | port: this.config.redis.port, 37 | password: this.config.redis.password 38 | } 39 | 40 | this.subscriber = redis.createClient(redisConfig); 41 | this.rpcSubscriber = redis.createClient(redisConfig); 42 | this.publisher = redis.createClient(redisConfig); 43 | 44 | this.subscriber.on("message", (channel, message) => { 45 | try { 46 | let wrapped = JSON.parse(message); 47 | if (wrapped.recipient == this.address) { 48 | let sofa = SOFA.parse(wrapped.sofa); 49 | process.stdout.clearLine(); 50 | process.stdout.cursorTo(0); 51 | console.log('bot> '+sofa.string) 52 | prompt() 53 | } 54 | } catch(e) { 55 | console.log("On Message Error: "+e); 56 | } 57 | }); 58 | this.subscriber.subscribe(this.channelName); 59 | } 60 | 61 | send(message) { 62 | if (typeof message === "string") { 63 | message = SOFA.Message({body: message}) 64 | } 65 | this.publisher.publish(this.config.address, JSON.stringify({ 66 | sofa: message.string, 67 | sender: this.config.address, 68 | recipient: session.address 69 | })); 70 | } 71 | 72 | run() { 73 | prompt(); 74 | this.rl.on('line', (line) => { 75 | if (line === "/quit") { 76 | this.rl.close(); 77 | } else { 78 | var message = JSON.stringify({ 79 | sofa: SOFA.Message({body: line}).string, 80 | sender: this.address, 81 | recipient: this.config.address 82 | }); 83 | this.publisher.publish(this.channelName, message); 84 | } 85 | prompt(); 86 | }).on('close',function(){ 87 | process.exit(0); 88 | }); 89 | } 90 | } 91 | 92 | 93 | let c = new Console("user") 94 | 95 | function prompt() { 96 | mutableStdout.muted = false; 97 | c.rl.prompt(); 98 | mutableStdout.muted = true; 99 | } 100 | 101 | c.run() 102 | -------------------------------------------------------------------------------- /src/lib/FeedPoller.js: -------------------------------------------------------------------------------- 1 | const Config = require('./Config'); 2 | const FeedParser = require('feedparser'); 3 | const request = require('request'); 4 | const redis = require('redis'); 5 | const constants = require('./constants'); 6 | const SOFA = require('sofa-js'); 7 | 8 | const config = new Config(process.argv[2]); 9 | const redisClient = redis.createClient({ 10 | host: config.redis.host, 11 | port: config.redis.port, 12 | password: config.redis.password 13 | }); 14 | 15 | class FeedPoller { 16 | constructor(bot) { 17 | this.url = 'http://www.weekinethereum.com/rss'; 18 | this.feedparser = new FeedParser(); 19 | this.articles = []; 20 | this.getArticles(); 21 | this.start(); 22 | this.bot = bot; 23 | }; 24 | 25 | start() { 26 | this.timer = setInterval(() => { 27 | this.getArticles(); 28 | this.broadcast(); 29 | }, 60 * 5 * 1000); // 5 minutes 30 | } 31 | 32 | // Broadcast to all users the latest article 33 | broadcast() { 34 | let latestArticle = this.articles[0]; 35 | console.log(`Broadcast Latest Article: ${latestArticle.id}`) 36 | let self = this; 37 | 38 | redisClient.smembers('subscribed', function(err, users) { 39 | users.forEach(function(user) { 40 | redisClient.hget('latest', user, function(err, lastReceivedId) { 41 | if (latestArticle.id !== lastReceivedId) { 42 | console.log(`Sending to user ${user}`) 43 | let message = `Hey! The latest issue of ${constants.NAME} is out: ${latestArticle.link}`; 44 | 45 | self.bot.client.send(user, SOFA.Message({ 46 | body: message, 47 | controls: constants.SUBSCRIBED_CONTROLS, 48 | showKeyboard: false, 49 | })); 50 | 51 | self.markRead(user, latestArticle.id); 52 | } 53 | }); 54 | }); 55 | }); 56 | }; 57 | 58 | addUser(session) { 59 | let userId = session.get('tokenId'); 60 | redisClient.sadd('subscribed', userId); 61 | this.markRead(userId, this.articles[0].id); 62 | }; 63 | 64 | removeUser(session) { 65 | redisClient.srem('subscribed', session.get('tokenId')); 66 | }; 67 | 68 | isSubscribed(userId, callback) { 69 | redisClient.sismember('subscribed', userId, function(err, val) { 70 | callback(err, val); 71 | }); 72 | }; 73 | 74 | markRead(userId, articleId) { 75 | redisClient.hset('latest', userId, articleId); 76 | }; 77 | 78 | getArticles() { 79 | // Populate this.articles with all articles available 80 | let req = request(this.url) 81 | let self = this; 82 | 83 | req.on('response', function (res) { 84 | if (res.statusCode !== 200) { 85 | console.log('Request failed'); 86 | } else { 87 | this.pipe(self.feedparser); 88 | } 89 | }); 90 | 91 | req.on('error', function (error) { 92 | console.log('Request failed'); 93 | }); 94 | 95 | this.feedparser.on('readable', function () { 96 | // This is where the action is! 97 | let meta = this.meta; 98 | let item, article; 99 | this.articles = []; 100 | 101 | while (item = this.read()) { 102 | article = { 103 | id: item['guid'], 104 | title: item['title'], 105 | link: item['link'], 106 | date: item['date'], 107 | description: item['description'], 108 | summary: item['summary'] 109 | } 110 | self.articles.push(article) 111 | } 112 | }); 113 | 114 | this.feedparser.on('error', function (error) { 115 | console.log('Feed parser failed'); 116 | console.log(error); 117 | }); 118 | 119 | } 120 | 121 | latestArticle() { 122 | return this.articles[0]; 123 | } 124 | 125 | articlesList() { 126 | return this.articles; 127 | } 128 | } 129 | 130 | module.exports = FeedPoller; 131 | -------------------------------------------------------------------------------- /src/lib/Fiat.js: -------------------------------------------------------------------------------- 1 | var rp = require('request-promise-native'); 2 | const endpoint = 'https://api.coinbase.com/v2/exchange-rates?currency=ETH' 3 | 4 | const CACHE_AGE_LIMIT = 5 * 60 * 1000 // 5 minutes 5 | let rates = {} 6 | let helpers = {} 7 | let cachedAt = 0 8 | 9 | function getRates() { 10 | console.log("Fiat: Fetching rates") 11 | return rp(endpoint) 12 | .then((body) => { 13 | cachedAt = new Date().getTime() 14 | 15 | let freshRates = JSON.parse(body).data.rates 16 | for (let k in freshRates) { 17 | rates[k] = freshRates[k] 18 | } 19 | generateHelpers() 20 | return helpers 21 | }) 22 | .catch((error) => { 23 | console.log("Fiat fetch error: " + error) 24 | }) 25 | } 26 | 27 | function generateHelpers() { 28 | for (let [code, rate] of Object.entries(rates)) { 29 | (function(code,rate) { 30 | helpers[code] = function(fiat) { 31 | if (fiat) { 32 | return fiat / rates[code] 33 | } else { 34 | return rates[code] 35 | } 36 | } 37 | })(code,rate) 38 | } 39 | } 40 | 41 | function fetch(limit=CACHE_AGE_LIMIT) { 42 | let now = new Date().getTime() 43 | if (now - cachedAt > limit) { 44 | return getRates() 45 | } else { 46 | console.log("Fiat: Using cached rates") 47 | return Promise.resolve(helpers) 48 | } 49 | } 50 | 51 | getRates() 52 | .then((helper) => { 53 | console.log("Fiat: Rates initialized successfully") 54 | }) 55 | 56 | module.exports = { fetch: fetch, rates: rates } 57 | -------------------------------------------------------------------------------- /src/lib/Logger.js: -------------------------------------------------------------------------------- 1 | const wrap = require('word-wrap'); 2 | const chalk = require('chalk'); 3 | chalk.enabled = true; 4 | 5 | function mapLines(s, f) { 6 | if (s == null) { s = "null" } 7 | return s.split('\n').map(f) 8 | } 9 | 10 | class Logger { 11 | 12 | static sentMessage(sofa) { 13 | Logger.log(Logger.colorPrefix('\u21D0 ', wrap(sofa.string, {width: 60, cut: true}), chalk.green, chalk.grey)); 14 | Logger.log(Logger.color('\u21D0 ', sofa.display, chalk.green)); 15 | Logger.log('\n'); 16 | } 17 | 18 | static receivedMessage(sofa) { 19 | Logger.log(Logger.colorPrefix('\u21D2 ', wrap(sofa.string, {width: 60, cut: true}), chalk.yellow, chalk.grey)); 20 | Logger.log(Logger.color('\u21D2 ', sofa.display, chalk.yellow)); 21 | Logger.log('\n'); 22 | } 23 | 24 | static color(prefix, message, color) { 25 | let lines = mapLines(message, (x) => { return color(prefix + x) }); 26 | return lines.join('\n'); 27 | } 28 | 29 | static colorPrefix(prefix, message, color, color2) { 30 | let lines = mapLines(message, (x) => { return color(prefix) + color2(x) }); 31 | return lines.join('\n'); 32 | } 33 | 34 | static log(o) { 35 | console.log(o); 36 | } 37 | 38 | } 39 | 40 | module.exports = Logger; 41 | -------------------------------------------------------------------------------- /src/lib/Session.js: -------------------------------------------------------------------------------- 1 | const Config = require('./Config'); 2 | const fs = require('fs'); 3 | const mkdirp = require('mkdirp'); 4 | const pg = require('pg'); 5 | const url = require('url') 6 | const unit = require('ethjs-unit'); 7 | const SOFA = require('sofa-js'); 8 | 9 | class Session { 10 | constructor(bot, pgPool, config, address, onReady) { 11 | this.bot = bot; 12 | this.config = config; 13 | this.pgPool = pgPool; 14 | 15 | if (!fs.existsSync(this.config.store)) { 16 | mkdirp.sync(this.config.store); 17 | } 18 | this.address = address; 19 | this.path = this.config.store+'/'+address+'.json'; 20 | this.data = { 21 | address: this.address 22 | }; 23 | this.thread = null; 24 | this.state = null; 25 | 26 | this.load(onReady); 27 | } 28 | 29 | get(key) { 30 | if (key === 'tokenId') { 31 | return this.address; 32 | } 33 | return this.data[key]; 34 | } 35 | 36 | set(key, value) { 37 | this.data[key] = value; 38 | this.flush(); 39 | } 40 | 41 | setState(name) { 42 | this.state = name; 43 | this.set('_state', name); 44 | } 45 | 46 | openThread(name) { 47 | this.closeThread(); 48 | this.set('_thread', name) 49 | this.thread = this.bot.threads[name]; 50 | this.thread.open(this); 51 | } 52 | 53 | closeThread() { 54 | if (this.thread) { 55 | this.thread.close(this); 56 | } 57 | this.thread = null; 58 | this.set('_thread', null); 59 | this.setState(null) 60 | } 61 | 62 | reset() { 63 | this.closeThread() 64 | this.setState(null) 65 | this.data = { 66 | address: this.address 67 | }; 68 | this.flush(); 69 | } 70 | 71 | reply(message) { 72 | this.bot.client.send(this.address, message); 73 | } 74 | 75 | sendEth(value, callback) { 76 | value = '0x' + unit.toWei(value, 'ether').toString(16) 77 | this.bot.client.rpc(this, { 78 | method: "sendTransaction", 79 | params: { 80 | to: this.get('paymentAddress'), 81 | value: value 82 | } 83 | }, (session, error, result) => { 84 | if (result) { 85 | session.reply(SOFA.Payment({ 86 | status: "unconfirmed", 87 | value: value, 88 | txHash: result.txHash, 89 | fromAddress: this.config.address, 90 | toAddress: this.address 91 | })); 92 | } 93 | if (callback) { callback(session, error, result); } 94 | }); 95 | } 96 | 97 | requestEth(value, message) { 98 | value = '0x' + unit.toWei(value, 'ether').toString(16) 99 | this.reply(SOFA.PaymentRequest({ 100 | body: message, 101 | value: value, 102 | destinationAddress: this.config.paymentAddress 103 | })); 104 | } 105 | 106 | load(onReady) { 107 | this.execute('SELECT * from bot_sessions WHERE eth_address = $1', [this.address], (err, result) => { 108 | if (err) { console.log(err) } 109 | if (!err && result.rows.length > 0) { 110 | this.data = result.rows[0].data 111 | if (this.data._thread) { 112 | this.thread = this.bot.threads[this.data._thread]; 113 | } 114 | if (this.data._state) { 115 | this.state = this.data._state; 116 | } 117 | } else { 118 | this.data = { 119 | address: this.address 120 | }; 121 | } 122 | onReady() 123 | }); 124 | } 125 | 126 | flush() { 127 | this.data.timestamp = Math.round(new Date().getTime()/1000); 128 | let query = `INSERT INTO bot_sessions (eth_address, data) 129 | VALUES ($1, $2) 130 | ON CONFLICT (eth_address) DO UPDATE 131 | SET data = $2`; 132 | this.execute(query, [this.address, this.data], (err, result) => { 133 | if (err) { console.log(err) } 134 | }) 135 | } 136 | 137 | execute(query, args, cb) { 138 | this.pgPool.connect((err, client, done) => { 139 | if (err) { return cb(err) } 140 | client.query(query, args, (err, result) => { 141 | done(err); 142 | if (err) { return cb(err) } 143 | cb(null, result); 144 | }) 145 | }) 146 | } 147 | 148 | get json() { 149 | return JSON.stringify(this.data); 150 | } 151 | } 152 | 153 | module.exports = Session; 154 | -------------------------------------------------------------------------------- /src/lib/Thread.js: -------------------------------------------------------------------------------- 1 | class Thread { 2 | constructor(bot) { 3 | this.bot = bot; 4 | this.hearings = []; 5 | this.onOpen = null; 6 | this.onClose = null; 7 | this.states = {}; 8 | } 9 | 10 | open(session) { 11 | if (this.onOpen) { this.onOpen(session) } 12 | } 13 | 14 | close(session) { 15 | if (this.onClose) { this.onClose(session) } 16 | } 17 | 18 | hear(pattern, stateName, cb) { 19 | this.hearings.push({pattern: pattern, state: stateName, callback: cb}); 20 | } 21 | 22 | state(name) { 23 | if (!this.states.hasOwnProperty(name)) { 24 | this.states[name] = { 25 | hear: (pattern, cb) => { 26 | this.hear(pattern, name, cb); 27 | } 28 | } 29 | } 30 | return this.states[name]; 31 | } 32 | 33 | onClientMessage(session, message) { 34 | let match; 35 | for (let hearing of this.hearings) { 36 | let rx = new RegExp(hearing.pattern); 37 | match = rx.exec(message.string); 38 | let stateMatch = hearing.state ? session.state == hearing.state : true 39 | if (match && stateMatch) { 40 | hearing.callback(session, message); 41 | return true; 42 | } 43 | } 44 | return false; 45 | } 46 | } 47 | 48 | module.exports = Thread; 49 | -------------------------------------------------------------------------------- /src/lib/constants.js: -------------------------------------------------------------------------------- 1 | const constants = { 2 | NAME: 'Week in Ethereum News', 3 | SUBSCRIBED_CONTROLS: [ 4 | { type: 'button', label: 'Latest 📨', value: 'latest' }, 5 | { type: 'button', label: 'Tip 💸', value: 'tip' }, 6 | { 7 | type: 'group', 8 | label: 'More', 9 | controls: [ 10 | { type: 'button', label: 'About', value: 'about' }, 11 | { type: 'button', label: 'Previous Articles', value: 'historical' }, 12 | { type: 'button', label: 'Unsubscribe 👋', value: 'unsubscribe' }, 13 | ], 14 | }, 15 | ], 16 | UNSUBSCRIBED_CONTROLS: [ 17 | { type: 'button', label: 'Latest 📨', value: 'latest' }, 18 | { type: 'button', label: 'Subscribe 📩', value: 'subscribe' }, 19 | ], 20 | AMOUNTS: { 21 | amount1: 0.25, 22 | amount2: 1, 23 | amount3: 5, 24 | amount4: 25, 25 | } 26 | } 27 | 28 | module.exports = constants; 29 | -------------------------------------------------------------------------------- /system.properties: -------------------------------------------------------------------------------- 1 | java.runtime.version=1.8 2 | -------------------------------------------------------------------------------- /yarn-error.log: -------------------------------------------------------------------------------- 1 | Arguments: 2 | /usr/local/Cellar/node/7.5.0/bin/node /usr/local/bin/yarn install 3 | 4 | PATH: 5 | /Users/brianarmstrong/.rbenv/shims:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin 6 | 7 | Yarn version: 8 | 0.20.3 9 | 10 | Node version: 11 | 7.5.0 12 | 13 | Platform: 14 | darwin x64 15 | 16 | npm manifest: 17 | { 18 | "name": "token-app-js", 19 | "version": "0.1.1", 20 | "description": "", 21 | "main": "src/bot.js", 22 | "dependencies": { 23 | "chalk": "^1.1.3", 24 | "ethjs-unit": "^0.1.6", 25 | "feedparser": "^2.1.0", 26 | "glob": "^7.1.1", 27 | "js-yaml": "^3.7.0", 28 | "mkdirp": "^0.5.1", 29 | "pg": "^6.1.2", 30 | "redis": "^2.6.5", 31 | "request": "^2.81.0", 32 | "request-promise-native": "^1.0.3", 33 | "sofa-js": "sofaprotocol/sofa-js#34c6aa7", 34 | "word-wrap": "^1.2.1" 35 | }, 36 | "devDependencies": {}, 37 | "scripts": { 38 | "test": "echo \"Error: no test specified\" && exit 1", 39 | "console": "nodemon -L src/lib/Console.js config.yml" 40 | }, 41 | "engines": { 42 | "node": "7.4.0" 43 | }, 44 | "author": "Token", 45 | "license": "ISC" 46 | } 47 | 48 | yarn manifest: 49 | No manifest 50 | 51 | Lockfile: 52 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 53 | # yarn lockfile v1 54 | 55 | 56 | <<<<<<< HEAD 57 | addressparser@^1.0.1: 58 | version "1.0.1" 59 | resolved "https://registry.yarnpkg.com/addressparser/-/addressparser-1.0.1.tgz#47afbe1a2a9262191db6838e4fd1d39b40821746" 60 | 61 | ======= 62 | >>>>>>> 7b57e2ee584891409362b8499b7cf4c2d651aa53 63 | ajv@^4.11.3, ajv@^4.9.1: 64 | version "4.11.5" 65 | resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.5.tgz#b6ee74657b993a01dce44b7944d56f485828d5bd" 66 | dependencies: 67 | co "^4.6.0" 68 | json-stable-stringify "^1.0.1" 69 | 70 | ansi-regex@^2.0.0: 71 | version "2.1.1" 72 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" 73 | 74 | ansi-styles@^2.2.1: 75 | version "2.2.1" 76 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" 77 | 78 | ap@~0.2.0: 79 | version "0.2.0" 80 | resolved "https://registry.yarnpkg.com/ap/-/ap-0.2.0.tgz#ae0942600b29912f0d2b14ec60c45e8f330b6110" 81 | 82 | argparse@^1.0.7: 83 | version "1.0.9" 84 | resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86" 85 | dependencies: 86 | sprintf-js "~1.0.2" 87 | 88 | <<<<<<< HEAD 89 | array-indexofobject@~0.0.1: 90 | version "0.0.1" 91 | resolved "https://registry.yarnpkg.com/array-indexofobject/-/array-indexofobject-0.0.1.tgz#aaa128e62c9b3c358094568c219ff64fe489d42a" 92 | 93 | ======= 94 | >>>>>>> 7b57e2ee584891409362b8499b7cf4c2d651aa53 95 | asn1@~0.2.3: 96 | version "0.2.3" 97 | resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86" 98 | 99 | assert-plus@1.0.0, assert-plus@^1.0.0: 100 | version "1.0.0" 101 | resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" 102 | 103 | assert-plus@^0.2.0: 104 | version "0.2.0" 105 | resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234" 106 | 107 | asynckit@^0.4.0: 108 | version "0.4.0" 109 | resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" 110 | 111 | aws-sign2@~0.6.0: 112 | version "0.6.0" 113 | resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f" 114 | 115 | aws4@^1.2.1: 116 | version "1.6.0" 117 | resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" 118 | 119 | balanced-match@^0.4.1: 120 | version "0.4.2" 121 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838" 122 | 123 | bcrypt-pbkdf@^1.0.0: 124 | version "1.0.1" 125 | resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d" 126 | dependencies: 127 | tweetnacl "^0.14.3" 128 | 129 | bn.js@4.11.6: 130 | version "4.11.6" 131 | resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.6.tgz#53344adb14617a13f6e8dd2ce28905d1c0ba3215" 132 | 133 | boom@2.x.x: 134 | version "2.10.1" 135 | resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f" 136 | dependencies: 137 | hoek "2.x.x" 138 | 139 | brace-expansion@^1.0.0: 140 | version "1.1.6" 141 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.6.tgz#7197d7eaa9b87e648390ea61fc66c84427420df9" 142 | dependencies: 143 | balanced-match "^0.4.1" 144 | concat-map "0.0.1" 145 | 146 | <<<<<<< HEAD 147 | buffer-shims@^1.0.0: 148 | version "1.0.0" 149 | resolved "https://registry.yarnpkg.com/buffer-shims/-/buffer-shims-1.0.0.tgz#9978ce317388c649ad8793028c3477ef044a8b51" 150 | 151 | ======= 152 | >>>>>>> 7b57e2ee584891409362b8499b7cf4c2d651aa53 153 | buffer-writer@1.0.1: 154 | version "1.0.1" 155 | resolved "https://registry.yarnpkg.com/buffer-writer/-/buffer-writer-1.0.1.tgz#22a936901e3029afcd7547eb4487ceb697a3bf08" 156 | 157 | caseless@~0.12.0: 158 | version "0.12.0" 159 | resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" 160 | 161 | chalk@^1.1.3: 162 | version "1.1.3" 163 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" 164 | dependencies: 165 | ansi-styles "^2.2.1" 166 | escape-string-regexp "^1.0.2" 167 | has-ansi "^2.0.0" 168 | strip-ansi "^3.0.0" 169 | supports-color "^2.0.0" 170 | 171 | co@^4.6.0: 172 | version "4.6.0" 173 | resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" 174 | 175 | combined-stream@^1.0.5, combined-stream@~1.0.5: 176 | version "1.0.5" 177 | resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009" 178 | dependencies: 179 | delayed-stream "~1.0.0" 180 | 181 | concat-map@0.0.1: 182 | version "0.0.1" 183 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 184 | 185 | <<<<<<< HEAD 186 | core-util-is@~1.0.0: 187 | version "1.0.2" 188 | resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" 189 | 190 | ======= 191 | >>>>>>> 7b57e2ee584891409362b8499b7cf4c2d651aa53 192 | cryptiles@2.x.x: 193 | version "2.0.5" 194 | resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" 195 | dependencies: 196 | boom "2.x.x" 197 | 198 | dashdash@^1.12.0: 199 | version "1.14.1" 200 | resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" 201 | dependencies: 202 | assert-plus "^1.0.0" 203 | 204 | delayed-stream@~1.0.0: 205 | version "1.0.0" 206 | resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" 207 | 208 | double-ended-queue@^2.1.0-0: 209 | version "2.1.0-0" 210 | resolved "https://registry.yarnpkg.com/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz#103d3527fd31528f40188130c841efdd78264e5c" 211 | 212 | ecc-jsbn@~0.1.1: 213 | version "0.1.1" 214 | resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505" 215 | dependencies: 216 | jsbn "~0.1.0" 217 | 218 | escape-string-regexp@^1.0.2: 219 | version "1.0.5" 220 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 221 | 222 | esprima@^3.1.1: 223 | version "3.1.3" 224 | resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" 225 | 226 | ethjs-unit@^0.1.6: 227 | version "0.1.6" 228 | resolved "https://registry.yarnpkg.com/ethjs-unit/-/ethjs-unit-0.1.6.tgz#c665921e476e87bce2a9d588a6fe0405b2c41699" 229 | dependencies: 230 | bn.js "4.11.6" 231 | number-to-bn "1.7.0" 232 | 233 | extend@~3.0.0: 234 | version "3.0.0" 235 | resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.0.tgz#5a474353b9f3353ddd8176dfd37b91c83a46f1d4" 236 | 237 | extsprintf@1.0.2: 238 | version "1.0.2" 239 | resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.0.2.tgz#e1080e0658e300b06294990cc70e1502235fd550" 240 | 241 | <<<<<<< HEAD 242 | feedparser@^2.1.0: 243 | version "2.1.0" 244 | resolved "https://registry.yarnpkg.com/feedparser/-/feedparser-2.1.0.tgz#4cd98bf04e18db5b8644f91e98da89dd179f1fe7" 245 | dependencies: 246 | addressparser "^1.0.1" 247 | array-indexofobject "~0.0.1" 248 | lodash.assign "^4.2.0" 249 | lodash.get "^4.4.2" 250 | lodash.has "^4.5.2" 251 | lodash.uniq "^4.5.0" 252 | readable-stream "^2.2.2" 253 | sax "^1.2.1" 254 | 255 | ======= 256 | >>>>>>> 7b57e2ee584891409362b8499b7cf4c2d651aa53 257 | forever-agent@~0.6.1: 258 | version "0.6.1" 259 | resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" 260 | 261 | form-data@~2.1.1: 262 | version "2.1.2" 263 | resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.2.tgz#89c3534008b97eada4cbb157d58f6f5df025eae4" 264 | dependencies: 265 | asynckit "^0.4.0" 266 | combined-stream "^1.0.5" 267 | mime-types "^2.1.12" 268 | 269 | fs.realpath@^1.0.0: 270 | version "1.0.0" 271 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 272 | 273 | generic-pool@2.4.2: 274 | version "2.4.2" 275 | resolved "https://registry.yarnpkg.com/generic-pool/-/generic-pool-2.4.2.tgz#886bc5bf0beb7db96e81bcbba078818de5a62683" 276 | 277 | getpass@^0.1.1: 278 | version "0.1.6" 279 | resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.6.tgz#283ffd9fc1256840875311c1b60e8c40187110e6" 280 | dependencies: 281 | assert-plus "^1.0.0" 282 | 283 | glob@^7.1.1: 284 | version "7.1.1" 285 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8" 286 | dependencies: 287 | fs.realpath "^1.0.0" 288 | inflight "^1.0.4" 289 | inherits "2" 290 | minimatch "^3.0.2" 291 | once "^1.3.0" 292 | path-is-absolute "^1.0.0" 293 | 294 | har-schema@^1.0.5: 295 | version "1.0.5" 296 | resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e" 297 | 298 | har-validator@~4.2.1: 299 | version "4.2.1" 300 | resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a" 301 | dependencies: 302 | ajv "^4.9.1" 303 | har-schema "^1.0.5" 304 | 305 | has-ansi@^2.0.0: 306 | version "2.0.0" 307 | resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" 308 | dependencies: 309 | ansi-regex "^2.0.0" 310 | 311 | hawk@~3.1.3: 312 | version "3.1.3" 313 | resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4" 314 | dependencies: 315 | boom "2.x.x" 316 | cryptiles "2.x.x" 317 | hoek "2.x.x" 318 | sntp "1.x.x" 319 | 320 | hoek@2.x.x: 321 | version "2.16.3" 322 | resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" 323 | 324 | http-signature@~1.1.0: 325 | version "1.1.1" 326 | resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" 327 | dependencies: 328 | assert-plus "^0.2.0" 329 | jsprim "^1.2.2" 330 | sshpk "^1.7.0" 331 | 332 | inflight@^1.0.4: 333 | version "1.0.6" 334 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 335 | dependencies: 336 | once "^1.3.0" 337 | wrappy "1" 338 | 339 | <<<<<<< HEAD 340 | inherits@2, inherits@~2.0.1: 341 | ======= 342 | inherits@2: 343 | >>>>>>> 7b57e2ee584891409362b8499b7cf4c2d651aa53 344 | version "2.0.3" 345 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 346 | 347 | is-hex-prefixed@1.0.0: 348 | version "1.0.0" 349 | resolved "https://registry.yarnpkg.com/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz#7d8d37e6ad77e5d127148913c573e082d777f554" 350 | 351 | is-typedarray@~1.0.0: 352 | version "1.0.0" 353 | resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" 354 | 355 | <<<<<<< HEAD 356 | isarray@~1.0.0: 357 | version "1.0.0" 358 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" 359 | 360 | ======= 361 | >>>>>>> 7b57e2ee584891409362b8499b7cf4c2d651aa53 362 | isstream@~0.1.2: 363 | version "0.1.2" 364 | resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" 365 | 366 | jodid25519@^1.0.0: 367 | version "1.0.2" 368 | resolved "https://registry.yarnpkg.com/jodid25519/-/jodid25519-1.0.2.tgz#06d4912255093419477d425633606e0e90782967" 369 | dependencies: 370 | jsbn "~0.1.0" 371 | 372 | js-yaml@^3.7.0: 373 | version "3.8.1" 374 | resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.8.1.tgz#782ba50200be7b9e5a8537001b7804db3ad02628" 375 | dependencies: 376 | argparse "^1.0.7" 377 | esprima "^3.1.1" 378 | 379 | jsbn@~0.1.0: 380 | version "0.1.1" 381 | resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" 382 | 383 | json-schema@0.2.3: 384 | version "0.2.3" 385 | resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" 386 | 387 | json-stable-stringify@^1.0.1: 388 | version "1.0.1" 389 | resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" 390 | dependencies: 391 | jsonify "~0.0.0" 392 | 393 | json-stringify-safe@~5.0.1: 394 | version "5.0.1" 395 | resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" 396 | 397 | jsonify@~0.0.0: 398 | version "0.0.0" 399 | resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" 400 | 401 | jsprim@^1.2.2: 402 | version "1.4.0" 403 | resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.0.tgz#a3b87e40298d8c380552d8cc7628a0bb95a22918" 404 | dependencies: 405 | assert-plus "1.0.0" 406 | extsprintf "1.0.2" 407 | json-schema "0.2.3" 408 | verror "1.3.6" 409 | 410 | <<<<<<< HEAD 411 | lodash.assign@^4.2.0: 412 | version "4.2.0" 413 | resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" 414 | 415 | lodash.get@^4.4.2: 416 | version "4.4.2" 417 | resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" 418 | 419 | lodash.has@^4.5.2: 420 | version "4.5.2" 421 | resolved "https://registry.yarnpkg.com/lodash.has/-/lodash.has-4.5.2.tgz#d19f4dc1095058cccbe2b0cdf4ee0fe4aa37c862" 422 | 423 | lodash.uniq@^4.5.0: 424 | version "4.5.0" 425 | resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" 426 | 427 | ======= 428 | >>>>>>> 7b57e2ee584891409362b8499b7cf4c2d651aa53 429 | lodash@^4.13.1: 430 | version "4.17.4" 431 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" 432 | 433 | mime-db@~1.27.0: 434 | version "1.27.0" 435 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.27.0.tgz#820f572296bbd20ec25ed55e5b5de869e5436eb1" 436 | 437 | mime-types@^2.1.12, mime-types@~2.1.7: 438 | version "2.1.15" 439 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.15.tgz#a4ebf5064094569237b8cf70046776d09fc92aed" 440 | dependencies: 441 | mime-db "~1.27.0" 442 | 443 | minimatch@^3.0.2: 444 | version "3.0.3" 445 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.3.tgz#2a4e4090b96b2db06a9d7df01055a62a77c9b774" 446 | dependencies: 447 | brace-expansion "^1.0.0" 448 | 449 | minimist@0.0.8: 450 | version "0.0.8" 451 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" 452 | 453 | mkdirp@^0.5.1: 454 | version "0.5.1" 455 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" 456 | dependencies: 457 | minimist "0.0.8" 458 | 459 | nan@>=2.0.0: 460 | version "2.5.1" 461 | resolved "https://registry.yarnpkg.com/nan/-/nan-2.5.1.tgz#d5b01691253326a97a2bbee9e61c55d8d60351e2" 462 | 463 | number-to-bn@1.7.0, number-to-bn@^1.7.0: 464 | version "1.7.0" 465 | resolved "https://registry.yarnpkg.com/number-to-bn/-/number-to-bn-1.7.0.tgz#bb3623592f7e5f9e0030b1977bd41a0c53fe1ea0" 466 | dependencies: 467 | bn.js "4.11.6" 468 | strip-hex-prefix "1.0.0" 469 | 470 | oauth-sign@~0.8.1: 471 | version "0.8.2" 472 | resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" 473 | 474 | object-assign@4.1.0: 475 | version "4.1.0" 476 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.0.tgz#7a3b3d0e98063d43f4c03f2e8ae6cd51a86883a0" 477 | 478 | once@^1.3.0: 479 | version "1.4.0" 480 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 481 | dependencies: 482 | wrappy "1" 483 | 484 | packet-reader@0.2.0: 485 | version "0.2.0" 486 | resolved "https://registry.yarnpkg.com/packet-reader/-/packet-reader-0.2.0.tgz#819df4d010b82d5ea5671f8a1a3acf039bcd7700" 487 | 488 | path-is-absolute@^1.0.0: 489 | version "1.0.1" 490 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 491 | 492 | performance-now@^0.2.0: 493 | version "0.2.0" 494 | resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5" 495 | 496 | pg-connection-string@0.1.3: 497 | version "0.1.3" 498 | resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-0.1.3.tgz#da1847b20940e42ee1492beaf65d49d91b245df7" 499 | 500 | pg-pool@1.*: 501 | version "1.6.0" 502 | resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-1.6.0.tgz#2e300199927b6d7db6be71e2e3435dddddf07b41" 503 | dependencies: 504 | generic-pool "2.4.2" 505 | object-assign "4.1.0" 506 | 507 | pg-types@1.*: 508 | version "1.11.0" 509 | resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-1.11.0.tgz#aae91a82d952b633bb88d006350a166daaf6ea90" 510 | dependencies: 511 | ap "~0.2.0" 512 | postgres-array "~1.0.0" 513 | postgres-bytea "~1.0.0" 514 | postgres-date "~1.0.0" 515 | postgres-interval "~1.0.0" 516 | 517 | pg@^6.1.2: 518 | version "6.1.2" 519 | resolved "https://registry.yarnpkg.com/pg/-/pg-6.1.2.tgz#2c896a7434502e2b938c100fc085b4e974a186db" 520 | dependencies: 521 | buffer-writer "1.0.1" 522 | packet-reader "0.2.0" 523 | pg-connection-string "0.1.3" 524 | pg-pool "1.*" 525 | pg-types "1.*" 526 | pgpass "1.x" 527 | semver "4.3.2" 528 | 529 | pgpass@1.x: 530 | version "1.0.1" 531 | resolved "https://registry.yarnpkg.com/pgpass/-/pgpass-1.0.1.tgz#0de8b5bef993295d90a7e17d976f568dcd25d49f" 532 | dependencies: 533 | split "^1.0.0" 534 | 535 | postgres-array@~1.0.0: 536 | version "1.0.2" 537 | resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-1.0.2.tgz#8e0b32eb03bf77a5c0a7851e0441c169a256a238" 538 | 539 | postgres-bytea@~1.0.0: 540 | version "1.0.0" 541 | resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-1.0.0.tgz#027b533c0aa890e26d172d47cf9ccecc521acd35" 542 | 543 | postgres-date@~1.0.0: 544 | version "1.0.3" 545 | resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-1.0.3.tgz#e2d89702efdb258ff9d9cee0fe91bd06975257a8" 546 | 547 | postgres-interval@~1.0.0: 548 | version "1.0.2" 549 | resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.0.2.tgz#7261438d862b412921c6fdb7617668424b73a6ed" 550 | dependencies: 551 | xtend "^4.0.0" 552 | 553 | <<<<<<< HEAD 554 | process-nextick-args@~1.0.6: 555 | version "1.0.7" 556 | resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" 557 | 558 | ======= 559 | >>>>>>> 7b57e2ee584891409362b8499b7cf4c2d651aa53 560 | punycode@^1.4.1: 561 | version "1.4.1" 562 | resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" 563 | 564 | qs@~6.4.0: 565 | version "6.4.0" 566 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" 567 | 568 | <<<<<<< HEAD 569 | readable-stream@^2.2.2: 570 | version "2.2.6" 571 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.2.6.tgz#8b43aed76e71483938d12a8d46c6cf1a00b1f816" 572 | dependencies: 573 | buffer-shims "^1.0.0" 574 | core-util-is "~1.0.0" 575 | inherits "~2.0.1" 576 | isarray "~1.0.0" 577 | process-nextick-args "~1.0.6" 578 | string_decoder "~0.10.x" 579 | util-deprecate "~1.0.1" 580 | 581 | ======= 582 | >>>>>>> 7b57e2ee584891409362b8499b7cf4c2d651aa53 583 | redis-commands@^1.2.0: 584 | version "1.3.1" 585 | resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.3.1.tgz#81d826f45fa9c8b2011f4cd7a0fe597d241d442b" 586 | 587 | redis-parser@^2.0.0: 588 | version "2.4.0" 589 | resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-2.4.0.tgz#018ea743077aae944d0b798b2fd12587320bf3c9" 590 | 591 | redis@^2.6.5: 592 | version "2.6.5" 593 | resolved "https://registry.yarnpkg.com/redis/-/redis-2.6.5.tgz#87c1eff4a489f94b70871f3d08b6988f23a95687" 594 | dependencies: 595 | double-ended-queue "^2.1.0-0" 596 | redis-commands "^1.2.0" 597 | redis-parser "^2.0.0" 598 | 599 | request-promise-core@1.1.1: 600 | version "1.1.1" 601 | resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.1.tgz#3eee00b2c5aa83239cfb04c5700da36f81cd08b6" 602 | dependencies: 603 | lodash "^4.13.1" 604 | 605 | request-promise-native@^1.0.3: 606 | version "1.0.3" 607 | resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.3.tgz#9cb2b2f69f137e4acf35116a08a441cbfd0c0134" 608 | dependencies: 609 | request-promise-core "1.1.1" 610 | stealthy-require "^1.0.0" 611 | 612 | request@^2.81.0: 613 | version "2.81.0" 614 | resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" 615 | dependencies: 616 | aws-sign2 "~0.6.0" 617 | aws4 "^1.2.1" 618 | caseless "~0.12.0" 619 | combined-stream "~1.0.5" 620 | extend "~3.0.0" 621 | forever-agent "~0.6.1" 622 | form-data "~2.1.1" 623 | har-validator "~4.2.1" 624 | hawk "~3.1.3" 625 | http-signature "~1.1.0" 626 | is-typedarray "~1.0.0" 627 | isstream "~0.1.2" 628 | json-stringify-safe "~5.0.1" 629 | mime-types "~2.1.7" 630 | oauth-sign "~0.8.1" 631 | performance-now "^0.2.0" 632 | qs "~6.4.0" 633 | safe-buffer "^5.0.1" 634 | stringstream "~0.0.4" 635 | tough-cookie "~2.3.0" 636 | tunnel-agent "^0.6.0" 637 | uuid "^3.0.0" 638 | 639 | safe-buffer@^5.0.1: 640 | version "5.0.1" 641 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.0.1.tgz#d263ca54696cd8a306b5ca6551e92de57918fbe7" 642 | 643 | <<<<<<< HEAD 644 | sax@^1.2.1: 645 | version "1.2.2" 646 | resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.2.tgz#fd8631a23bc7826bef5d871bdb87378c95647828" 647 | 648 | ======= 649 | >>>>>>> 7b57e2ee584891409362b8499b7cf4c2d651aa53 650 | semver@4.3.2: 651 | version "4.3.2" 652 | resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.2.tgz#c7a07158a80bedd052355b770d82d6640f803be7" 653 | 654 | sleep@^5.1.0: 655 | version "5.1.0" 656 | resolved "https://registry.yarnpkg.com/sleep/-/sleep-5.1.0.tgz#0e0b16205d9f9e3b0a95357744b103e797b4c7c1" 657 | dependencies: 658 | nan ">=2.0.0" 659 | 660 | sntp@1.x.x: 661 | version "1.0.9" 662 | resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198" 663 | dependencies: 664 | hoek "2.x.x" 665 | 666 | sofa-js@sofaprotocol/sofa-js#34c6aa7: 667 | version "0.1.0" 668 | resolved "https://codeload.github.com/sofaprotocol/sofa-js/tar.gz/34c6aa7" 669 | dependencies: 670 | ajv "^4.11.3" 671 | ethjs-unit "^0.1.6" 672 | glob "^7.1.1" 673 | number-to-bn "^1.7.0" 674 | 675 | split@^1.0.0: 676 | version "1.0.0" 677 | resolved "https://registry.yarnpkg.com/split/-/split-1.0.0.tgz#c4395ce683abcd254bc28fe1dabb6e5c27dcffae" 678 | dependencies: 679 | through "2" 680 | 681 | sprintf-js@~1.0.2: 682 | version "1.0.3" 683 | resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" 684 | 685 | sshpk@^1.7.0: 686 | version "1.11.0" 687 | resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.11.0.tgz#2d8d5ebb4a6fab28ffba37fa62a90f4a3ea59d77" 688 | dependencies: 689 | asn1 "~0.2.3" 690 | assert-plus "^1.0.0" 691 | dashdash "^1.12.0" 692 | getpass "^0.1.1" 693 | optionalDependencies: 694 | bcrypt-pbkdf "^1.0.0" 695 | ecc-jsbn "~0.1.1" 696 | jodid25519 "^1.0.0" 697 | jsbn "~0.1.0" 698 | tweetnacl "~0.14.0" 699 | 700 | stealthy-require@^1.0.0: 701 | version "1.0.0" 702 | resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.0.0.tgz#1a8ed8fc19a8b56268f76f5a1a3e3832b0c26200" 703 | 704 | <<<<<<< HEAD 705 | string_decoder@~0.10.x: 706 | version "0.10.31" 707 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" 708 | 709 | ======= 710 | >>>>>>> 7b57e2ee584891409362b8499b7cf4c2d651aa53 711 | stringstream@~0.0.4: 712 | version "0.0.5" 713 | resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878" 714 | 715 | strip-ansi@^3.0.0: 716 | version "3.0.1" 717 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" 718 | dependencies: 719 | ansi-regex "^2.0.0" 720 | 721 | strip-hex-prefix@1.0.0: 722 | version "1.0.0" 723 | resolved "https://registry.yarnpkg.com/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz#0c5f155fef1151373377de9dbb588da05500e36f" 724 | dependencies: 725 | is-hex-prefixed "1.0.0" 726 | 727 | supports-color@^2.0.0: 728 | version "2.0.0" 729 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" 730 | 731 | through@2: 732 | version "2.3.8" 733 | resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" 734 | 735 | tough-cookie@~2.3.0: 736 | version "2.3.2" 737 | resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.2.tgz#f081f76e4c85720e6c37a5faced737150d84072a" 738 | dependencies: 739 | punycode "^1.4.1" 740 | 741 | tunnel-agent@^0.6.0: 742 | version "0.6.0" 743 | resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" 744 | dependencies: 745 | safe-buffer "^5.0.1" 746 | 747 | tweetnacl@^0.14.3, tweetnacl@~0.14.0: 748 | version "0.14.5" 749 | resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" 750 | 751 | <<<<<<< HEAD 752 | util-deprecate@~1.0.1: 753 | version "1.0.2" 754 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 755 | 756 | ======= 757 | >>>>>>> 7b57e2ee584891409362b8499b7cf4c2d651aa53 758 | uuid@^3.0.0: 759 | version "3.0.1" 760 | resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.1.tgz#6544bba2dfda8c1cf17e629a3a305e2bb1fee6c1" 761 | 762 | verror@1.3.6: 763 | version "1.3.6" 764 | resolved "https://registry.yarnpkg.com/verror/-/verror-1.3.6.tgz#cff5df12946d297d2baaefaa2689e25be01c005c" 765 | dependencies: 766 | extsprintf "1.0.2" 767 | 768 | word-wrap@^1.2.1: 769 | version "1.2.1" 770 | resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.1.tgz#248f459b465d179a17bc407c854d3151d07e45d8" 771 | 772 | wrappy@1: 773 | version "1.0.2" 774 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 775 | 776 | xtend@^4.0.0: 777 | version "4.0.1" 778 | resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" 779 | 780 | Trace: 781 | SyntaxError: Unknown token 5:1 782 | at Parser.unexpected (/usr/local/Cellar/yarn/0.20.3/libexec/lib/node_modules/yarn/lib/lockfile/parse.js:224:11) 783 | at Parser.parse (/usr/local/Cellar/yarn/0.20.3/libexec/lib/node_modules/yarn/lib/lockfile/parse.js:329:14) 784 | at exports.default (/usr/local/Cellar/yarn/0.20.3/libexec/lib/node_modules/yarn/lib/lockfile/parse.js:13:17) 785 | at /usr/local/Cellar/yarn/0.20.3/libexec/lib/node_modules/yarn/lib/lockfile/wrapper.js:129:60 786 | at Generator.next () 787 | at step (/usr/local/Cellar/yarn/0.20.3/libexec/lib/node_modules/yarn/node_modules/babel-runtime/helpers/asyncToGenerator.js:17:30) 788 | at /usr/local/Cellar/yarn/0.20.3/libexec/lib/node_modules/yarn/node_modules/babel-runtime/helpers/asyncToGenerator.js:28:13 789 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | addressparser@^1.0.1: 6 | version "1.0.1" 7 | resolved "https://registry.yarnpkg.com/addressparser/-/addressparser-1.0.1.tgz#47afbe1a2a9262191db6838e4fd1d39b40821746" 8 | 9 | ajv@^4.11.3, ajv@^4.9.1: 10 | version "4.11.5" 11 | resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.5.tgz#b6ee74657b993a01dce44b7944d56f485828d5bd" 12 | dependencies: 13 | co "^4.6.0" 14 | json-stable-stringify "^1.0.1" 15 | 16 | ansi-regex@^2.0.0: 17 | version "2.1.1" 18 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" 19 | 20 | ansi-styles@^2.2.1: 21 | version "2.2.1" 22 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" 23 | 24 | ap@~0.2.0: 25 | version "0.2.0" 26 | resolved "https://registry.yarnpkg.com/ap/-/ap-0.2.0.tgz#ae0942600b29912f0d2b14ec60c45e8f330b6110" 27 | 28 | argparse@^1.0.7: 29 | version "1.0.9" 30 | resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86" 31 | dependencies: 32 | sprintf-js "~1.0.2" 33 | 34 | array-indexofobject@~0.0.1: 35 | version "0.0.1" 36 | resolved "https://registry.yarnpkg.com/array-indexofobject/-/array-indexofobject-0.0.1.tgz#aaa128e62c9b3c358094568c219ff64fe489d42a" 37 | 38 | asn1@~0.2.3: 39 | version "0.2.3" 40 | resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86" 41 | 42 | assert-plus@1.0.0, assert-plus@^1.0.0: 43 | version "1.0.0" 44 | resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" 45 | 46 | assert-plus@^0.2.0: 47 | version "0.2.0" 48 | resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234" 49 | 50 | asynckit@^0.4.0: 51 | version "0.4.0" 52 | resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" 53 | 54 | aws-sign2@~0.6.0: 55 | version "0.6.0" 56 | resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f" 57 | 58 | aws4@^1.2.1: 59 | version "1.6.0" 60 | resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" 61 | 62 | balanced-match@^0.4.1: 63 | version "0.4.2" 64 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838" 65 | 66 | bcrypt-pbkdf@^1.0.0: 67 | version "1.0.1" 68 | resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d" 69 | dependencies: 70 | tweetnacl "^0.14.3" 71 | 72 | bn.js@4.11.6: 73 | version "4.11.6" 74 | resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.6.tgz#53344adb14617a13f6e8dd2ce28905d1c0ba3215" 75 | 76 | boom@2.x.x: 77 | version "2.10.1" 78 | resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f" 79 | dependencies: 80 | hoek "2.x.x" 81 | 82 | brace-expansion@^1.0.0: 83 | version "1.1.6" 84 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.6.tgz#7197d7eaa9b87e648390ea61fc66c84427420df9" 85 | dependencies: 86 | balanced-match "^0.4.1" 87 | concat-map "0.0.1" 88 | 89 | buffer-shims@^1.0.0: 90 | version "1.0.0" 91 | resolved "https://registry.yarnpkg.com/buffer-shims/-/buffer-shims-1.0.0.tgz#9978ce317388c649ad8793028c3477ef044a8b51" 92 | 93 | buffer-writer@1.0.1: 94 | version "1.0.1" 95 | resolved "https://registry.yarnpkg.com/buffer-writer/-/buffer-writer-1.0.1.tgz#22a936901e3029afcd7547eb4487ceb697a3bf08" 96 | 97 | caseless@~0.12.0: 98 | version "0.12.0" 99 | resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" 100 | 101 | chalk@^1.1.3: 102 | version "1.1.3" 103 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" 104 | dependencies: 105 | ansi-styles "^2.2.1" 106 | escape-string-regexp "^1.0.2" 107 | has-ansi "^2.0.0" 108 | strip-ansi "^3.0.0" 109 | supports-color "^2.0.0" 110 | 111 | co@^4.6.0: 112 | version "4.6.0" 113 | resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" 114 | 115 | combined-stream@^1.0.5, combined-stream@~1.0.5: 116 | version "1.0.5" 117 | resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009" 118 | dependencies: 119 | delayed-stream "~1.0.0" 120 | 121 | concat-map@0.0.1: 122 | version "0.0.1" 123 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 124 | 125 | core-util-is@~1.0.0: 126 | version "1.0.2" 127 | resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" 128 | 129 | cryptiles@2.x.x: 130 | version "2.0.5" 131 | resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" 132 | dependencies: 133 | boom "2.x.x" 134 | 135 | dashdash@^1.12.0: 136 | version "1.14.1" 137 | resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" 138 | dependencies: 139 | assert-plus "^1.0.0" 140 | 141 | delayed-stream@~1.0.0: 142 | version "1.0.0" 143 | resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" 144 | 145 | double-ended-queue@^2.1.0-0: 146 | version "2.1.0-0" 147 | resolved "https://registry.yarnpkg.com/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz#103d3527fd31528f40188130c841efdd78264e5c" 148 | 149 | ecc-jsbn@~0.1.1: 150 | version "0.1.1" 151 | resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505" 152 | dependencies: 153 | jsbn "~0.1.0" 154 | 155 | escape-string-regexp@^1.0.2: 156 | version "1.0.5" 157 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 158 | 159 | esprima@^3.1.1: 160 | version "3.1.3" 161 | resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" 162 | 163 | ethjs-unit@^0.1.6: 164 | version "0.1.6" 165 | resolved "https://registry.yarnpkg.com/ethjs-unit/-/ethjs-unit-0.1.6.tgz#c665921e476e87bce2a9d588a6fe0405b2c41699" 166 | dependencies: 167 | bn.js "4.11.6" 168 | number-to-bn "1.7.0" 169 | 170 | extend@~3.0.0: 171 | version "3.0.0" 172 | resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.0.tgz#5a474353b9f3353ddd8176dfd37b91c83a46f1d4" 173 | 174 | extsprintf@1.0.2: 175 | version "1.0.2" 176 | resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.0.2.tgz#e1080e0658e300b06294990cc70e1502235fd550" 177 | 178 | feedparser@^2.1.0: 179 | version "2.1.0" 180 | resolved "https://registry.yarnpkg.com/feedparser/-/feedparser-2.1.0.tgz#4cd98bf04e18db5b8644f91e98da89dd179f1fe7" 181 | dependencies: 182 | addressparser "^1.0.1" 183 | array-indexofobject "~0.0.1" 184 | lodash.assign "^4.2.0" 185 | lodash.get "^4.4.2" 186 | lodash.has "^4.5.2" 187 | lodash.uniq "^4.5.0" 188 | readable-stream "^2.2.2" 189 | sax "^1.2.1" 190 | 191 | forever-agent@~0.6.1: 192 | version "0.6.1" 193 | resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" 194 | 195 | form-data@~2.1.1: 196 | version "2.1.2" 197 | resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.2.tgz#89c3534008b97eada4cbb157d58f6f5df025eae4" 198 | dependencies: 199 | asynckit "^0.4.0" 200 | combined-stream "^1.0.5" 201 | mime-types "^2.1.12" 202 | 203 | fs.realpath@^1.0.0: 204 | version "1.0.0" 205 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 206 | 207 | generic-pool@2.4.2: 208 | version "2.4.2" 209 | resolved "https://registry.yarnpkg.com/generic-pool/-/generic-pool-2.4.2.tgz#886bc5bf0beb7db96e81bcbba078818de5a62683" 210 | 211 | getpass@^0.1.1: 212 | version "0.1.6" 213 | resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.6.tgz#283ffd9fc1256840875311c1b60e8c40187110e6" 214 | dependencies: 215 | assert-plus "^1.0.0" 216 | 217 | glob@^7.1.1: 218 | version "7.1.1" 219 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8" 220 | dependencies: 221 | fs.realpath "^1.0.0" 222 | inflight "^1.0.4" 223 | inherits "2" 224 | minimatch "^3.0.2" 225 | once "^1.3.0" 226 | path-is-absolute "^1.0.0" 227 | 228 | har-schema@^1.0.5: 229 | version "1.0.5" 230 | resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e" 231 | 232 | har-validator@~4.2.1: 233 | version "4.2.1" 234 | resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a" 235 | dependencies: 236 | ajv "^4.9.1" 237 | har-schema "^1.0.5" 238 | 239 | has-ansi@^2.0.0: 240 | version "2.0.0" 241 | resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" 242 | dependencies: 243 | ansi-regex "^2.0.0" 244 | 245 | hawk@~3.1.3: 246 | version "3.1.3" 247 | resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4" 248 | dependencies: 249 | boom "2.x.x" 250 | cryptiles "2.x.x" 251 | hoek "2.x.x" 252 | sntp "1.x.x" 253 | 254 | hoek@2.x.x: 255 | version "2.16.3" 256 | resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" 257 | 258 | http-signature@~1.1.0: 259 | version "1.1.1" 260 | resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" 261 | dependencies: 262 | assert-plus "^0.2.0" 263 | jsprim "^1.2.2" 264 | sshpk "^1.7.0" 265 | 266 | inflight@^1.0.4: 267 | version "1.0.6" 268 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 269 | dependencies: 270 | once "^1.3.0" 271 | wrappy "1" 272 | 273 | inherits@2, inherits@~2.0.1: 274 | version "2.0.3" 275 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 276 | 277 | is-hex-prefixed@1.0.0: 278 | version "1.0.0" 279 | resolved "https://registry.yarnpkg.com/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz#7d8d37e6ad77e5d127148913c573e082d777f554" 280 | 281 | is-typedarray@~1.0.0: 282 | version "1.0.0" 283 | resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" 284 | 285 | isarray@~1.0.0: 286 | version "1.0.0" 287 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" 288 | 289 | isstream@~0.1.2: 290 | version "0.1.2" 291 | resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" 292 | 293 | jodid25519@^1.0.0: 294 | version "1.0.2" 295 | resolved "https://registry.yarnpkg.com/jodid25519/-/jodid25519-1.0.2.tgz#06d4912255093419477d425633606e0e90782967" 296 | dependencies: 297 | jsbn "~0.1.0" 298 | 299 | js-yaml@^3.7.0: 300 | version "3.8.1" 301 | resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.8.1.tgz#782ba50200be7b9e5a8537001b7804db3ad02628" 302 | dependencies: 303 | argparse "^1.0.7" 304 | esprima "^3.1.1" 305 | 306 | jsbn@~0.1.0: 307 | version "0.1.1" 308 | resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" 309 | 310 | json-schema@0.2.3: 311 | version "0.2.3" 312 | resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" 313 | 314 | json-stable-stringify@^1.0.1: 315 | version "1.0.1" 316 | resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" 317 | dependencies: 318 | jsonify "~0.0.0" 319 | 320 | json-stringify-safe@~5.0.1: 321 | version "5.0.1" 322 | resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" 323 | 324 | jsonify@~0.0.0: 325 | version "0.0.0" 326 | resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" 327 | 328 | jsprim@^1.2.2: 329 | version "1.4.0" 330 | resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.0.tgz#a3b87e40298d8c380552d8cc7628a0bb95a22918" 331 | dependencies: 332 | assert-plus "1.0.0" 333 | extsprintf "1.0.2" 334 | json-schema "0.2.3" 335 | verror "1.3.6" 336 | 337 | lodash.assign@^4.2.0: 338 | version "4.2.0" 339 | resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" 340 | 341 | lodash.get@^4.4.2: 342 | version "4.4.2" 343 | resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" 344 | 345 | lodash.has@^4.5.2: 346 | version "4.5.2" 347 | resolved "https://registry.yarnpkg.com/lodash.has/-/lodash.has-4.5.2.tgz#d19f4dc1095058cccbe2b0cdf4ee0fe4aa37c862" 348 | 349 | lodash.uniq@^4.5.0: 350 | version "4.5.0" 351 | resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" 352 | 353 | lodash@^4.13.1: 354 | version "4.17.4" 355 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" 356 | 357 | mime-db@~1.27.0: 358 | version "1.27.0" 359 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.27.0.tgz#820f572296bbd20ec25ed55e5b5de869e5436eb1" 360 | 361 | mime-types@^2.1.12, mime-types@~2.1.7: 362 | version "2.1.15" 363 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.15.tgz#a4ebf5064094569237b8cf70046776d09fc92aed" 364 | dependencies: 365 | mime-db "~1.27.0" 366 | 367 | minimatch@^3.0.2: 368 | version "3.0.3" 369 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.3.tgz#2a4e4090b96b2db06a9d7df01055a62a77c9b774" 370 | dependencies: 371 | brace-expansion "^1.0.0" 372 | 373 | minimist@0.0.8: 374 | version "0.0.8" 375 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" 376 | 377 | mkdirp@^0.5.1: 378 | version "0.5.1" 379 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" 380 | dependencies: 381 | minimist "0.0.8" 382 | 383 | nan@>=2.0.0: 384 | version "2.5.1" 385 | resolved "https://registry.yarnpkg.com/nan/-/nan-2.5.1.tgz#d5b01691253326a97a2bbee9e61c55d8d60351e2" 386 | 387 | number-to-bn@1.7.0, number-to-bn@^1.7.0: 388 | version "1.7.0" 389 | resolved "https://registry.yarnpkg.com/number-to-bn/-/number-to-bn-1.7.0.tgz#bb3623592f7e5f9e0030b1977bd41a0c53fe1ea0" 390 | dependencies: 391 | bn.js "4.11.6" 392 | strip-hex-prefix "1.0.0" 393 | 394 | oauth-sign@~0.8.1: 395 | version "0.8.2" 396 | resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" 397 | 398 | object-assign@4.1.0: 399 | version "4.1.0" 400 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.0.tgz#7a3b3d0e98063d43f4c03f2e8ae6cd51a86883a0" 401 | 402 | once@^1.3.0: 403 | version "1.4.0" 404 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 405 | dependencies: 406 | wrappy "1" 407 | 408 | packet-reader@0.2.0: 409 | version "0.2.0" 410 | resolved "https://registry.yarnpkg.com/packet-reader/-/packet-reader-0.2.0.tgz#819df4d010b82d5ea5671f8a1a3acf039bcd7700" 411 | 412 | path-is-absolute@^1.0.0: 413 | version "1.0.1" 414 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 415 | 416 | performance-now@^0.2.0: 417 | version "0.2.0" 418 | resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5" 419 | 420 | pg-connection-string@0.1.3: 421 | version "0.1.3" 422 | resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-0.1.3.tgz#da1847b20940e42ee1492beaf65d49d91b245df7" 423 | 424 | pg-pool@1.*: 425 | version "1.6.0" 426 | resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-1.6.0.tgz#2e300199927b6d7db6be71e2e3435dddddf07b41" 427 | dependencies: 428 | generic-pool "2.4.2" 429 | object-assign "4.1.0" 430 | 431 | pg-types@1.*: 432 | version "1.11.0" 433 | resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-1.11.0.tgz#aae91a82d952b633bb88d006350a166daaf6ea90" 434 | dependencies: 435 | ap "~0.2.0" 436 | postgres-array "~1.0.0" 437 | postgres-bytea "~1.0.0" 438 | postgres-date "~1.0.0" 439 | postgres-interval "~1.0.0" 440 | 441 | pg@^6.1.2: 442 | version "6.1.2" 443 | resolved "https://registry.yarnpkg.com/pg/-/pg-6.1.2.tgz#2c896a7434502e2b938c100fc085b4e974a186db" 444 | dependencies: 445 | buffer-writer "1.0.1" 446 | packet-reader "0.2.0" 447 | pg-connection-string "0.1.3" 448 | pg-pool "1.*" 449 | pg-types "1.*" 450 | pgpass "1.x" 451 | semver "4.3.2" 452 | 453 | pgpass@1.x: 454 | version "1.0.1" 455 | resolved "https://registry.yarnpkg.com/pgpass/-/pgpass-1.0.1.tgz#0de8b5bef993295d90a7e17d976f568dcd25d49f" 456 | dependencies: 457 | split "^1.0.0" 458 | 459 | postgres-array@~1.0.0: 460 | version "1.0.2" 461 | resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-1.0.2.tgz#8e0b32eb03bf77a5c0a7851e0441c169a256a238" 462 | 463 | postgres-bytea@~1.0.0: 464 | version "1.0.0" 465 | resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-1.0.0.tgz#027b533c0aa890e26d172d47cf9ccecc521acd35" 466 | 467 | postgres-date@~1.0.0: 468 | version "1.0.3" 469 | resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-1.0.3.tgz#e2d89702efdb258ff9d9cee0fe91bd06975257a8" 470 | 471 | postgres-interval@~1.0.0: 472 | version "1.0.2" 473 | resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.0.2.tgz#7261438d862b412921c6fdb7617668424b73a6ed" 474 | dependencies: 475 | xtend "^4.0.0" 476 | 477 | process-nextick-args@~1.0.6: 478 | version "1.0.7" 479 | resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" 480 | 481 | punycode@^1.4.1: 482 | version "1.4.1" 483 | resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" 484 | 485 | qs@~6.4.0: 486 | version "6.4.0" 487 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" 488 | 489 | readable-stream@^2.2.2: 490 | version "2.2.6" 491 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.2.6.tgz#8b43aed76e71483938d12a8d46c6cf1a00b1f816" 492 | dependencies: 493 | buffer-shims "^1.0.0" 494 | core-util-is "~1.0.0" 495 | inherits "~2.0.1" 496 | isarray "~1.0.0" 497 | process-nextick-args "~1.0.6" 498 | string_decoder "~0.10.x" 499 | util-deprecate "~1.0.1" 500 | 501 | redis-commands@^1.2.0: 502 | version "1.3.1" 503 | resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.3.1.tgz#81d826f45fa9c8b2011f4cd7a0fe597d241d442b" 504 | 505 | redis-parser@^2.0.0: 506 | version "2.4.0" 507 | resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-2.4.0.tgz#018ea743077aae944d0b798b2fd12587320bf3c9" 508 | 509 | redis@^2.6.5: 510 | version "2.6.5" 511 | resolved "https://registry.yarnpkg.com/redis/-/redis-2.6.5.tgz#87c1eff4a489f94b70871f3d08b6988f23a95687" 512 | dependencies: 513 | double-ended-queue "^2.1.0-0" 514 | redis-commands "^1.2.0" 515 | redis-parser "^2.0.0" 516 | 517 | request-promise-core@1.1.1: 518 | version "1.1.1" 519 | resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.1.tgz#3eee00b2c5aa83239cfb04c5700da36f81cd08b6" 520 | dependencies: 521 | lodash "^4.13.1" 522 | 523 | request-promise-native@^1.0.3: 524 | version "1.0.3" 525 | resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.3.tgz#9cb2b2f69f137e4acf35116a08a441cbfd0c0134" 526 | dependencies: 527 | request-promise-core "1.1.1" 528 | stealthy-require "^1.0.0" 529 | 530 | request@^2.81.0: 531 | version "2.81.0" 532 | resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" 533 | dependencies: 534 | aws-sign2 "~0.6.0" 535 | aws4 "^1.2.1" 536 | caseless "~0.12.0" 537 | combined-stream "~1.0.5" 538 | extend "~3.0.0" 539 | forever-agent "~0.6.1" 540 | form-data "~2.1.1" 541 | har-validator "~4.2.1" 542 | hawk "~3.1.3" 543 | http-signature "~1.1.0" 544 | is-typedarray "~1.0.0" 545 | isstream "~0.1.2" 546 | json-stringify-safe "~5.0.1" 547 | mime-types "~2.1.7" 548 | oauth-sign "~0.8.1" 549 | performance-now "^0.2.0" 550 | qs "~6.4.0" 551 | safe-buffer "^5.0.1" 552 | stringstream "~0.0.4" 553 | tough-cookie "~2.3.0" 554 | tunnel-agent "^0.6.0" 555 | uuid "^3.0.0" 556 | 557 | safe-buffer@^5.0.1: 558 | version "5.0.1" 559 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.0.1.tgz#d263ca54696cd8a306b5ca6551e92de57918fbe7" 560 | 561 | sax@^1.2.1: 562 | version "1.2.2" 563 | resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.2.tgz#fd8631a23bc7826bef5d871bdb87378c95647828" 564 | 565 | semver@4.3.2: 566 | version "4.3.2" 567 | resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.2.tgz#c7a07158a80bedd052355b770d82d6640f803be7" 568 | 569 | sleep@^5.1.0: 570 | version "5.1.0" 571 | resolved "https://registry.yarnpkg.com/sleep/-/sleep-5.1.0.tgz#0e0b16205d9f9e3b0a95357744b103e797b4c7c1" 572 | dependencies: 573 | nan ">=2.0.0" 574 | 575 | sntp@1.x.x: 576 | version "1.0.9" 577 | resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198" 578 | dependencies: 579 | hoek "2.x.x" 580 | 581 | sofa-js@sofaprotocol/sofa-js#34c6aa7: 582 | version "0.1.0" 583 | resolved "https://codeload.github.com/sofaprotocol/sofa-js/tar.gz/34c6aa7" 584 | dependencies: 585 | ajv "^4.11.3" 586 | ethjs-unit "^0.1.6" 587 | glob "^7.1.1" 588 | number-to-bn "^1.7.0" 589 | 590 | split@^1.0.0: 591 | version "1.0.0" 592 | resolved "https://registry.yarnpkg.com/split/-/split-1.0.0.tgz#c4395ce683abcd254bc28fe1dabb6e5c27dcffae" 593 | dependencies: 594 | through "2" 595 | 596 | sprintf-js@~1.0.2: 597 | version "1.0.3" 598 | resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" 599 | 600 | sshpk@^1.7.0: 601 | version "1.11.0" 602 | resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.11.0.tgz#2d8d5ebb4a6fab28ffba37fa62a90f4a3ea59d77" 603 | dependencies: 604 | asn1 "~0.2.3" 605 | assert-plus "^1.0.0" 606 | dashdash "^1.12.0" 607 | getpass "^0.1.1" 608 | optionalDependencies: 609 | bcrypt-pbkdf "^1.0.0" 610 | ecc-jsbn "~0.1.1" 611 | jodid25519 "^1.0.0" 612 | jsbn "~0.1.0" 613 | tweetnacl "~0.14.0" 614 | 615 | stealthy-require@^1.0.0: 616 | version "1.0.0" 617 | resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.0.0.tgz#1a8ed8fc19a8b56268f76f5a1a3e3832b0c26200" 618 | 619 | string_decoder@~0.10.x: 620 | version "0.10.31" 621 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" 622 | 623 | stringstream@~0.0.4: 624 | version "0.0.5" 625 | resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878" 626 | 627 | strip-ansi@^3.0.0: 628 | version "3.0.1" 629 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" 630 | dependencies: 631 | ansi-regex "^2.0.0" 632 | 633 | strip-hex-prefix@1.0.0: 634 | version "1.0.0" 635 | resolved "https://registry.yarnpkg.com/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz#0c5f155fef1151373377de9dbb588da05500e36f" 636 | dependencies: 637 | is-hex-prefixed "1.0.0" 638 | 639 | supports-color@^2.0.0: 640 | version "2.0.0" 641 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" 642 | 643 | through@2: 644 | version "2.3.8" 645 | resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" 646 | 647 | tough-cookie@~2.3.0: 648 | version "2.3.2" 649 | resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.2.tgz#f081f76e4c85720e6c37a5faced737150d84072a" 650 | dependencies: 651 | punycode "^1.4.1" 652 | 653 | tunnel-agent@^0.6.0: 654 | version "0.6.0" 655 | resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" 656 | dependencies: 657 | safe-buffer "^5.0.1" 658 | 659 | tweetnacl@^0.14.3, tweetnacl@~0.14.0: 660 | version "0.14.5" 661 | resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" 662 | 663 | util-deprecate@~1.0.1: 664 | version "1.0.2" 665 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 666 | 667 | uuid@^3.0.0: 668 | version "3.0.1" 669 | resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.1.tgz#6544bba2dfda8c1cf17e629a3a305e2bb1fee6c1" 670 | 671 | verror@1.3.6: 672 | version "1.3.6" 673 | resolved "https://registry.yarnpkg.com/verror/-/verror-1.3.6.tgz#cff5df12946d297d2baaefaa2689e25be01c005c" 674 | dependencies: 675 | extsprintf "1.0.2" 676 | 677 | word-wrap@^1.2.1: 678 | version "1.2.1" 679 | resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.1.tgz#248f459b465d179a17bc407c854d3151d07e45d8" 680 | 681 | wrappy@1: 682 | version "1.0.2" 683 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 684 | 685 | xtend@^4.0.0: 686 | version "4.0.1" 687 | resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" 688 | --------------------------------------------------------------------------------