├── .dockerignore ├── .editorconfig ├── .gitignore ├── Dockerfile ├── Makefile ├── README.md ├── bin └── cli.js ├── docs └── main.png ├── index.js ├── kubernetes ├── redis-cluster-ip.yml └── redis-deployment.yml ├── manager ├── Dockerfile ├── kubernetes │ ├── server-deployment.yml │ └── server-load-balancer-service.yml └── pkg │ ├── config.go │ ├── config │ └── config.go │ ├── kubernetes │ ├── client.go │ └── watchtower_service.go │ ├── server │ ├── index.go │ ├── response.go │ ├── server.go │ └── watchtower_router.go │ └── watchtower.go ├── package.json ├── src ├── clients │ ├── redis.js │ ├── twilio.js │ └── twitter.js ├── utils.js └── watchtower.js └── yarn.lock /.dockerignore: -------------------------------------------------------------------------------- 1 | manager 2 | node_modules 3 | configmap.yml 4 | .env 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | charset = utf-8 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | indent_style = tab 9 | indent_size = 2 10 | 11 | [*.{diff,md}] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_STORE 2 | app 3 | .env 4 | node_modules 5 | configmap.yml 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mhart/alpine-node:11 2 | 3 | WORKDIR /app 4 | COPY package.json /app 5 | RUN npm install 6 | COPY . /app 7 | CMD node ./bin/cli 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: install test build serve clean pack deploy ship 2 | 3 | TAG?=$(shell git rev-list HEAD --max-count=1 --abbrev-commit) 4 | 5 | export TAG 6 | 7 | install: 8 | go get ./manager/cmd/app 9 | 10 | test: install 11 | go test ./... 12 | 13 | build: install 14 | go build -ldflags "-X main.version=$(TAG)" -o manager/app manager/cmd/app/* 15 | 16 | serve: build 17 | ./app 18 | 19 | clean: 20 | rm ./manager/app 21 | 22 | clean-watchtowers: 23 | kubectl delete deployment -n watchtower --selector=app=watchtower 24 | 25 | pack: 26 | GOOS=linux make build 27 | docker build -t mattgarnett/auto-k8s:$(TAG) manager 28 | 29 | upload: 30 | docker push mattgarnett/auto-k8s:$(TAG) 31 | 32 | deploy: 33 | kubectl delete deployment -n watchtower server-deployment 34 | sed 's/REPLACE_WITH_TAG/$(TAG)/' manager/kubernetes/server-deployment.yml | kubectl apply -f - 35 | kubectl apply -f manager/kubernetes/server-load-balancer-service.yml 36 | 37 | ship: pack upload deploy clean 38 | 39 | 40 | build-watchtower: 41 | docker build -t mattgarnett/watchtower:$(TAG) . 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Watchtower 2 | 3 | Watchtower is a monitoring solution for [Plasma](https://medium.com/@argongroup/ethereum-plasma-explained-608720d3c60e) that is able to notify registered users of malicious activity occuring on the chain in real time. 4 | 5 | *We built our project around [Plasma Group](https://plasma.group)'s implementation of Plasma Cash.* 6 | 7 | ### Forks and other related repos: 8 | * https://github.com/villanuevawill/plasma-chain-operator 9 | * https://github.com/villanuevawill/plasma-core 10 | * https://github.com/villanuevawill/plasma-client 11 | * https://github.com/villanuevawill/plasma-js-lib 12 | * https://github.com/c-o-l-o-r/standalone-tor-proxy 13 | 14 | ![Watchtower screenshot](docs/main.png?raw=true) 15 | -------------------------------------------------------------------------------- /bin/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('dotenv').config() 4 | 5 | const program = require('commander') 6 | const colors = require('colors') 7 | const Watchtower = require('../src/watchtower') 8 | 9 | program 10 | .version('0.0.1') 11 | .option('-a, --address
', 'Public address to monitor') 12 | .option('-p, --phone ', 'Phone number to recieve SMS notifications on') 13 | .parse(process.argv) 14 | 15 | // const debug = program.debug ? 'debug:*' : '' 16 | 17 | // const options = { 18 | // finalityDepth: program.finality, 19 | // port: program.port, 20 | // ethereumEndpoint: program.ethereum, 21 | // debug: `service:*,${debug}`, 22 | // contractProvider: PlasmaCore.providers.ContractProviders.ContractProvider, 23 | // walletProvider: wallets[program.wallet], 24 | // operatorProvider: PlasmaCore.providers.OperatorProviders.HttpOperatorProvider, 25 | // plasmaChainName: program.chain, 26 | // registryAddress: program.registry 27 | // } 28 | 29 | const options = { 30 | address: program.address, 31 | phone: program.phone, 32 | } 33 | 34 | const watchtower = new Watchtower(options) 35 | 36 | // (async () => { 37 | // // const latest = await latestVersion(pkg.name) 38 | // // if (pkg.version !== latest) { 39 | // // console.log(colors.red('ERROR: ') + 'Your plasma-client is out of date.') 40 | // // console.log('Please update to the latest version by running:') 41 | // // console.log(colors.green('npm install -g --upgrade plasma-client')) 42 | // // console.log() 43 | // // console.log(`You might also want to reset your database (this won't delete your accounts):`) 44 | // // console.log(colors.green('plasma-cli killdb')) 45 | // // return 46 | // // } 47 | 48 | 49 | 50 | // // console.log('Plasma Client v' + pkg.version + ' 🎉 🎉 🎉 ') 51 | 52 | // // console.log(getSectionTitle('DISCLAIMER')) 53 | // // console.log('Plasma Client is alpha software and will probably break.') 54 | // // console.log(`Please do NOT use this application with real money (unless you're willing to lose it).`) 55 | 56 | // // console.log(getSectionTitle('Available Accounts')) 57 | // // const accounts = await client.getAccounts() 58 | // // accounts.forEach((account, i) => { 59 | // // const maxDigits = (accounts.length - 1).toString().length 60 | // // const accountNumber = i.toString().padStart(maxDigits, '0') 61 | // // console.log(`(${accountNumber}) ${account}`) 62 | // // }) 63 | 64 | // // console.log(getSectionTitle('Client Information')) 65 | // // console.log(`Plasma Chain: ${program.chain}`) 66 | // // console.log(`Ethereum Node: ${program.ethereum}`) 67 | // // console.log(`Listening on: http://${program.hostname}:${program.port}`) 68 | 69 | // // console.log(getSectionTitle('Logs')) 70 | // })() 71 | 72 | 73 | 74 | async function main() { 75 | await watchtower.start() 76 | } 77 | 78 | main().then(() => console.log('done')) 79 | -------------------------------------------------------------------------------- /docs/main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lightclient/watchtower/89c1c437b793e1cd403d7c7626c09867955135d1/docs/main.png -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const Watchtower = require('./src/watchtower') 2 | 3 | module.exports = Watchtower 4 | -------------------------------------------------------------------------------- /kubernetes/redis-cluster-ip.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: redis-cluster-ip-service 5 | namespace: watchtower 6 | spec: 7 | type: ClusterIP 8 | selector: 9 | component: redis 10 | ports: 11 | - port: 6379 12 | targetPort: 6379 13 | -------------------------------------------------------------------------------- /kubernetes/redis-deployment.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: redis-deployment 5 | namespace: watchtower 6 | spec: 7 | replicas: 1 8 | selector: 9 | matchLabels: 10 | component: redis 11 | template: 12 | metadata: 13 | labels: 14 | component: redis 15 | spec: 16 | containers: 17 | - name: redis 18 | image: redis 19 | ports: 20 | - containerPort: 6379 21 | -------------------------------------------------------------------------------- /manager/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.4 2 | ADD app /bin/app 3 | CMD ["app"] 4 | EXPOSE 8080 5 | -------------------------------------------------------------------------------- /manager/kubernetes/server-deployment.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: server-deployment 5 | namespace: watchtower 6 | spec: 7 | replicas: 1 8 | selector: 9 | matchLabels: 10 | component: server 11 | template: 12 | metadata: 13 | labels: 14 | component: server 15 | spec: 16 | containers: 17 | - name: server 18 | image: mattgarnett/auto-k8s:REPLACE_WITH_TAG 19 | command: ["app"] 20 | ports: 21 | - containerPort: 8080 22 | imagePullPolicy: Always 23 | -------------------------------------------------------------------------------- /manager/kubernetes/server-load-balancer-service.yml: -------------------------------------------------------------------------------- 1 | kind: Service 2 | apiVersion: v1 3 | metadata: 4 | name: server-load-balancer 5 | namespace: watchtower 6 | spec: 7 | type: LoadBalancer 8 | ports: 9 | - port: 80 10 | targetPort: 8080 11 | selector: 12 | component: server 13 | -------------------------------------------------------------------------------- /manager/pkg/config.go: -------------------------------------------------------------------------------- 1 | package root 2 | 3 | // type MongoConfig struct { 4 | // Ip string `json:"ip"` 5 | // DbName string `json:"dbName"` 6 | // } 7 | 8 | type ServerConfig struct { 9 | Port string `json:"port"` 10 | } 11 | 12 | type KubernetesConfig struct { 13 | Namespace string `json:"namespace"` 14 | } 15 | 16 | // type AuthConfig struct { 17 | // Secret string `json:"secret"` 18 | // } 19 | 20 | type Config struct { 21 | Server *ServerConfig `json:"server"` 22 | Kubernetes *KubernetesConfig `json:"kubernetes"` 23 | } 24 | -------------------------------------------------------------------------------- /manager/pkg/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "os" 5 | 6 | root "github.com/c-o-l-o-r/watchtower/manager/pkg" 7 | ) 8 | 9 | func GetConfig() *root.Config { 10 | return &root.Config{ 11 | Server: &root.ServerConfig{Port: envOrDefaultString("watchtower:server:port", ":8080")}, 12 | Kubernetes: &root.KubernetesConfig{Namespace: envOrDefaultString("watchtower:namespace", "watchtower")}, 13 | } 14 | } 15 | 16 | func envOrDefaultString(envVar string, defaultValue string) string { 17 | value := os.Getenv(envVar) 18 | if value == "" { 19 | return defaultValue 20 | } 21 | 22 | return value 23 | } 24 | -------------------------------------------------------------------------------- /manager/pkg/kubernetes/client.go: -------------------------------------------------------------------------------- 1 | package kubernetes 2 | 3 | import ( 4 | root "github.com/c-o-l-o-r/watchtower/manager/pkg" 5 | "k8s.io/client-go/kubernetes" 6 | appsv1 "k8s.io/client-go/kubernetes/typed/apps/v1" 7 | "k8s.io/client-go/rest" 8 | ) 9 | 10 | type Client struct { 11 | deployments appsv1.DeploymentInterface 12 | } 13 | 14 | func NewClient(config *root.KubernetesConfig) (*Client, error) { 15 | clusterConfig, err := rest.InClusterConfig() 16 | if err != nil { 17 | panic(err.Error()) 18 | } 19 | 20 | clientset, err := kubernetes.NewForConfig(clusterConfig) 21 | if err != nil { 22 | panic(err.Error()) 23 | } 24 | 25 | return &Client{deployments: clientset.AppsV1().Deployments(config.Namespace)}, err 26 | } 27 | -------------------------------------------------------------------------------- /manager/pkg/kubernetes/watchtower_service.go: -------------------------------------------------------------------------------- 1 | package kubernetes 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | root "github.com/c-o-l-o-r/watchtower/manager/pkg" 8 | appsv1 "k8s.io/api/apps/v1" 9 | apiv1 "k8s.io/api/core/v1" 10 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 | ) 12 | 13 | type WatchtowerService struct { 14 | client *Client 15 | } 16 | 17 | func NewWatchtowerService(c *Client) *WatchtowerService { 18 | return &WatchtowerService{c} 19 | } 20 | 21 | func (p *WatchtowerService) CreateWatchtower(a root.WatchtowerAttributes) error { 22 | 23 | // Address provided to kubernetes must be lowercase in order to adhere to RFC 1123 24 | lowercaseAddress := strings.ToLower(a.Address) 25 | 26 | deployment := &appsv1.Deployment{ 27 | ObjectMeta: metav1.ObjectMeta{ 28 | Name: fmt.Sprintf("watchtower-%s", lowercaseAddress), 29 | Namespace: "watchtower", 30 | }, 31 | Spec: appsv1.DeploymentSpec{ 32 | Replicas: int32Ptr(1), 33 | Selector: &metav1.LabelSelector{ 34 | MatchLabels: map[string]string{ 35 | "app": "watchtower", 36 | }, 37 | }, 38 | Template: apiv1.PodTemplateSpec{ 39 | ObjectMeta: metav1.ObjectMeta{ 40 | Labels: map[string]string{ 41 | "app": "watchtower", 42 | }, 43 | }, 44 | Spec: apiv1.PodSpec{ 45 | Containers: []apiv1.Container{ 46 | { 47 | Name: "web", 48 | Image: "mattgarnett/watchtower:89ae292", 49 | Command: []string{"node"}, 50 | ImagePullPolicy: "Always", 51 | Args: []string{"./bin/cli.js", "-p", a.Phone, "-a", a.Address}, 52 | Ports: []apiv1.ContainerPort{ 53 | { 54 | Name: "http", 55 | Protocol: apiv1.ProtocolTCP, 56 | ContainerPort: 80, 57 | }, 58 | }, 59 | Env: []apiv1.EnvVar{ 60 | { 61 | Name: "REDIS_HOST", 62 | Value: "redis://redis-cluster-ip-service:6379", 63 | }, 64 | { 65 | Name: "TWILIO_AUTH_TOKEN", 66 | ValueFrom: &apiv1.EnvVarSource{ 67 | ConfigMapKeyRef: &apiv1.ConfigMapKeySelector{ 68 | LocalObjectReference: apiv1.LocalObjectReference{ 69 | Name: "watchtower-config", 70 | }, 71 | Key: "TWILIO_AUTH_TOKEN", 72 | }, 73 | }, 74 | }, 75 | { 76 | Name: "TWILIO_SID", 77 | ValueFrom: &apiv1.EnvVarSource{ 78 | ConfigMapKeyRef: &apiv1.ConfigMapKeySelector{ 79 | LocalObjectReference: apiv1.LocalObjectReference{ 80 | Name: "watchtower-config", 81 | }, 82 | Key: "TWILIO_SID", 83 | }, 84 | }, 85 | }, 86 | { 87 | Name: "TWILIO_FROM_NUMBER", 88 | ValueFrom: &apiv1.EnvVarSource{ 89 | ConfigMapKeyRef: &apiv1.ConfigMapKeySelector{ 90 | LocalObjectReference: apiv1.LocalObjectReference{ 91 | Name: "watchtower-config", 92 | }, 93 | Key: "TWILIO_FROM_NUMBER", 94 | }, 95 | }, 96 | }, 97 | { 98 | Name: "TWITTER_CONSUMER_KEY", 99 | ValueFrom: &apiv1.EnvVarSource{ 100 | ConfigMapKeyRef: &apiv1.ConfigMapKeySelector{ 101 | LocalObjectReference: apiv1.LocalObjectReference{ 102 | Name: "watchtower-config", 103 | }, 104 | Key: "TWITTER_CONSUMER_KEY", 105 | }, 106 | }, 107 | }, 108 | { 109 | Name: "TWITTER_CONSUMER_SECRET", 110 | ValueFrom: &apiv1.EnvVarSource{ 111 | ConfigMapKeyRef: &apiv1.ConfigMapKeySelector{ 112 | LocalObjectReference: apiv1.LocalObjectReference{ 113 | Name: "watchtower-config", 114 | }, 115 | Key: "TWITTER_CONSUMER_SECRET", 116 | }, 117 | }, 118 | }, 119 | { 120 | Name: "TWITTER_ACCESS_TOKEN", 121 | ValueFrom: &apiv1.EnvVarSource{ 122 | ConfigMapKeyRef: &apiv1.ConfigMapKeySelector{ 123 | LocalObjectReference: apiv1.LocalObjectReference{ 124 | Name: "watchtower-config", 125 | }, 126 | Key: "TWITTER_ACCESS_TOKEN", 127 | }, 128 | }, 129 | }, 130 | { 131 | Name: "TWITTER_ACCESS_TOKEN_SECRET", 132 | ValueFrom: &apiv1.EnvVarSource{ 133 | ConfigMapKeyRef: &apiv1.ConfigMapKeySelector{ 134 | LocalObjectReference: apiv1.LocalObjectReference{ 135 | Name: "watchtower-config", 136 | }, 137 | Key: "TWITTER_ACCESS_TOKEN_SECRET", 138 | }, 139 | }, 140 | }, 141 | }, 142 | }, 143 | }, 144 | }, 145 | }, 146 | }, 147 | } 148 | 149 | _, err := p.client.deployments.Create(deployment) 150 | return err 151 | } 152 | 153 | func int32Ptr(i int32) *int32 { return &i } 154 | -------------------------------------------------------------------------------- /manager/pkg/server/index.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | const Index string = ` 4 | 5 | 6 | 7 | 235 | 236 | 237 |
238 | source 239 |
240 |
241 |
242 |
243 |

WATCHTOWER

244 | 245 |
246 |
247 | Receive notifications of malicious activity on your Plasma chain via 248 | text message and our Twitter bot. 249 |
250 | 279 | 280 |
281 |

ethereum address

282 | 283 |

phone number

284 | 285 | 286 |
287 |
288 |
289 |
290 | 291 |
292 |
293 |
294 | 295 | 296 | ` 297 | -------------------------------------------------------------------------------- /manager/pkg/server/response.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | ) 7 | 8 | func Error(w http.ResponseWriter, code int, message string) { 9 | Json(w, code, map[string]string{"error": message}) 10 | } 11 | 12 | func Json(w http.ResponseWriter, code int, payload interface{}) { 13 | response, _ := json.Marshal(payload) 14 | 15 | w.Header().Set("Content-Type", "application/json") 16 | w.WriteHeader(code) 17 | w.Write(response) 18 | } 19 | -------------------------------------------------------------------------------- /manager/pkg/server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "html/template" 5 | "log" 6 | "net/http" 7 | "os" 8 | 9 | root "github.com/c-o-l-o-r/watchtower/manager/pkg" 10 | "github.com/gorilla/handlers" 11 | "github.com/gorilla/mux" 12 | ) 13 | 14 | type Server struct { 15 | router *mux.Router 16 | config *root.ServerConfig 17 | } 18 | 19 | func NewServer(w root.WatchtowerService, config *root.Config) *Server { 20 | s := Server{ 21 | router: mux.NewRouter(), 22 | config: config.Server, 23 | } 24 | 25 | NewWatchtowerRouter(w, s.getSubrouter("/watchtower")) 26 | s.router.HandleFunc("/", IndexHandler) 27 | return &s 28 | } 29 | 30 | func (s *Server) Start() { 31 | log.Println("Listening on port " + s.config.Port) 32 | if err := http.ListenAndServe(s.config.Port, handlers.LoggingHandler(os.Stdout, s.router)); err != nil { 33 | log.Fatal("http.ListenAndServe: ", err) 34 | } 35 | } 36 | 37 | func (s *Server) getSubrouter(path string) *mux.Router { 38 | return s.router.PathPrefix(path).Subrouter() 39 | } 40 | 41 | func IndexHandler(w http.ResponseWriter, r *http.Request) { 42 | t := template.New("index") 43 | t, err := t.Parse(Index) 44 | if err != nil { 45 | panic(err) 46 | } 47 | 48 | t.Execute(w, nil) 49 | } 50 | -------------------------------------------------------------------------------- /manager/pkg/server/watchtower_router.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | 8 | root "github.com/c-o-l-o-r/watchtower/manager/pkg" 9 | "github.com/gorilla/mux" 10 | "github.com/gorilla/schema" 11 | ) 12 | 13 | type watchtowerRouter struct { 14 | watchtowerService root.WatchtowerService 15 | } 16 | 17 | func NewWatchtowerRouter(w root.WatchtowerService, router *mux.Router) *mux.Router { 18 | watchtowerRouter := watchtowerRouter{w} 19 | 20 | router.HandleFunc("/", watchtowerRouter.createWatchtowerJSONHandler). 21 | HeadersRegexp("Content-Type", "application/(text|json)"). 22 | Methods("POST") 23 | 24 | router.HandleFunc("/", watchtowerRouter.createWatchtowerFormHandler). 25 | HeadersRegexp("Content-Type", "application/x-www-form-urlencoded"). 26 | Methods("POST") 27 | 28 | return router 29 | } 30 | 31 | func (wt *watchtowerRouter) createWatchtowerJSONHandler(w http.ResponseWriter, r *http.Request) { 32 | decoder := json.NewDecoder(r.Body) 33 | var watchtowerAttributes root.WatchtowerAttributes 34 | err := decoder.Decode(&watchtowerAttributes) 35 | if err != nil { 36 | panic(err) 37 | } 38 | 39 | err = wt.watchtowerService.CreateWatchtower(watchtowerAttributes) 40 | if err != nil { 41 | panic(err) 42 | } 43 | 44 | Json(w, http.StatusOK, "success") 45 | } 46 | 47 | func (wt *watchtowerRouter) createWatchtowerFormHandler(w http.ResponseWriter, r *http.Request) { 48 | refer := r.Header.Get("Referer") 49 | 50 | err := r.ParseForm() 51 | if err != nil { 52 | redirectFail(w, r, refer) 53 | } 54 | 55 | decoder := schema.NewDecoder() 56 | var watchtowerAttributes root.WatchtowerAttributes 57 | err = decoder.Decode(&watchtowerAttributes, r.PostForm) 58 | if err != nil { 59 | redirectFail(w, r, refer) 60 | } 61 | 62 | err = wt.watchtowerService.CreateWatchtower(watchtowerAttributes) 63 | if err != nil { 64 | redirectFail(w, r, refer) 65 | } 66 | 67 | redirectURL := fmt.Sprintf("%s?success=true&address=%s", refer, watchtowerAttributes.Address) 68 | http.Redirect(w, r, redirectURL, 302) 69 | } 70 | 71 | func redirectFail(w http.ResponseWriter, r *http.Request, url string) { 72 | http.Redirect(w, r, fmt.Sprintf("%s?success=false", url), 302) 73 | } 74 | -------------------------------------------------------------------------------- /manager/pkg/watchtower.go: -------------------------------------------------------------------------------- 1 | package root 2 | 3 | type WatchtowerAttributes struct { 4 | Address string 5 | Email string 6 | Phone string 7 | } 8 | 9 | type WatchtowerService interface { 10 | CreateWatchtower(a WatchtowerAttributes) error 11 | } 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "watchtower", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "repository": "git@github.com:c-o-l-o-r/watchtower.git", 6 | "author": "Matt Garnett ", 7 | "license": "MIT", 8 | "dependencies": { 9 | "axios": "^0.18.0", 10 | "colors": "^1.3.3", 11 | "commander": "^2.19.0", 12 | "dotenv": "^6.2.0", 13 | "inquirer": "^6.2.2", 14 | "redis": "^2.8.0", 15 | "twilio": "^3.28.0", 16 | "twit": "^2.2.11", 17 | "uuid": "^3.3.2" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/clients/redis.js: -------------------------------------------------------------------------------- 1 | const redis = require('redis'); 2 | const { promisify } = require('util'); 3 | 4 | const client = redis.createClient({ url: process.env['REDIS_HOST'] }); 5 | 6 | const get = promisify(client.get).bind(client); 7 | const set = promisify(client.set).bind(client); 8 | 9 | module.exports = { get, set } 10 | -------------------------------------------------------------------------------- /src/clients/twilio.js: -------------------------------------------------------------------------------- 1 | const accountSid = process.env.TWILIO_SID; 2 | const authToken = process.env.TWILIO_AUTH_TOKEN; 3 | const client = require('twilio')(accountSid, authToken); 4 | 5 | function send(message, to) { 6 | return client.messages.create({ 7 | body: message, 8 | from: process.env.TWILIO_FROM_NUMBER, 9 | to: to 10 | }) 11 | } 12 | 13 | module.exports = { send } 14 | -------------------------------------------------------------------------------- /src/clients/twitter.js: -------------------------------------------------------------------------------- 1 | const Twit = require('twit') 2 | 3 | const T = new Twit({ 4 | consumer_key: process.env.TWITTER_CONSUMER_KEY, 5 | consumer_secret: process.env.TWITTER_CONSUMER_SECRET, 6 | access_token: process.env.TWITTER_ACCESS_TOKEN, 7 | access_token_secret: process.env.TWITTER_ACCESS_TOKEN_SECRET, 8 | timeout_ms: 60*1000, // optional HTTP request timeout to apply to all requests. 9 | strictSSL: true, // optional - requires SSL certificates to be valid. 10 | }) 11 | 12 | function post(status) { 13 | return new Promise((resolve, reject) => { 14 | T.post('statuses/update', { status }, function(err, data, response) { 15 | if (err) { 16 | reject(err) 17 | } 18 | 19 | resolve(data) 20 | }) 21 | }) 22 | } 23 | 24 | module.exports = { post } 25 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | function sleep(ms) { 2 | return new Promise(resolve => setTimeout(resolve, ms)); 3 | } 4 | 5 | module.exports = { sleep } 6 | -------------------------------------------------------------------------------- /src/watchtower.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios') 2 | const uuidv4 = require('uuid/v4') 3 | 4 | const twilio = require('./clients/twilio') 5 | const twitter = require('./clients/twitter') 6 | const redis = require('./clients/redis') 7 | 8 | const utils = require('./utils') 9 | 10 | // const defaultOptions = { 11 | // port: '9898', 12 | // dbProvider: LevelDBProvider, 13 | // operatorProvider: PlasmaCore.providers.OperatorProviders.HttpOperatorProvider, 14 | // contractProvider: PlasmaCore.providers.ContractProviders.ContractProvider, 15 | // walletProvider: PlasmaCore.providers.WalletProviders.LocalWalletProvider, 16 | // dbPath: dbPaths.CHAIN_DB_PATH 17 | // } 18 | 19 | /** 20 | * Class that houses the Watchtower. 21 | */ 22 | class Watchtower { 23 | constructor (options) { 24 | this.options = Object.assign({}, options) 25 | console.log(options) 26 | } 27 | 28 | /** 29 | * Starts the node. 30 | */ 31 | async start () { 32 | this.started = true 33 | await this._initConnection() 34 | await this._sendRPC('pg_monitorAccount', this.options.address) 35 | this._pollNode() 36 | } 37 | 38 | /** 39 | * Stops the node. 40 | */ 41 | stop () { 42 | this.started = false 43 | } 44 | 45 | /** 46 | * Polls the node for invalid transactions and notifies the account owner if 47 | * someone attempts to transfer a coinrange they own 48 | */ 49 | async _pollNode() { 50 | if (!this.started) return 51 | 52 | try { 53 | console.log('polling') 54 | 55 | const response = await this._sendRPC( 56 | 'pg_getMaliciousTransactions', 57 | this.options.address 58 | ) 59 | 60 | console.log('got this from node: ', response) 61 | 62 | for (element in response.result) { 63 | const [txHash, error] = element 64 | 65 | const notified = await redis.get(txHash) 66 | 67 | if (!notified) { 68 | redis.set(txHash, 'true') 69 | this._notifyOwner() 70 | } 71 | } 72 | } finally { 73 | await utils.sleep(10000) 74 | this._pollOperator() 75 | } 76 | } 77 | 78 | /** 79 | * Sends the owner a text message regarding the invalid transfer attempt and 80 | * posts a tweet to the Plasma chain's Twitter account 81 | */ 82 | async _notifyOwner() { 83 | await Promise.all([ 84 | this._sendSMS(), 85 | this._sendTweet(), 86 | ]) 87 | } 88 | 89 | /** 90 | * Sends a SMS message to the account owner 91 | */ 92 | async _sendSMS(data) { 93 | console.log('sending sms') 94 | try { 95 | await twilio.send('Your coins are running away!', this.options.phone) 96 | } catch (e) { 97 | console.log(e) 98 | } 99 | } 100 | 101 | /** 102 | * Post a Tweet to the Plasma chain's Twitter account 103 | */ 104 | async _sendTweet(data) { 105 | console.log('sending tweet') 106 | try { 107 | await twitter.post('Bad stuff is happening on the chain!') 108 | } catch(e) { 109 | console.log(e) 110 | } 111 | } 112 | 113 | /** 114 | * Initializes axios http connection to the Plasma node 115 | */ 116 | async _initConnection () { 117 | this.endpoint = 'http://plasma-node-cluster-ip-service:9898' 118 | this.http = axios.create({ 119 | baseURL: this.endpoint.startsWith('http') 120 | ? this.endpoint 121 | : `https://${this.endpoint}` 122 | }) 123 | } 124 | 125 | /** 126 | * Sends an RPC call over http to the Plasma node 127 | * @param {string} method 128 | * @param {string | Object} params 129 | */ 130 | async _sendRPC(method, params) { 131 | try { 132 | const response = await this.http.post('/', { 133 | jsonrpc: '2.0', 134 | method: method, 135 | params: params, 136 | id: uuidv4() 137 | }) 138 | 139 | return JSON.parse(response.data) 140 | } catch(e) { 141 | throw e 142 | } 143 | } 144 | } 145 | 146 | module.exports = Watchtower 147 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@types/body-parser@*": 6 | version "1.17.0" 7 | resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.17.0.tgz#9f5c9d9bd04bb54be32d5eb9fc0d8c974e6cf58c" 8 | integrity sha512-a2+YeUjPkztKJu5aIF2yArYFQQp8d51wZ7DavSHjFuY1mqVgidGyzEQ41JIVNy82fXj8yPgy2vJmfIywgESW6w== 9 | dependencies: 10 | "@types/connect" "*" 11 | "@types/node" "*" 12 | 13 | "@types/connect@*": 14 | version "3.4.32" 15 | resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.32.tgz#aa0e9616b9435ccad02bc52b5b454ffc2c70ba28" 16 | integrity sha512-4r8qa0quOvh7lGD0pre62CAb1oni1OO6ecJLGCezTmhQ8Fz50Arx9RUszryR8KlgK6avuSXvviL6yWyViQABOg== 17 | dependencies: 18 | "@types/node" "*" 19 | 20 | "@types/express-serve-static-core@*": 21 | version "4.16.1" 22 | resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.16.1.tgz#35df7b302299a4ab138a643617bd44078e74d44e" 23 | integrity sha512-QgbIMRU1EVRry5cIu1ORCQP4flSYqLM1lS5LYyGWfKnFT3E58f0gKto7BR13clBFVrVZ0G0rbLZ1hUpSkgQQOA== 24 | dependencies: 25 | "@types/node" "*" 26 | "@types/range-parser" "*" 27 | 28 | "@types/express@^4.11.1": 29 | version "4.16.1" 30 | resolved "https://registry.yarnpkg.com/@types/express/-/express-4.16.1.tgz#d756bd1a85c34d87eaf44c888bad27ba8a4b7cf0" 31 | integrity sha512-V0clmJow23WeyblmACoxbHBu2JKlE5TiIme6Lem14FnPW9gsttyHtk6wq7njcdIWH1njAaFgR8gW09lgY98gQg== 32 | dependencies: 33 | "@types/body-parser" "*" 34 | "@types/express-serve-static-core" "*" 35 | "@types/serve-static" "*" 36 | 37 | "@types/mime@*": 38 | version "2.0.1" 39 | resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.1.tgz#dc488842312a7f075149312905b5e3c0b054c79d" 40 | integrity sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw== 41 | 42 | "@types/node@*": 43 | version "11.9.4" 44 | resolved "https://registry.yarnpkg.com/@types/node/-/node-11.9.4.tgz#ceb0048a546db453f6248f2d1d95e937a6f00a14" 45 | integrity sha512-Zl8dGvAcEmadgs1tmSPcvwzO1YRsz38bVJQvH1RvRqSR9/5n61Q1ktcDL0ht3FXWR+ZpVmXVwN1LuH4Ax23NsA== 46 | 47 | "@types/range-parser@*": 48 | version "1.2.3" 49 | resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c" 50 | integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA== 51 | 52 | "@types/serve-static@*": 53 | version "1.13.2" 54 | resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.2.tgz#f5ac4d7a6420a99a6a45af4719f4dcd8cd907a48" 55 | integrity sha512-/BZ4QRLpH/bNYgZgwhKEh+5AsboDBcUdlBYgzoLX0fpj3Y2gp6EApyOlM3bK53wQS/OE1SrdSYBAbux2D1528Q== 56 | dependencies: 57 | "@types/express-serve-static-core" "*" 58 | "@types/mime" "*" 59 | 60 | ajv@^6.5.5: 61 | version "6.9.1" 62 | resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.9.1.tgz#a4d3683d74abc5670e75f0b16520f70a20ea8dc1" 63 | integrity sha512-XDN92U311aINL77ieWHmqCcNlwjoP5cHXDxIxbf2MaPYuCXOHS7gHH8jktxeK5omgd52XbSTX6a4Piwd1pQmzA== 64 | dependencies: 65 | fast-deep-equal "^2.0.1" 66 | fast-json-stable-stringify "^2.0.0" 67 | json-schema-traverse "^0.4.1" 68 | uri-js "^4.2.2" 69 | 70 | ansi-escapes@^3.2.0: 71 | version "3.2.0" 72 | resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" 73 | integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== 74 | 75 | ansi-regex@^3.0.0: 76 | version "3.0.0" 77 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" 78 | integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= 79 | 80 | ansi-regex@^4.0.0: 81 | version "4.0.0" 82 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.0.0.tgz#70de791edf021404c3fd615aa89118ae0432e5a9" 83 | integrity sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w== 84 | 85 | ansi-styles@^3.2.1: 86 | version "3.2.1" 87 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" 88 | integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== 89 | dependencies: 90 | color-convert "^1.9.0" 91 | 92 | asap@^2.0.0: 93 | version "2.0.6" 94 | resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" 95 | integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= 96 | 97 | asn1@~0.2.3: 98 | version "0.2.4" 99 | resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" 100 | integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== 101 | dependencies: 102 | safer-buffer "~2.1.0" 103 | 104 | assert-plus@1.0.0, assert-plus@^1.0.0: 105 | version "1.0.0" 106 | resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" 107 | integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= 108 | 109 | asynckit@^0.4.0: 110 | version "0.4.0" 111 | resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" 112 | integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= 113 | 114 | aws-sign2@~0.7.0: 115 | version "0.7.0" 116 | resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" 117 | integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= 118 | 119 | aws4@^1.8.0: 120 | version "1.8.0" 121 | resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" 122 | integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ== 123 | 124 | axios@^0.18.0: 125 | version "0.18.0" 126 | resolved "https://registry.yarnpkg.com/axios/-/axios-0.18.0.tgz#32d53e4851efdc0a11993b6cd000789d70c05102" 127 | integrity sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI= 128 | dependencies: 129 | follow-redirects "^1.3.0" 130 | is-buffer "^1.1.5" 131 | 132 | bcrypt-pbkdf@^1.0.0: 133 | version "1.0.2" 134 | resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" 135 | integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= 136 | dependencies: 137 | tweetnacl "^0.14.3" 138 | 139 | bluebird@^3.1.5: 140 | version "3.5.3" 141 | resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.3.tgz#7d01c6f9616c9a51ab0f8c549a79dfe6ec33efa7" 142 | integrity sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw== 143 | 144 | buffer-equal-constant-time@1.0.1: 145 | version "1.0.1" 146 | resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" 147 | integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= 148 | 149 | caseless@~0.12.0: 150 | version "0.12.0" 151 | resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" 152 | integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= 153 | 154 | chalk@^2.4.2: 155 | version "2.4.2" 156 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" 157 | integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== 158 | dependencies: 159 | ansi-styles "^3.2.1" 160 | escape-string-regexp "^1.0.5" 161 | supports-color "^5.3.0" 162 | 163 | chardet@^0.7.0: 164 | version "0.7.0" 165 | resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" 166 | integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== 167 | 168 | cli-cursor@^2.1.0: 169 | version "2.1.0" 170 | resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" 171 | integrity sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU= 172 | dependencies: 173 | restore-cursor "^2.0.0" 174 | 175 | cli-width@^2.0.0: 176 | version "2.2.0" 177 | resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" 178 | integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk= 179 | 180 | color-convert@^1.9.0: 181 | version "1.9.3" 182 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" 183 | integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== 184 | dependencies: 185 | color-name "1.1.3" 186 | 187 | color-name@1.1.3: 188 | version "1.1.3" 189 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" 190 | integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= 191 | 192 | colors@^1.3.3: 193 | version "1.3.3" 194 | resolved "https://registry.yarnpkg.com/colors/-/colors-1.3.3.tgz#39e005d546afe01e01f9c4ca8fa50f686a01205d" 195 | integrity sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg== 196 | 197 | combined-stream@^1.0.6, combined-stream@~1.0.6: 198 | version "1.0.7" 199 | resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.7.tgz#2d1d24317afb8abe95d6d2c0b07b57813539d828" 200 | integrity sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w== 201 | dependencies: 202 | delayed-stream "~1.0.0" 203 | 204 | commander@^2.19.0: 205 | version "2.19.0" 206 | resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a" 207 | integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg== 208 | 209 | core-util-is@1.0.2: 210 | version "1.0.2" 211 | resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" 212 | integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= 213 | 214 | dashdash@^1.12.0: 215 | version "1.14.1" 216 | resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" 217 | integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= 218 | dependencies: 219 | assert-plus "^1.0.0" 220 | 221 | debug@^3.2.6: 222 | version "3.2.6" 223 | resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" 224 | integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== 225 | dependencies: 226 | ms "^2.1.1" 227 | 228 | delayed-stream@~1.0.0: 229 | version "1.0.0" 230 | resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" 231 | integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= 232 | 233 | deprecate@1.0.0: 234 | version "1.0.0" 235 | resolved "https://registry.yarnpkg.com/deprecate/-/deprecate-1.0.0.tgz#661490ed2428916a6c8883d8834e5646f4e4a4a8" 236 | integrity sha1-ZhSQ7SQokWpsiIPYg05WRvTkpKg= 237 | 238 | dotenv@^6.2.0: 239 | version "6.2.0" 240 | resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-6.2.0.tgz#941c0410535d942c8becf28d3f357dbd9d476064" 241 | integrity sha512-HygQCKUBSFl8wKQZBSemMywRWcEDNidvNbjGVyZu3nbZ8qq9ubiPoGLMdRDpfSrpkkm9BXYFkpKxxFX38o/76w== 242 | 243 | double-ended-queue@^2.1.0-0: 244 | version "2.1.0-0" 245 | resolved "https://registry.yarnpkg.com/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz#103d3527fd31528f40188130c841efdd78264e5c" 246 | integrity sha1-ED01J/0xUo9AGIEwyEHv3XgmTlw= 247 | 248 | ecc-jsbn@~0.1.1: 249 | version "0.1.2" 250 | resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" 251 | integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= 252 | dependencies: 253 | jsbn "~0.1.0" 254 | safer-buffer "^2.1.0" 255 | 256 | ecdsa-sig-formatter@1.0.10: 257 | version "1.0.10" 258 | resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz#1c595000f04a8897dfb85000892a0f4c33af86c3" 259 | integrity sha1-HFlQAPBKiJffuFAAiSoPTDOvhsM= 260 | dependencies: 261 | safe-buffer "^5.0.1" 262 | 263 | escape-string-regexp@^1.0.5: 264 | version "1.0.5" 265 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 266 | integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= 267 | 268 | extend@~3.0.2: 269 | version "3.0.2" 270 | resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" 271 | integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== 272 | 273 | external-editor@^3.0.3: 274 | version "3.0.3" 275 | resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.0.3.tgz#5866db29a97826dbe4bf3afd24070ead9ea43a27" 276 | integrity sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA== 277 | dependencies: 278 | chardet "^0.7.0" 279 | iconv-lite "^0.4.24" 280 | tmp "^0.0.33" 281 | 282 | extsprintf@1.3.0: 283 | version "1.3.0" 284 | resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" 285 | integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= 286 | 287 | extsprintf@^1.2.0: 288 | version "1.4.0" 289 | resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" 290 | integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= 291 | 292 | fast-deep-equal@^2.0.1: 293 | version "2.0.1" 294 | resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" 295 | integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= 296 | 297 | fast-json-stable-stringify@^2.0.0: 298 | version "2.0.0" 299 | resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" 300 | integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I= 301 | 302 | figures@^2.0.0: 303 | version "2.0.0" 304 | resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" 305 | integrity sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI= 306 | dependencies: 307 | escape-string-regexp "^1.0.5" 308 | 309 | follow-redirects@^1.3.0: 310 | version "1.7.0" 311 | resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.7.0.tgz#489ebc198dc0e7f64167bd23b03c4c19b5784c76" 312 | integrity sha512-m/pZQy4Gj287eNy94nivy5wchN3Kp+Q5WgUPNy5lJSZ3sgkVKSYV/ZChMAQVIgx1SqfZ2zBZtPA2YlXIWxxJOQ== 313 | dependencies: 314 | debug "^3.2.6" 315 | 316 | forever-agent@~0.6.1: 317 | version "0.6.1" 318 | resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" 319 | integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= 320 | 321 | form-data@~2.3.2: 322 | version "2.3.3" 323 | resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" 324 | integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== 325 | dependencies: 326 | asynckit "^0.4.0" 327 | combined-stream "^1.0.6" 328 | mime-types "^2.1.12" 329 | 330 | getpass@^0.1.1: 331 | version "0.1.7" 332 | resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" 333 | integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= 334 | dependencies: 335 | assert-plus "^1.0.0" 336 | 337 | har-schema@^2.0.0: 338 | version "2.0.0" 339 | resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" 340 | integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= 341 | 342 | har-validator@~5.1.0: 343 | version "5.1.3" 344 | resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" 345 | integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== 346 | dependencies: 347 | ajv "^6.5.5" 348 | har-schema "^2.0.0" 349 | 350 | has-flag@^3.0.0: 351 | version "3.0.0" 352 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" 353 | integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= 354 | 355 | http-signature@~1.2.0: 356 | version "1.2.0" 357 | resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" 358 | integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= 359 | dependencies: 360 | assert-plus "^1.0.0" 361 | jsprim "^1.2.2" 362 | sshpk "^1.7.0" 363 | 364 | iconv-lite@^0.4.24: 365 | version "0.4.24" 366 | resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" 367 | integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== 368 | dependencies: 369 | safer-buffer ">= 2.1.2 < 3" 370 | 371 | inquirer@^6.2.2: 372 | version "6.2.2" 373 | resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.2.2.tgz#46941176f65c9eb20804627149b743a218f25406" 374 | integrity sha512-Z2rREiXA6cHRR9KBOarR3WuLlFzlIfAEIiB45ll5SSadMg7WqOh1MKEjjndfuH5ewXdixWCxqnVfGOQzPeiztA== 375 | dependencies: 376 | ansi-escapes "^3.2.0" 377 | chalk "^2.4.2" 378 | cli-cursor "^2.1.0" 379 | cli-width "^2.0.0" 380 | external-editor "^3.0.3" 381 | figures "^2.0.0" 382 | lodash "^4.17.11" 383 | mute-stream "0.0.7" 384 | run-async "^2.2.0" 385 | rxjs "^6.4.0" 386 | string-width "^2.1.0" 387 | strip-ansi "^5.0.0" 388 | through "^2.3.6" 389 | 390 | is-buffer@^1.1.5: 391 | version "1.1.6" 392 | resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" 393 | integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== 394 | 395 | is-fullwidth-code-point@^2.0.0: 396 | version "2.0.0" 397 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" 398 | integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= 399 | 400 | is-promise@^2.1.0: 401 | version "2.1.0" 402 | resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" 403 | integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o= 404 | 405 | is-typedarray@~1.0.0: 406 | version "1.0.0" 407 | resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" 408 | integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= 409 | 410 | isstream@~0.1.2: 411 | version "0.1.2" 412 | resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" 413 | integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= 414 | 415 | jsbn@~0.1.0: 416 | version "0.1.1" 417 | resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" 418 | integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= 419 | 420 | json-schema-traverse@^0.4.1: 421 | version "0.4.1" 422 | resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" 423 | integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== 424 | 425 | json-schema@0.2.3: 426 | version "0.2.3" 427 | resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" 428 | integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= 429 | 430 | json-stringify-safe@~5.0.1: 431 | version "5.0.1" 432 | resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" 433 | integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= 434 | 435 | jsonwebtoken@^8.1.0: 436 | version "8.4.0" 437 | resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.4.0.tgz#8757f7b4cb7440d86d5e2f3becefa70536c8e46a" 438 | integrity sha512-coyXjRTCy0pw5WYBpMvWOMN+Kjaik2MwTUIq9cna/W7NpO9E+iYbumZONAz3hcr+tXFJECoQVrtmIoC3Oz0gvg== 439 | dependencies: 440 | jws "^3.1.5" 441 | lodash.includes "^4.3.0" 442 | lodash.isboolean "^3.0.3" 443 | lodash.isinteger "^4.0.4" 444 | lodash.isnumber "^3.0.3" 445 | lodash.isplainobject "^4.0.6" 446 | lodash.isstring "^4.0.1" 447 | lodash.once "^4.0.0" 448 | ms "^2.1.1" 449 | 450 | jsprim@^1.2.2: 451 | version "1.4.1" 452 | resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" 453 | integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= 454 | dependencies: 455 | assert-plus "1.0.0" 456 | extsprintf "1.3.0" 457 | json-schema "0.2.3" 458 | verror "1.10.0" 459 | 460 | jwa@^1.2.0: 461 | version "1.2.0" 462 | resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.2.0.tgz#606da70c1c6d425cad329c77c99f2df2a981489a" 463 | integrity sha512-Grku9ZST5NNQ3hqNUodSkDfEBqAmGA1R8yiyPHOnLzEKI0GaCQC/XhFmsheXYuXzFQJdILbh+lYBiliqG5R/Vg== 464 | dependencies: 465 | buffer-equal-constant-time "1.0.1" 466 | ecdsa-sig-formatter "1.0.10" 467 | safe-buffer "^5.0.1" 468 | 469 | jws@^3.1.5: 470 | version "3.2.1" 471 | resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.1.tgz#d79d4216a62c9afa0a3d5e8b5356d75abdeb2be5" 472 | integrity sha512-bGA2omSrFUkd72dhh05bIAN832znP4wOU3lfuXtRBuGTbsmNmDXMQg28f0Vsxaxgk4myF5YkKQpz6qeRpMgX9g== 473 | dependencies: 474 | jwa "^1.2.0" 475 | safe-buffer "^5.0.1" 476 | 477 | lodash.includes@^4.3.0: 478 | version "4.3.0" 479 | resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" 480 | integrity sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8= 481 | 482 | lodash.isboolean@^3.0.3: 483 | version "3.0.3" 484 | resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" 485 | integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY= 486 | 487 | lodash.isinteger@^4.0.4: 488 | version "4.0.4" 489 | resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" 490 | integrity sha1-YZwK89A/iwTDH1iChAt3sRzWg0M= 491 | 492 | lodash.isnumber@^3.0.3: 493 | version "3.0.3" 494 | resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" 495 | integrity sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w= 496 | 497 | lodash.isplainobject@^4.0.6: 498 | version "4.0.6" 499 | resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" 500 | integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs= 501 | 502 | lodash.isstring@^4.0.1: 503 | version "4.0.1" 504 | resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" 505 | integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE= 506 | 507 | lodash.once@^4.0.0: 508 | version "4.1.1" 509 | resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" 510 | integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= 511 | 512 | lodash@^4.17.10, lodash@^4.17.11: 513 | version "4.17.11" 514 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" 515 | integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== 516 | 517 | mime-db@~1.38.0: 518 | version "1.38.0" 519 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.38.0.tgz#1a2aab16da9eb167b49c6e4df2d9c68d63d8e2ad" 520 | integrity sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg== 521 | 522 | mime-types@^2.1.12, mime-types@~2.1.19: 523 | version "2.1.22" 524 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.22.tgz#fe6b355a190926ab7698c9a0556a11199b2199bd" 525 | integrity sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog== 526 | dependencies: 527 | mime-db "~1.38.0" 528 | 529 | mime@^1.3.4: 530 | version "1.6.0" 531 | resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" 532 | integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== 533 | 534 | mimic-fn@^1.0.0: 535 | version "1.2.0" 536 | resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" 537 | integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== 538 | 539 | moment@2.19.3: 540 | version "2.19.3" 541 | resolved "https://registry.yarnpkg.com/moment/-/moment-2.19.3.tgz#bdb99d270d6d7fda78cc0fbace855e27fe7da69f" 542 | integrity sha1-vbmdJw1tf9p4zA+6zoVeJ/59pp8= 543 | 544 | ms@^2.1.1: 545 | version "2.1.1" 546 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" 547 | integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== 548 | 549 | mute-stream@0.0.7: 550 | version "0.0.7" 551 | resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" 552 | integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= 553 | 554 | oauth-sign@~0.9.0: 555 | version "0.9.0" 556 | resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" 557 | integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== 558 | 559 | onetime@^2.0.0: 560 | version "2.0.1" 561 | resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" 562 | integrity sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ= 563 | dependencies: 564 | mimic-fn "^1.0.0" 565 | 566 | os-tmpdir@~1.0.2: 567 | version "1.0.2" 568 | resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" 569 | integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= 570 | 571 | performance-now@^2.1.0: 572 | version "2.1.0" 573 | resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" 574 | integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= 575 | 576 | pop-iterate@^1.0.1: 577 | version "1.0.1" 578 | resolved "https://registry.yarnpkg.com/pop-iterate/-/pop-iterate-1.0.1.tgz#ceacfdab4abf353d7a0f2aaa2c1fc7b3f9413ba3" 579 | integrity sha1-zqz9q0q/NT16DyqqLB/Hs/lBO6M= 580 | 581 | psl@^1.1.24: 582 | version "1.1.31" 583 | resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.31.tgz#e9aa86d0101b5b105cbe93ac6b784cd547276184" 584 | integrity sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw== 585 | 586 | punycode@^1.4.1: 587 | version "1.4.1" 588 | resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" 589 | integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= 590 | 591 | punycode@^2.1.0: 592 | version "2.1.1" 593 | resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" 594 | integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== 595 | 596 | q@2.0.x: 597 | version "2.0.3" 598 | resolved "https://registry.yarnpkg.com/q/-/q-2.0.3.tgz#75b8db0255a1a5af82f58c3f3aaa1efec7d0d134" 599 | integrity sha1-dbjbAlWhpa+C9Yw/Oqoe/sfQ0TQ= 600 | dependencies: 601 | asap "^2.0.0" 602 | pop-iterate "^1.0.1" 603 | weak-map "^1.0.5" 604 | 605 | qs@~6.5.2: 606 | version "6.5.2" 607 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" 608 | integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== 609 | 610 | redis-commands@^1.2.0: 611 | version "1.4.0" 612 | resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.4.0.tgz#52f9cf99153efcce56a8f86af986bd04e988602f" 613 | integrity sha512-cu8EF+MtkwI4DLIT0x9P8qNTLFhQD4jLfxLR0cCNkeGzs87FN6879JOJwNQR/1zD7aSYNbU0hgsV9zGY71Itvw== 614 | 615 | redis-parser@^2.6.0: 616 | version "2.6.0" 617 | resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-2.6.0.tgz#52ed09dacac108f1a631c07e9b69941e7a19504b" 618 | integrity sha1-Uu0J2srBCPGmMcB+m2mUHnoZUEs= 619 | 620 | redis@^2.8.0: 621 | version "2.8.0" 622 | resolved "https://registry.yarnpkg.com/redis/-/redis-2.8.0.tgz#202288e3f58c49f6079d97af7a10e1303ae14b02" 623 | integrity sha512-M1OkonEQwtRmZv4tEWF2VgpG0JWJ8Fv1PhlgT5+B+uNq2cA3Rt1Yt/ryoR+vQNOQcIEgdCdfH0jr3bDpihAw1A== 624 | dependencies: 625 | double-ended-queue "^2.1.0-0" 626 | redis-commands "^1.2.0" 627 | redis-parser "^2.6.0" 628 | 629 | request@^2.68.0, request@^2.87.0: 630 | version "2.88.0" 631 | resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" 632 | integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== 633 | dependencies: 634 | aws-sign2 "~0.7.0" 635 | aws4 "^1.8.0" 636 | caseless "~0.12.0" 637 | combined-stream "~1.0.6" 638 | extend "~3.0.2" 639 | forever-agent "~0.6.1" 640 | form-data "~2.3.2" 641 | har-validator "~5.1.0" 642 | http-signature "~1.2.0" 643 | is-typedarray "~1.0.0" 644 | isstream "~0.1.2" 645 | json-stringify-safe "~5.0.1" 646 | mime-types "~2.1.19" 647 | oauth-sign "~0.9.0" 648 | performance-now "^2.1.0" 649 | qs "~6.5.2" 650 | safe-buffer "^5.1.2" 651 | tough-cookie "~2.4.3" 652 | tunnel-agent "^0.6.0" 653 | uuid "^3.3.2" 654 | 655 | restore-cursor@^2.0.0: 656 | version "2.0.0" 657 | resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" 658 | integrity sha1-n37ih/gv0ybU/RYpI9YhKe7g368= 659 | dependencies: 660 | onetime "^2.0.0" 661 | signal-exit "^3.0.2" 662 | 663 | rootpath@0.1.2: 664 | version "0.1.2" 665 | resolved "https://registry.yarnpkg.com/rootpath/-/rootpath-0.1.2.tgz#5b379a87dca906e9b91d690a599439bef267ea6b" 666 | integrity sha1-Wzeah9ypBum5HWkKWZQ5vvJn6ms= 667 | 668 | run-async@^2.2.0: 669 | version "2.3.0" 670 | resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" 671 | integrity sha1-A3GrSuC91yDUFm19/aZP96RFpsA= 672 | dependencies: 673 | is-promise "^2.1.0" 674 | 675 | rxjs@^6.4.0: 676 | version "6.4.0" 677 | resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.4.0.tgz#f3bb0fe7bda7fb69deac0c16f17b50b0b8790504" 678 | integrity sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw== 679 | dependencies: 680 | tslib "^1.9.0" 681 | 682 | safe-buffer@^5.0.1, safe-buffer@^5.1.2: 683 | version "5.1.2" 684 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" 685 | integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== 686 | 687 | "safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: 688 | version "2.1.2" 689 | resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" 690 | integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== 691 | 692 | scmp@2.0.0: 693 | version "2.0.0" 694 | resolved "https://registry.yarnpkg.com/scmp/-/scmp-2.0.0.tgz#247110ef22ccf897b13a3f0abddb52782393cd6a" 695 | integrity sha1-JHEQ7yLM+JexOj8KvdtSeCOTzWo= 696 | 697 | signal-exit@^3.0.2: 698 | version "3.0.2" 699 | resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" 700 | integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= 701 | 702 | sshpk@^1.7.0: 703 | version "1.16.1" 704 | resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" 705 | integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== 706 | dependencies: 707 | asn1 "~0.2.3" 708 | assert-plus "^1.0.0" 709 | bcrypt-pbkdf "^1.0.0" 710 | dashdash "^1.12.0" 711 | ecc-jsbn "~0.1.1" 712 | getpass "^0.1.1" 713 | jsbn "~0.1.0" 714 | safer-buffer "^2.0.2" 715 | tweetnacl "~0.14.0" 716 | 717 | string-width@^2.1.0: 718 | version "2.1.1" 719 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" 720 | integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== 721 | dependencies: 722 | is-fullwidth-code-point "^2.0.0" 723 | strip-ansi "^4.0.0" 724 | 725 | strip-ansi@^4.0.0: 726 | version "4.0.0" 727 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" 728 | integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= 729 | dependencies: 730 | ansi-regex "^3.0.0" 731 | 732 | strip-ansi@^5.0.0: 733 | version "5.0.0" 734 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.0.0.tgz#f78f68b5d0866c20b2c9b8c61b5298508dc8756f" 735 | integrity sha512-Uu7gQyZI7J7gn5qLn1Np3G9vcYGTVqB+lFTytnDJv83dd8T22aGH451P3jueT2/QemInJDfxHB5Tde5OzgG1Ow== 736 | dependencies: 737 | ansi-regex "^4.0.0" 738 | 739 | supports-color@^5.3.0: 740 | version "5.5.0" 741 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" 742 | integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== 743 | dependencies: 744 | has-flag "^3.0.0" 745 | 746 | through@^2.3.6: 747 | version "2.3.8" 748 | resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" 749 | integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= 750 | 751 | tmp@^0.0.33: 752 | version "0.0.33" 753 | resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" 754 | integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== 755 | dependencies: 756 | os-tmpdir "~1.0.2" 757 | 758 | tough-cookie@~2.4.3: 759 | version "2.4.3" 760 | resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" 761 | integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ== 762 | dependencies: 763 | psl "^1.1.24" 764 | punycode "^1.4.1" 765 | 766 | tslib@^1.9.0: 767 | version "1.9.3" 768 | resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" 769 | integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ== 770 | 771 | tunnel-agent@^0.6.0: 772 | version "0.6.0" 773 | resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" 774 | integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= 775 | dependencies: 776 | safe-buffer "^5.0.1" 777 | 778 | tweetnacl@^0.14.3, tweetnacl@~0.14.0: 779 | version "0.14.5" 780 | resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" 781 | integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= 782 | 783 | twilio@^3.28.0: 784 | version "3.28.0" 785 | resolved "https://registry.yarnpkg.com/twilio/-/twilio-3.28.0.tgz#8ad8b88529ac0cdf0d571f88bf22dc60d1caa002" 786 | integrity sha512-wsBU2+4bWpuhVl4ZjYPaR7WYD2U27ghALclK2Jg9pp4RgtD0EhPMIhTFWkGJ8+zUqdIW/ZkYA/4UocDx9vdoQw== 787 | dependencies: 788 | "@types/express" "^4.11.1" 789 | deprecate "1.0.0" 790 | jsonwebtoken "^8.1.0" 791 | lodash "^4.17.10" 792 | moment "2.19.3" 793 | q "2.0.x" 794 | request "^2.87.0" 795 | rootpath "0.1.2" 796 | scmp "2.0.0" 797 | xmlbuilder "9.0.1" 798 | 799 | twit@^2.2.11: 800 | version "2.2.11" 801 | resolved "https://registry.yarnpkg.com/twit/-/twit-2.2.11.tgz#554343d1cf343ddf503280db821f61be5ab407c3" 802 | integrity sha512-BkdwvZGRVoUTcEBp0zuocuqfih4LB+kEFUWkWJOVBg6pAE9Ebv9vmsYTTrfXleZGf45Bj5H3A1/O9YhF2uSYNg== 803 | dependencies: 804 | bluebird "^3.1.5" 805 | mime "^1.3.4" 806 | request "^2.68.0" 807 | 808 | uri-js@^4.2.2: 809 | version "4.2.2" 810 | resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" 811 | integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== 812 | dependencies: 813 | punycode "^2.1.0" 814 | 815 | uuid@^3.3.2: 816 | version "3.3.2" 817 | resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" 818 | integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== 819 | 820 | verror@1.10.0: 821 | version "1.10.0" 822 | resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" 823 | integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= 824 | dependencies: 825 | assert-plus "^1.0.0" 826 | core-util-is "1.0.2" 827 | extsprintf "^1.2.0" 828 | 829 | weak-map@^1.0.5: 830 | version "1.0.5" 831 | resolved "https://registry.yarnpkg.com/weak-map/-/weak-map-1.0.5.tgz#79691584d98607f5070bd3b70a40e6bb22e401eb" 832 | integrity sha1-eWkVhNmGB/UHC9O3CkDmuyLkAes= 833 | 834 | xmlbuilder@9.0.1: 835 | version "9.0.1" 836 | resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.1.tgz#91cd70897755363eba57c12ddeeab4a341a61f65" 837 | integrity sha1-kc1wiXdVNj66V8Et3uq0o0GmH2U= 838 | --------------------------------------------------------------------------------