diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 00000000..95c2b9b2 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,20 @@ +{ + "env": { + "node": true, + "commonjs": true, + "es6": true + }, + "plugins": ["prettier"], + "extends": "eslint:recommended", + "globals": { + "Atomics": "readonly", + "SharedArrayBuffer": "readonly" + }, + "parserOptions": { + "ecmaVersion": 2018 + }, + "rules": { + "prettier/prettier": "error", + "no-console": "off" + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..9a7fe73d --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +.DS_Store + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Dependency directories +node_modules/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# dotenv environment variables file +.env \ No newline at end of file diff --git a/.sample.env b/.sample.env new file mode 100644 index 00000000..bca686c9 --- /dev/null +++ b/.sample.env @@ -0,0 +1,28 @@ +# Environment Config + +# store your secrets and config variables in here +# only invited collaborators will be able to see your .env values + +# Page and Application information +PAGE_ID= +APP_ID= +PAGE_ACCESS_TOKEN= + +# Your App secret can be found in App Dashboard -> Seetings -> Basic +APP_SECRET= + +# A random string that is used for the webhook verification request +VERIFY_TOKEN= + +# URL where you host this code +# You can use LocalTunnel or Heroku ex: https://mystic-wind-83.herokuapp.com +APP_URL= + +# URL of your website where the "shop now" is located +# Can be the same as your app domain URL ex: https://www.originalcoastclothing.com/ +SHOP_URL= + +# Preferred port +PORT=3000 + +# note: .env is a shell file so there can't be spaces around = \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..8437918a --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,7 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +## [1.0] - 2019-04-30 +### Added +- Launch the Sample Messenger App Original Coast Clothing diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..ab7c1c5c --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,5 @@ +# Code of Conduct + +Facebook has adopted a Code of Conduct that we expect project participants to adhere to. +Please read the [full text](https://code.fb.com/codeofconduct/) +so that you can understand what actions will and will not be tolerated.CODE_OF_CONDUCT.md \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..407c17fb --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,45 @@ +# Contributing to Sample Messenger App Original Coast Clothing +We want to make contributing to this project as easy and transparent as +possible. + +## Our Development Process +Informed by internal platform changes and Messenger product design and best +practices we will decide on the direction of the product. Suggestions are welcome +and for external contributions the recomendation is to file an Issue explaining +why the changes would be usefull. Please also add to the issue if you would like +to submit a Pull Request to solve the issue. + +When submitting a PR please provide the context and is best to reference the +issue. + +## Pull Requests +We actively welcome your pull requests. + +1. Fork the repo and create your branch from `master`. +2. If you've added code that should be tested, add tests. +3. If you've changed APIs, update the documentation. +4. Ensure the test suite passes. +5. Make sure your code lints. +6. If you haven't already, complete the Contributor License Agreement ("CLA"). + +## Contributor License Agreement ("CLA") +In order to accept your pull request, we need you to submit a CLA. You only need +to do this once to work on any of Facebook's open source projects. + +Complete your CLA here: + +## Issues +We use GitHub issues to track public bugs. Please ensure your description is +clear and has sufficient instructions to be able to reproduce the issue. + +Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe +disclosure of security bugs. In those cases, please go through the process +outlined on that page and do not file a public issue. + +## Coding Style +* We use Prettier for styling + +## License +By contributing to Sample Messenger App Original Coast Clothing, you agree that +your contributions will be licensed under the LICENSE file in the root directory +of this source tree. \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..bc8d312d --- /dev/null +++ b/LICENSE @@ -0,0 +1,30 @@ +BSD License + +For Messsenger For Sample Messenger App Original Coast Clothing + +Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name Facebook nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 00000000..675bf0d4 --- /dev/null +++ b/README.md @@ -0,0 +1,144 @@ +# Original Coast Clothing Messenger Bot + +Original Coast Clothing (OC) is a fictional clothing brand created to showcase key features of the Messenger Platform. OC leverages key features to deliver a great customer experience. Using this demo as inspiration, you can create a delightful messaging experience that leverages both automation and live customer support. We are also providing the open source code of the app and a guide to deploy the experience on your local environment or remote server. + +[Access the Messenger experience](https://m.me/OriginalCoastClothing?ref=GITHUB) + +![Messenger Experience](public/experience.png) + +See the [Developer Documentations on this experience](https://developers.facebook.com/docs/messenger-platform/getting-started/sample-apps/original-coast-clothing). + +# Setting up your Messenger App + +## Requirements + +- **Facebook Page:** Will be used as the identity of your messaging experience. When people chat with your page. To create a new Page, visit https://www.facebook.com/pages/create. +- **Facebook Developer Account:** Required to create new apps, which are the core of any Facebook integration. You can create a new developer account by going to the [Facebook Developers website](https://developers.facebook.com/) and clicking the "Get Started" button. +- **Facebook App:** Contains the settings for your Messenger automation, including access tokens. To create a new app, visit your [app dashboard](https://developers.facebook.com/apps). + +## Setup Steps + +Before you begin, make sure you have completed all of the requirements listed above. At this point you should have a Page and a registered Facebook App. + +#### Get the App id and App Secret + +1. Go to your app Basic Settings, [Find your app here](https://developers.facebook.com/apps) +2. Save the **App ID** number and the **App Secret** + +#### Grant Messenger access to your Facebook App + +1. Go to your app Dashboard +2. Under Add Product find Messenger and click Set Up +3. Now you should be in the App Messenger Settings +4. Under Access Tokens, click on Edit Permissions +5. Select the desired page and allow Manage and access Page conversations in Messenger +6. Select the desired page and an access token should appear +7. Get the Page ID from the page access token by using the [Access Token Debugger](https://developers.facebook.com/tools/debug/accesstoken/) +8. In the section Built-In NLP, select your page and enable the toggle + +# Installation + +Clone this repository on your local machine: + +```bash +$ git clone git@github.com:fbsamples/original-coast-clothing.git +$ cd original-coast-clothing +``` + +You will need: + +- [Node](https://nodejs.org/en/) 10.x or higher +- [Localtunnel](https://github.com/localtunnel/localtunnel) or remote server like [Heroku](https://www.heroku.com/) + +# Usage + +## Using Local Tunnel + +#### 1. Install the dependencies + +```bash +$ npm install +``` + +Alternatively, you can use [Yarn](https://yarnpkg.com/en/): + +```bash +$ yarn install +``` + +#### 2. Install Local Tunnel +```bash +npm install -g localtunnel +``` + +Open a new terminal tab and request a tunnel to your local server with your preferred port +```bash +lt --port 3000 +``` + +#### 3. Rename the file `.sample.env` to `.env` + +```bash +mv .sample.env .env +``` + + Edit the `.env` file to add all the values for your app and page. Then run your app locally using the built-in web server + +#### 4. Run your app locally using the built-in web server< + +```bash +node app.js +``` + +You should now be able to access the application in your browser at [http://localhost:3000](http://localhost:3000) + +#### 5. Configure your webhook subcription and set the Messenger profile + +Use the `VERIFY_TOKEN` that you created in `.env` file and call the **/profile** endpoint like so: +``` +http://localhost:3000/profile?mode=all&verify_token=verify-token +``` + +#### 6. Test that your app setup is successful + +Send a message to your Page from Facebook or in Messenger, if your webhook receives an event, you have fully set up your app! Voilà! + +## Using Heroku +#### 1. Install the Heroku CLI + +Download and install the [Heroku CLI](https://devcenter.heroku.com/articles/heroku-cli) + +#### 2. Create an app from the CLI + +```bash +git init +heroku apps:create +# Creating app... done, ⬢ mystic-wind-83 +# Created http://mystic-wind-83.herokuapp.com/ | git@heroku.com:mystic-wind-83.git +``` + +#### 3. Deploy the code +```bash +git add . +git commit -m "My first commit" +git push heroku master +``` + +#### 4. Set your environment variables + In your Heroku App Dashboard [https://dashboard.heroku.com/apps/mystic-wind-83](https://dashboard.heroku.com/apps/mystic-wind-83) set up the config vars following the comments in the file ```.sample.env``` + +#### 5. Configure your webhook subscription and set the Messenger profile + You should now be able to access the application. Use the ```VERIFY_TOKEN``` that you created as config vars and call the **/profile** endpoint like so: + + ``` + http://mystic-wind-83.herokuapp.com/profile?mode=all&verify_token=verify-token + ``` + +#### 6. Test that your app setup is successful + + Send a message to your Page from Facebook or in Messenger, if your webhook receives an event, you have fully set up your app! Voilà! + +## License +Sample Messenger App Original Coast Clothing is BSD licensed, as found in the LICENSE file. + +See the [CONTRIBUTING](CONTRIBUTING.md) file for how to help out. \ No newline at end of file diff --git a/app.js b/app.js new file mode 100644 index 00000000..011e7c4c --- /dev/null +++ b/app.js @@ -0,0 +1,189 @@ +/** + * Copyright 2019-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + * + * Messenger For Original Coast Clothing + * https://developers.facebook.com/docs/messenger-platform/getting-started/sample-apps/original-coast-clothing + */ + +"use strict"; + +// Imports dependencies and set up http server +const express = require("express"), + { urlencoded, json } = require("body-parser"), + path = require("path"), + Receive = require("./services/receive"), + GraphAPi = require("./services/graph-api"), + User = require("./services/user"), + config = require("./services/config"), + i18n = require("./i18n.config"), + app = express(); + +var users = {}; + +// Parse application/x-www-form-urlencoded +app.use( + urlencoded({ + extended: true + }) +); + +// Parse application/json +app.use(json()); + +// Serving static files in Express +app.use(express.static(path.join(path.resolve(), "public"))); + +// Set template emgine in Express +app.set("view engine", "ejs"); + +// Respond with index file when a GET request is made to the homepage +app.get("/", function(_req, res) { + res.render("index"); +}); + +// Adds support for GET requests to our webhook +app.get("/webhook", (req, res) => { + // Parse the query params + let mode = req.query["hub.mode"]; + let token = req.query["hub.verify_token"]; + let challenge = req.query["hub.challenge"]; + + // Checks if a token and mode is in the query string of the request + if (mode && token) { + // Checks the mode and token sent is correct + if (mode === "subscribe" && token === config.verifyToken) { + // Responds with the challenge token from the request + console.log("WEBHOOK_VERIFIED"); + res.status(200).send(challenge); + } else { + // Responds with '403 Forbidden' if verify tokens do not match + res.sendStatus(403); + } + } +}); + +// Creates the endpoint for your webhook +app.post("/webhook", (req, res) => { + let body = req.body; + + // Checks if this is an event from a page subscription + if (body.object === "page") { + // Returns a '200 OK' response to all requests + res.status(200).send("EVENT_RECEIVED"); + + // Iterates over each entry - there may be multiple if batched + body.entry.forEach(function(entry) { + // Gets the body of the webhook event + let webhookEvent = entry.messaging[0]; + // console.log(webhookEvent); + + // Discard uninteresting events + if ("read" in webhookEvent) { + // console.log("Got a read event"); + return; + } + + if ("delivery" in webhookEvent) { + // console.log("Got a delivery event"); + return; + } + + // Get the sender PSID + let senderPsid = webhookEvent.sender.id; + + if (!(senderPsid in users)) { + let user = new User(senderPsid); + + GraphAPi.getUserProfile(senderPsid) + .then(userProfile => { + user.setProfile(userProfile); + }) + .catch(error => { + // The profile is unavailable + console.log("Profile is unavailable:", error); + }) + .finally(() => { + users[senderPsid] = user; + i18n.setLocale(user.locale); + console.log( + "New Profile PSID:", + senderPsid, + "with locale:", + i18n.getLocale() + ); + let receiveMessage = new Receive(users[senderPsid], webhookEvent); + return receiveMessage.handleMessage(); + }); + } else { + i18n.setLocale(users[senderPsid].locale); + console.log( + "Profile already exists PSID:", + senderPsid, + "with locale:", + i18n.getLocale() + ); + let receiveMessage = new Receive(users[senderPsid], webhookEvent); + return receiveMessage.handleMessage(); + } + }); + } else { + // Returns a '404 Not Found' if event is not from a page subscription + res.sendStatus(404); + } +}); + +// Set up your App's Messenger Profile +app.get("/profile", (req, res) => { + let token = req.query["verify_token"]; + let mode = req.query["mode"]; + + if (!config.webhookUrl.startsWith("https://")) { + res.status(200).send("ERROR - Need a proper API_URL in the .env file"); + } + var Profile = require("./services/profile.js"); + Profile = new Profile(); + + // Checks if a token and mode is in the query string of the request + if (mode && token) { + if (token === config.verifyToken) { + if (mode == "webhook" || mode == "all") { + Profile.setWebhook(); + res.write( + `

Set app ${config.appId} call to ${config.webhookUrl}

` + ); + } + if (mode == "profile" || mode == "all") { + Profile.setThread(); + res.write(`

Set Messenger Profile of Page ${config.pageId}

`); + } + if (mode == "personas" || mode == "all") { + Profile.setPersonas(); + res.write(`

Set Personas for ${config.appId}

`); + res.write( + "

To persist the personas, add the following variables to your environment variables:

" + ); + res.write(""); + } + res.status(200).end(); + } else { + // Responds with '403 Forbidden' if verify tokens do not match + res.sendStatus(403); + } + } else { + // Returns a '404 Not Found' if mode or token are missing + res.sendStatus(404); + } +}); + +// listen for requests :) +var listener = app.listen(config.port, function() { + console.log("Your app is listening on port " + listener.address().port); +}); diff --git a/i18n.config.js b/i18n.config.js new file mode 100644 index 00000000..68161ad7 --- /dev/null +++ b/i18n.config.js @@ -0,0 +1,25 @@ +/** + * Copyright 2019-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + * + * Messenger For Original Coast Clothing + * https://developers.facebook.com/docs/messenger-platform/getting-started/sample-apps/original-coast-clothing + */ + +const i18n = require("i18n"), + path = require("path"); + +i18n.configure({ + locales: ["en_US", "fr_FR", "es_ES", "es_LA", "pt_BR", "id_ID"], + defaultLocale: "en_US", + directory: path.join(__dirname, "locales"), + objectNotation: true, + api: { + __: "translate", + __n: "translateN" + } +}); + +module.exports = i18n; diff --git a/locales/en_US.json b/locales/en_US.json new file mode 100644 index 00000000..da06ba18 --- /dev/null +++ b/locales/en_US.json @@ -0,0 +1,71 @@ +{ + "profile": { + "greeting": "Welcome to Original Coast Clothing {{user_first_name}}! Click get started to enjoy our sample automated messaging experience. If you want to build a similar one visit https://fb.me/get-sample-oc" + }, + "menu": { + "support": "Customer Support", + "order": "Order updates", + "help": "Talk to an agent", + "suggestion": "Outfit suggestions", + "shop": "Shop now", + "start_over": "Start over" + }, + "get_started": { + "welcome": "Hi {{userFirstName}}! Welcome to Original Coast Clothing where you'll find styles for every occasion.", + "guidance": "At any time, use the menu below to navigate through the features", + "help": "What we can do to help you today?" + }, + "leadgen": { + "promo": "Hi {{userFirstName}}, finding great outfits is now easier and more convenient.", + "title": "50% Coupon", + "subtitle": "Here is your coupon", + "apply": "Apply discount", + "coupon": "Added your coupon to this thread, you can now check out the discounted summer collection" + }, + "curation": { + "prompt": "Are you looking for an outfit for yourself or someone else?", + "occasion": "Great! Let's get started. First, is there an occasion in mind?", + "price": "How much are you looking to spend?", + "me": "For me!", + "someone": "Someone else", + "work": "Work", + "dinner": "Dinner", + "party": "Party", + "sales": "Talk to sales", + "shop": "Shop this look", + "title": "Summer collection 2019", + "subtitle": "Featured outfit", + "show": "Another look" + }, + "order": { + "prompt": "We'll be happy to help you. Select from the following", + "search": "Search by order number", + "account": "Link my Account", + "dialog": "Account linking dialog", + "searching": "Searching for your last order...", + "number": "Sure, please write the order number", + "status": "Your order is on it’s way, expect to have it in 3 days." + }, + "care": { + "prompt": "To connect you with the right team please let me know what this is about?", + "order": "Order Inquiries", + "billing": "Billing Issues", + "other": "Other", + "issue": "Hi {{userFirstName}}, my name is {{agentFirstName}}, I'm here to assist you with any questions you might have about your {{topic}}.", + "default": "Hi {{userFirstName}}, my name is {{agentFirstName}}, I'm here to assist you today. How can I help you?", + "style": "Hi {{userFirstName}}, my name is {{agentFirstName}}, I’m here to answer questions you might have on our summer styles", + "end": "This is as far as the demo goes. Leverage the power of the messenger platform for your business. See https://fb.me/get-sample-oc to learn how to build a similar experience", + "help": "Help" + }, + "survey": { + "prompt": "Hi, want to get quick feedback on your recent experience with {{agentFirstName}}", + "positive": "Thanks for the feedback", + "neutral": "Thanks for the feedback, if there something we should improve feel free to comment with #suggestion", + "negative": "Thanks for telling us. Sorry to hear that, if there something we can do to turn this let us know", + "suggestion": "Your suggestion has been recorded for future review by the team. If this is something we need to get back to you right now just comment \"help\"" + }, + "fallback": { + "any": "Sorry, but I don’t recognize \"{{message}}\".", + "attachment": "Thanks for sending this attachment, we can connect you to an agent to review it or we can start over" + } +} diff --git a/locales/es_ES.json b/locales/es_ES.json new file mode 100644 index 00000000..7e5ac268 --- /dev/null +++ b/locales/es_ES.json @@ -0,0 +1,71 @@ +{ + "profile": { + "greeting": "Te damos la bienvenida a Original Coast Clothing {{user_first_name}}! Haga clic en comenzar para disfrutar de nuestra experiencia de mensajería automatizada de muestra. Si desea crear uno similar, visite https://fb.me/get-sample-oc" + }, + "menu": { + "support": "Servicio al cliente", + "order": "Estado de Órdenes", + "help": "Hablar con un agente", + "suggestion": "Estilos sugeridos", + "shop": "Tienda", + "start_over": "Empezar de nuevo" + }, + "get_started": { + "welcome": "Hola {{userFirstName}}! Te damos la bienvenida a Original Coast Clothing donde encontrarás estilos para cada ocasión.", + "guidance": "En cualquier momento, utilize el menú para iniciar cualquiera de las funciones", + "help": "¿Qué podemos hacer para ayudarte hoy?" + }, + "leadgen": { + "promo": "Hola, {{userFirstName}}, ahora es más fácil y más conveniente encontrar estilos.", + "title": "Cupón de 50%", + "subtitle": "Aqui esta tu cupón", + "apply": "Aplicar descuento", + "coupon": "Agregamos este cupón a la conversación, ahora puede ver la colección de verano con descuento" + }, + "curation": { + "prompt": "¿Estás buscando ropa para ti o para alguien más?", + "occasion": "¡Genial! Primero, ¿tienes alguna ocasión en mente?", + "price": "¿Cuanto quieres gastar?", + "me": "¡Para mi!", + "someone": "Alguien mas", + "work": "Trabajo", + "dinner": "Cena", + "party": "Celebración", + "sales": "Hablar con ventas", + "shop": "Compra este estilo", + "title": "Colección de Verano 2019", + "subtitle": "Estilo destacado", + "show": "Otro estilo" + }, + "order": { + "prompt": "Estaremos encantados de ayudarle. Seleccione entre los siguientes", + "search": "Búsqueda por número de pedido", + "account": "Vincular mi cuenta", + "dialog": "Diálogo de vinculación de cuenta", + "searching": "Buscando tu último pedido...", + "number": "Claro, por favor escriba el número de pedido", + "status": "Su pedido está en camino, espere recibirlo en 3 días." + }, + "care": { + "prompt": "Para conectarte con el equipo correcto, por favor, dime de qué se trata tu consulta", + "order": "Consultas de pedidos", + "billing": "Problemas de facturación", + "other": "Otro", + "issue": "Hola {{userFirstName}}, mi nombre es {{agentFirstName}}, estoy aquí para ayudarte con cualquier pregunta que puedas tener sobre {{topic}}.", + "default": "Hola, {{userFirstName}}, mi nombre es {{agentFirstName}}, estoy aquí para ayudarte. ¿Como puedo ayudarte?", + "style": "Hola, {{userFirstName}}, mi nombre es {{agentFirstName}}, estoy aquí para responder las preguntas que puedas tener sobre nuestros estilos de verano.", + "end": "Hasta aqui llega este demo. Aproveche el poder de la plataforma de Messenger para su negocio. Vea https://fb.me/get-sample-oc para aprender cómo construir una experiencia similar", + "help": "Ayuda" + }, + "survey": { + "prompt": "Hola, queremos tu opinión sobre tu experiencia reciente con {{agentFirstName}}", + "positive": "Gracias por la respuesta", + "neutral": "Gracias por la respuesta, Si hay algo que deberíamos mejorar, siéntete libre de comentar con #sugerencia", + "negative": "Gracias por decirnos. Lamento escuchar eso, si hay algo que podamos hacer para cambiar esto, háganoslo saber", + "suggestion": "Su sugerencia ha sido registrada. Si esto es algo que necesitamos para responder ahora mismo, simplemente comente \"ayuda\"" + }, + "fallback": { + "any": "Disculpe pero no reconozco \"{{message}}\".", + "attachment": "Gracias por enviar este archivo, te podemos conectar con un agente para revisarlo o podemos empezar de nuevo" + } +} diff --git a/locales/es_LA.json b/locales/es_LA.json new file mode 100644 index 00000000..7e5ac268 --- /dev/null +++ b/locales/es_LA.json @@ -0,0 +1,71 @@ +{ + "profile": { + "greeting": "Te damos la bienvenida a Original Coast Clothing {{user_first_name}}! Haga clic en comenzar para disfrutar de nuestra experiencia de mensajería automatizada de muestra. Si desea crear uno similar, visite https://fb.me/get-sample-oc" + }, + "menu": { + "support": "Servicio al cliente", + "order": "Estado de Órdenes", + "help": "Hablar con un agente", + "suggestion": "Estilos sugeridos", + "shop": "Tienda", + "start_over": "Empezar de nuevo" + }, + "get_started": { + "welcome": "Hola {{userFirstName}}! Te damos la bienvenida a Original Coast Clothing donde encontrarás estilos para cada ocasión.", + "guidance": "En cualquier momento, utilize el menú para iniciar cualquiera de las funciones", + "help": "¿Qué podemos hacer para ayudarte hoy?" + }, + "leadgen": { + "promo": "Hola, {{userFirstName}}, ahora es más fácil y más conveniente encontrar estilos.", + "title": "Cupón de 50%", + "subtitle": "Aqui esta tu cupón", + "apply": "Aplicar descuento", + "coupon": "Agregamos este cupón a la conversación, ahora puede ver la colección de verano con descuento" + }, + "curation": { + "prompt": "¿Estás buscando ropa para ti o para alguien más?", + "occasion": "¡Genial! Primero, ¿tienes alguna ocasión en mente?", + "price": "¿Cuanto quieres gastar?", + "me": "¡Para mi!", + "someone": "Alguien mas", + "work": "Trabajo", + "dinner": "Cena", + "party": "Celebración", + "sales": "Hablar con ventas", + "shop": "Compra este estilo", + "title": "Colección de Verano 2019", + "subtitle": "Estilo destacado", + "show": "Otro estilo" + }, + "order": { + "prompt": "Estaremos encantados de ayudarle. Seleccione entre los siguientes", + "search": "Búsqueda por número de pedido", + "account": "Vincular mi cuenta", + "dialog": "Diálogo de vinculación de cuenta", + "searching": "Buscando tu último pedido...", + "number": "Claro, por favor escriba el número de pedido", + "status": "Su pedido está en camino, espere recibirlo en 3 días." + }, + "care": { + "prompt": "Para conectarte con el equipo correcto, por favor, dime de qué se trata tu consulta", + "order": "Consultas de pedidos", + "billing": "Problemas de facturación", + "other": "Otro", + "issue": "Hola {{userFirstName}}, mi nombre es {{agentFirstName}}, estoy aquí para ayudarte con cualquier pregunta que puedas tener sobre {{topic}}.", + "default": "Hola, {{userFirstName}}, mi nombre es {{agentFirstName}}, estoy aquí para ayudarte. ¿Como puedo ayudarte?", + "style": "Hola, {{userFirstName}}, mi nombre es {{agentFirstName}}, estoy aquí para responder las preguntas que puedas tener sobre nuestros estilos de verano.", + "end": "Hasta aqui llega este demo. Aproveche el poder de la plataforma de Messenger para su negocio. Vea https://fb.me/get-sample-oc para aprender cómo construir una experiencia similar", + "help": "Ayuda" + }, + "survey": { + "prompt": "Hola, queremos tu opinión sobre tu experiencia reciente con {{agentFirstName}}", + "positive": "Gracias por la respuesta", + "neutral": "Gracias por la respuesta, Si hay algo que deberíamos mejorar, siéntete libre de comentar con #sugerencia", + "negative": "Gracias por decirnos. Lamento escuchar eso, si hay algo que podamos hacer para cambiar esto, háganoslo saber", + "suggestion": "Su sugerencia ha sido registrada. Si esto es algo que necesitamos para responder ahora mismo, simplemente comente \"ayuda\"" + }, + "fallback": { + "any": "Disculpe pero no reconozco \"{{message}}\".", + "attachment": "Gracias por enviar este archivo, te podemos conectar con un agente para revisarlo o podemos empezar de nuevo" + } +} diff --git a/locales/fr_FR.json b/locales/fr_FR.json new file mode 100644 index 00000000..bce381fa --- /dev/null +++ b/locales/fr_FR.json @@ -0,0 +1,71 @@ +{ + "profile": { + "greeting": "Bienvenue sur Original Coast Clothing {{user_first_name}}! Cliquez sur Démarrer pour profiter de notre démo d'expérience de messagerie automatisée. Si vous souhaitez créer une expérience similaire, visitez https://fb.me/get-sample-oc" + }, + "menu": { + "support": "Support client", + "order": "Mes commandes", + "help": "Parler à un conseiller", + "suggestion": "Recommandations", + "shop": "Acheter sur le site", + "start_over": "Recommencer" + }, + "get_started": { + "welcome": "Bonjour {{userFirstName}}! Bienvenue sur Original Coast Clothing où vous allez trouver des tenues pour toutes les occasions.", + "guidance": "A tout moment, vous pouvez utiliser le menu ci-dessous pour naviguer parmi les fonctionnalités", + "help": "Comment pouvons-nous vous aider aujourd’hui ?" + }, + "leadgen": { + "promo": "Bonjour {{userFirstName}}, trouver de belles tenues est maintenant plus facile et plus pratique.", + "title": "Coupon réduction de -50%", + "subtitle": "Voici votre coupon", + "apply": "Appliquer la réduction", + "coupon": "Votre coupon de réduction a été ajouté à cette conversation. Vous pouvez maintenant découvrir la Collection Été à petits prix" + }, + "curation": { + "prompt": "Vous cherchez une tenue pour vous ou pour quelqu'un d'autre ?", + "occasion": "Super ! Commençons. Premièrement, est ce pour une occasion particulière ?", + "price": "Quel est votre budget ?", + "me": "Pour moi !", + "someone": "Quelqu'un d'autre", + "work": "Travail", + "dinner": "Diner", + "party": "Soirée", + "sales": "Parler à un conseiller", + "shop": "Acheter ce look", + "title": "Collection Été 2019", + "subtitle": "Nouveautés", + "show": "Un autre look" + }, + "order": { + "prompt": "Ravis de pouvoir vous aider. Choisissez parmi les options suivantes:", + "search": "Rechercher par numéro de commande", + "account": "Associer mon compte client", + "dialog": "Fenêtre de dialogue d’association de compte", + "searching": "Nous recherchons votre dernière commande...", + "number": "Veuillez écrire le numéro de commande", + "status": "Votre commande est en cours de préparation. Vous devriez la recevoir dans les 3 prochains jours." + }, + "care": { + "prompt": "Pour vous connecter avec la bonne équipe, pouvez me préciser le sujet de votre question ?", + "order": "Commandes", + "billing": "Factures", + "other": "Autre", + "issue": "Bonjour {{userFirstName}}, je m’appelle {{agentFirstName}}, je suis ici pour répondre à toutes vos questions au sujet vos {{topic}}.", + "default": "Bonjour {{userFirstName}}, je m’appelle {{agentFirstName}}, je suis ici pour répondre à vos questions. Comment puis-je vous aider aujourd'hui ?", + "style": "Bonjour {{userFirstName}}, je m’appelle {{agentFirstName}}, je suis ici pour répondre à vos questions sur notre collection d’été", + "end": "Vous avez atteint la limite de notre démo. Vous aussi, vous pouvez tirer parti de la puissance de la Plateforme Messenger pour votre entreprise. Rendez-vous sur https://fb.me/get-sample-oc pour apprendre comment créer une expérience similaire.", + "help": "Aide" + }, + "survey": { + "prompt": "Bonjour, j’aimerai un rapide avis sur votre récente expérience avec {{agentFirstName}}", + "positive": "Merci d’avoir partagé votre avis", + "neutral": "Merci d’avoir partagé votre avis. Si nous devons améliorer quelque chose en particulier, n'hésitez pas à commenter avec #suggestion", + "negative": "Merci d’avoir partagé votre avis. Nous sommes désolé d'apprendre que votre expérience ne répond pas à vos attentes. S'il y a quelque chose que nous pouvons faire pour changer cela, laissez-le nous savoir", + "suggestion": "Votre suggestion a été enregistrée et sera revue par notre équipe. S’il s’agit d’un sujet sur lequel nous pouvons vous aider dès à présent, envoyez \"aide\"" + }, + "fallback": { + "any": "Désolé, mais je ne comprends pas \"{{message}}\".", + "attachment": "Merci d’avoir envoyé cette pièce jointe, nous pouvons vous connecter à un conseiller pour la passer en revue ou vous pouvez recommencer l’expérience" + } +} \ No newline at end of file diff --git a/locales/id_ID.json b/locales/id_ID.json new file mode 100644 index 00000000..8bcd36fd --- /dev/null +++ b/locales/id_ID.json @@ -0,0 +1,71 @@ +{ + "profile": { + "greeting": "Selamat datang di Original Coast Clothing {{user_first_name}}! Klik mulai untuk mencoba pengalaman olahpesan otomatis. Jika kamu ingin membangun pengalaman yg sama, kunjungi https://fb.me/get-sample-oc" + }, + "menu": { + "support": "Dukungan pelanggan", + "order": "Cek order", + "help": "Hubungi agen", + "suggestion": "Rekomendasi pakaian", + "shop": "Belanja sekarang", + "start_over": "Mulai kembali" + }, + "get_started": { + "welcome": "Hi {{userFirstName}}! Selamat datang di Original Coast Clothing dimana kamu akan menemukan pakaian buat semua kesempatan.", + "guidance": "Kapanpun, gunakan menu dibawah untuk navigasi antar fitur.", + "help": "Apa yang bisa kami bantu hari ini?" + }, + "leadgen": { + "promo": "Hi {{userFirstName}}, mencari pakaian bagus sekarang jadi lebih mudah.", + "title": "Kupon 50%", + "subtitle": "Ini adalah kupon anda", + "apply": "Gunakan diskon", + "coupon": "Kupon sudah ditambahkan ke percakapan ini, kamu sudah bisa mencari koleksi musim panas yang lagi diskon" + }, + "curation": { + "prompt": "Apakah kamu sedang mencari pakaian buat kamu sendiri atau orang lain?", + "occasion": "Bagus! Mari kita mulai. Pertama-tama, adakah acara tertentu yang ada di pikiran kamu?", + "price": "Berapa budget kamu?", + "me": "Untuk saya!", + "someone": "Orang lain", + "work": "Kerja", + "dinner": "Makan malam", + "party": "Pesta", + "sales": "Bicara dengan sales", + "shop": "Belanja model ini", + "title": "Koleksi musim panas 2019", + "subtitle": "Koleksi pakaian pilihan", + "show": "Tampilan berikutnya" + }, + "order": { + "prompt": "Kami dengan senang hati membantu anda. Pilih dari pilihan berikut", + "search": "Cek status orderan", + "account": "Koneksikan akun saya", + "dialog": "Dialog koneksi akun", + "searching": "Mencari order terakhir kamu...", + "number": "Tentu, silahkan tulis nomor orderan kamu.", + "status": "Order kamu sedang dalam perjalanan, dapatkan dalam 3 hari." + }, + "care": { + "prompt": "Untuk dihubungkan dengan tim yang sesuai, silahkan beri tahu kami masalah apa yang dihadapi?", + "order": "Orderan", + "billing": "Pembayaran", + "other": "Lainnya", + "issue": "Hi {{userFirstName}}, nama saya {{agentFirstName}}, Saya akan membantu anda seputar pertanyaan tentang {{topic}}.", + "default": "Hi {{userFirstName}}, nama saya {{agentFirstName}}, Saya akan membantu anda hari ini. Bagaimana saya membantu anda?", + "style": "Hi {{userFirstName}}, nama saya {{agentFirstName}}, Saya akan membantu anda mengenai pertanyaan seputar koleksi musim panas kami.", + "end": "Ini adalah akhir dari demo. Pergunakan fitur fitur messenger untuk bisnis anda. Kunjungi https://fb.me/get-sample-oc untuk belajar bagaimana membangun pengalaman yang sama.", + "help": "Bantuan" + }, + "survey": { + "prompt": "Hi, kami ingin mendapatkan saran dari pengalaman anda dengan {{agentFirstName}}", + "positive": "Terima kasih atas sarannya", + "neutral": "Terima kasih atas sarannya, jika ada sesuatu yang kami bisa perbaiki, kamu bisa komen dengan hashtag #suggestion", + "negative": "Terima kasih atas info nya. Kami sangat menyesali itu. Jika ada sesuatu yang kami bisa perbuat, silahkan beri tahu kami.", + "suggestion": "Saran anda sudah kami simpan untuk perbaikan pelayanan kami ke depan. Jika anda perlu bantuan sekarang, anda bisa komen/balas \"help\"" + }, + "fallback": { + "any": "Maaf, saya tidak mengenali \"{{message}}\".", + "attachment": "Terima kasih atas lampiran ini, kami akan menghubungkan anda dengan agen kami untuk mereview ini atau kita bisa mengulang kembali proses ini" + } +} \ No newline at end of file diff --git a/locales/pt_BR.json b/locales/pt_BR.json new file mode 100644 index 00000000..da06ba18 --- /dev/null +++ b/locales/pt_BR.json @@ -0,0 +1,71 @@ +{ + "profile": { + "greeting": "Welcome to Original Coast Clothing {{user_first_name}}! Click get started to enjoy our sample automated messaging experience. If you want to build a similar one visit https://fb.me/get-sample-oc" + }, + "menu": { + "support": "Customer Support", + "order": "Order updates", + "help": "Talk to an agent", + "suggestion": "Outfit suggestions", + "shop": "Shop now", + "start_over": "Start over" + }, + "get_started": { + "welcome": "Hi {{userFirstName}}! Welcome to Original Coast Clothing where you'll find styles for every occasion.", + "guidance": "At any time, use the menu below to navigate through the features", + "help": "What we can do to help you today?" + }, + "leadgen": { + "promo": "Hi {{userFirstName}}, finding great outfits is now easier and more convenient.", + "title": "50% Coupon", + "subtitle": "Here is your coupon", + "apply": "Apply discount", + "coupon": "Added your coupon to this thread, you can now check out the discounted summer collection" + }, + "curation": { + "prompt": "Are you looking for an outfit for yourself or someone else?", + "occasion": "Great! Let's get started. First, is there an occasion in mind?", + "price": "How much are you looking to spend?", + "me": "For me!", + "someone": "Someone else", + "work": "Work", + "dinner": "Dinner", + "party": "Party", + "sales": "Talk to sales", + "shop": "Shop this look", + "title": "Summer collection 2019", + "subtitle": "Featured outfit", + "show": "Another look" + }, + "order": { + "prompt": "We'll be happy to help you. Select from the following", + "search": "Search by order number", + "account": "Link my Account", + "dialog": "Account linking dialog", + "searching": "Searching for your last order...", + "number": "Sure, please write the order number", + "status": "Your order is on it’s way, expect to have it in 3 days." + }, + "care": { + "prompt": "To connect you with the right team please let me know what this is about?", + "order": "Order Inquiries", + "billing": "Billing Issues", + "other": "Other", + "issue": "Hi {{userFirstName}}, my name is {{agentFirstName}}, I'm here to assist you with any questions you might have about your {{topic}}.", + "default": "Hi {{userFirstName}}, my name is {{agentFirstName}}, I'm here to assist you today. How can I help you?", + "style": "Hi {{userFirstName}}, my name is {{agentFirstName}}, I’m here to answer questions you might have on our summer styles", + "end": "This is as far as the demo goes. Leverage the power of the messenger platform for your business. See https://fb.me/get-sample-oc to learn how to build a similar experience", + "help": "Help" + }, + "survey": { + "prompt": "Hi, want to get quick feedback on your recent experience with {{agentFirstName}}", + "positive": "Thanks for the feedback", + "neutral": "Thanks for the feedback, if there something we should improve feel free to comment with #suggestion", + "negative": "Thanks for telling us. Sorry to hear that, if there something we can do to turn this let us know", + "suggestion": "Your suggestion has been recorded for future review by the team. If this is something we need to get back to you right now just comment \"help\"" + }, + "fallback": { + "any": "Sorry, but I don’t recognize \"{{message}}\".", + "attachment": "Thanks for sending this attachment, we can connect you to an agent to review it or we can start over" + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..b8c964ad --- /dev/null +++ b/package.json @@ -0,0 +1,37 @@ +{ + "name": "original-coast-clothing", + "version": "1.0.0", + "description": "A sample app to show how to build a great Messenger experience!", + "main": "app.js", + "scripts": { + "start": "node app.js" + }, + "dependencies": { + "body-parser": "^1.18.3", + "camelcase": "^5.2.0", + "dotenv": "^6.2.0", + "ejs": "^2.6.1", + "express": "^4.16.4", + "i18n": "^0.8.3", + "path": "^0.12.7", + "request": "^2.88.0" + }, + "engines": { + "node": ">=10.x" + }, + "repository": { + "url": "https://github.com/fbsamples/original-coast-clothing" + }, + "author": "Facebook", + "license": "ISC", + "keywords": [ + "messenger", + "webhook" + ], + "devDependencies": { + "eslint": "^5.16.0", + "eslint-config-prettier": "^4.1.0", + "eslint-plugin-prettier": "^3.0.1", + "prettier": "1.17.0" + } +} \ No newline at end of file diff --git a/public/coupon.png b/public/coupon.png new file mode 100644 index 00000000..02ceb9d5 Binary files /dev/null and b/public/coupon.png differ diff --git a/public/experience.png b/public/experience.png new file mode 100644 index 00000000..e4ac93f3 Binary files /dev/null and b/public/experience.png differ diff --git a/public/logo-black.png b/public/logo-black.png new file mode 100644 index 00000000..66596a24 Binary files /dev/null and b/public/logo-black.png differ diff --git a/public/logo-white.png b/public/logo-white.png new file mode 100644 index 00000000..ab8a5889 Binary files /dev/null and b/public/logo-white.png differ diff --git a/public/logo-white.svg b/public/logo-white.svg new file mode 100644 index 00000000..21d89ce1 --- /dev/null +++ b/public/logo-white.svg @@ -0,0 +1 @@ +OCLogo-White \ No newline at end of file diff --git a/public/logo.png b/public/logo.png new file mode 100644 index 00000000..93045c3d Binary files /dev/null and b/public/logo.png differ diff --git a/public/logo.svg b/public/logo.svg new file mode 100644 index 00000000..846e6b73 --- /dev/null +++ b/public/logo.svg @@ -0,0 +1 @@ +OCLogo \ No newline at end of file diff --git a/public/order.png b/public/order.png new file mode 100644 index 00000000..531082da Binary files /dev/null and b/public/order.png differ diff --git a/public/personas/billing.jpg b/public/personas/billing.jpg new file mode 100644 index 00000000..5d8fa695 Binary files /dev/null and b/public/personas/billing.jpg differ diff --git a/public/personas/care.jpg b/public/personas/care.jpg new file mode 100644 index 00000000..ea9ecfeb Binary files /dev/null and b/public/personas/care.jpg differ diff --git a/public/personas/order.jpg b/public/personas/order.jpg new file mode 100644 index 00000000..1f45ce76 Binary files /dev/null and b/public/personas/order.jpg differ diff --git a/public/personas/sales.jpg b/public/personas/sales.jpg new file mode 100644 index 00000000..d5cf8087 Binary files /dev/null and b/public/personas/sales.jpg differ diff --git a/public/style.css b/public/style.css new file mode 100644 index 00000000..9e17f3f3 --- /dev/null +++ b/public/style.css @@ -0,0 +1,4 @@ +/* body { + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-size: 14px; +} */ \ No newline at end of file diff --git a/public/styles/female-dinner.jpg b/public/styles/female-dinner.jpg new file mode 100644 index 00000000..aba49285 Binary files /dev/null and b/public/styles/female-dinner.jpg differ diff --git a/public/styles/female-party.jpg b/public/styles/female-party.jpg new file mode 100644 index 00000000..fc9e24cf Binary files /dev/null and b/public/styles/female-party.jpg differ diff --git a/public/styles/female-work.jpg b/public/styles/female-work.jpg new file mode 100644 index 00000000..e0eb7808 Binary files /dev/null and b/public/styles/female-work.jpg differ diff --git a/public/styles/male-dinner.jpg b/public/styles/male-dinner.jpg new file mode 100644 index 00000000..562a0b97 Binary files /dev/null and b/public/styles/male-dinner.jpg differ diff --git a/public/styles/male-party.jpg b/public/styles/male-party.jpg new file mode 100644 index 00000000..46e282ab Binary files /dev/null and b/public/styles/male-party.jpg differ diff --git a/public/styles/male-work.jpg b/public/styles/male-work.jpg new file mode 100644 index 00000000..79c4fe51 Binary files /dev/null and b/public/styles/male-work.jpg differ diff --git a/public/styles/neutral-dinner.jpg b/public/styles/neutral-dinner.jpg new file mode 100644 index 00000000..a800f732 Binary files /dev/null and b/public/styles/neutral-dinner.jpg differ diff --git a/public/styles/neutral-party.jpg b/public/styles/neutral-party.jpg new file mode 100644 index 00000000..3485a0fa Binary files /dev/null and b/public/styles/neutral-party.jpg differ diff --git a/public/styles/neutral-work.jpg b/public/styles/neutral-work.jpg new file mode 100644 index 00000000..786a3366 Binary files /dev/null and b/public/styles/neutral-work.jpg differ diff --git a/services/care.js b/services/care.js new file mode 100644 index 00000000..297d4482 --- /dev/null +++ b/services/care.js @@ -0,0 +1,131 @@ +/** + * Copyright 2019-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + * + * Messenger For Original Coast Clothing + * https://developers.facebook.com/docs/messenger-platform/getting-started/sample-apps/original-coast-clothing + */ + +"use strict"; + +// Imports dependencies +const Response = require("./response"), + Survey = require("./survey"), + config = require("./config"), + i18n = require("../i18n.config"); + +module.exports = class Care { + constructor(user, webhookEvent) { + this.user = user; + this.webhookEvent = webhookEvent; + } + + handlePayload(payload) { + let response; + + switch (payload) { + case "CARE_HELP": + response = Response.genQuickReply( + i18n.__("care.prompt", { + userFirstName: this.user.firstName + }), + [ + { + title: i18n.__("care.order"), + payload: "CARE_ORDER" + }, + { + title: i18n.__("care.billing"), + payload: "CARE_BILLING" + }, + { + title: i18n.__("care.other"), + payload: "CARE_OTHER" + } + ] + ); + break; + case "CARE_ORDER": + // Send using Persona 1 and hand over to agent 1 to handle the conversation via HOP + + response = [ + Response.genTextWithPersona( + i18n.__("care.issue", { + userFirstName: this.user.firstName, + agentFirstName: config.personaOrder.name, + topic: i18n.__("care.order") + }), + config.personaOrder.id + ), + Response.genTextWithPersona( + i18n.__("care.end"), + config.personaOrder.id + ), + Survey.genAgentRating(config.personaOrder.name) + ]; + break; + + case "CARE_BILLING": + // Send using Persona 2 and hand over to agent 2 to handle the conversation via HOP + + response = [ + Response.genTextWithPersona( + i18n.__("care.issue", { + userFirstName: this.user.firstName, + agentFirstName: config.personaBilling.name, + topic: i18n.__("care.billing") + }), + config.personaBilling.id + ), + Response.genTextWithPersona( + i18n.__("care.end"), + config.personaBilling.id + ), + Survey.genAgentRating(config.personaBilling.name) + ]; + break; + + case "CARE_SALES": + // Send using Persona 3 and hand over to agent 3 to handle the conversation via HOP + + response = [ + Response.genTextWithPersona( + i18n.__("care.style", { + userFirstName: this.user.firstName, + agentFirstName: config.personaSales.name + }), + config.personaSales.id + ), + Response.genTextWithPersona( + i18n.__("care.end"), + config.personaSales.id + ), + Survey.genAgentRating(config.personaSales.name) + ]; + break; + + case "CARE_OTHER": + // Send using Persona 4 and hand over to agent 4 to handle the conversation via HOP + + response = [ + Response.genTextWithPersona( + i18n.__("care.default", { + userFirstName: this.user.firstName, + agentFirstName: config.personaCare.name + }), + config.personaCare.id + ), + Response.genTextWithPersona( + i18n.__("care.end"), + config.personaCare.id + ), + Survey.genAgentRating(config.personaCare.name) + ]; + break; + } + + return response; + } +}; diff --git a/services/config.js b/services/config.js new file mode 100644 index 00000000..63aa3bbd --- /dev/null +++ b/services/config.js @@ -0,0 +1,105 @@ +/** + * Copyright 2019-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + * + * Messenger For Original Coast Clothing + * https://developers.facebook.com/docs/messenger-platform/getting-started/sample-apps/original-coast-clothing + */ + +"use strict"; + +// Use dotenv to read .env vars into Node +require("dotenv").config(); + +module.exports = { + // Messenger Platform API + mPlatformDomain: "https://graph.facebook.com", + mPlatformVersion: "v3.2", + + // Page and Application information + pageId: process.env.PAGE_ID, + appId: process.env.APP_ID, + pageAccesToken: process.env.PAGE_ACCESS_TOKEN, + appSecret: process.env.APP_SECRET, + verifyToken: process.env.VERIFY_TOKEN, + + // URL of your app domain + appUrl: process.env.APP_URL, + + // URL of your website + shopUrl: process.env.SHOP_URL, + + // Persona IDs + personas: {}, + + // Preferred port (default to 3000) + port: process.env.PORT || 3000, + + get mPlatfom() { + return this.mPlatformDomain + "/" + this.mPlatformVersion; + }, + + // URL of your webhook endpoint + get webhookUrl() { + return this.appUrl + "/webhook"; + }, + + get newPersonas() { + return [ + { + name: "Jorge", + picture: `${this.appUrl}/personas/sales.jpg` + }, + { + name: "Laura", + picture: `${this.appUrl}/personas/billing.jpg` + }, + { + name: "Riandy", + picture: `${this.appUrl}/personas/order.jpg` + }, + { + name: "Daniel", + picture: `${this.appUrl}/personas/care.jpg` + } + ]; + }, + + pushPersona(persona) { + this.personas[persona.name] = persona.id; + }, + + get personaSales() { + let id = this.personas["Jorge"] || process.env.PERSONA_SALES; + return { + name: "Jorge", + id: id + }; + }, + + get personaBilling() { + let id = this.personas["Laura"] || process.env.PERSONA_BILLING; + return { + name: "Laura", + id: id + }; + }, + + get personaOrder() { + let id = this.personas["Riandy"] || process.env.PERSONA_ORDER; + return { + name: "Riandy", + id: id + }; + }, + + get personaCare() { + let id = this.personas["Daniel"] || process.env.PERSONA_CARE; + return { + name: "Daniel", + id: id + }; + } +}; diff --git a/services/curation.js b/services/curation.js new file mode 100644 index 00000000..b7c54f28 --- /dev/null +++ b/services/curation.js @@ -0,0 +1,256 @@ +/** + * Copyright 2019-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + * + * Messenger For Original Coast Clothing + * https://developers.facebook.com/docs/messenger-platform/getting-started/sample-apps/original-coast-clothing + */ + +"use strict"; + +// Imports dependencies +const Response = require("./response"), + config = require("./config"), + i18n = require("../i18n.config"); + +module.exports = class Curation { + constructor(user, webhookEvent) { + this.user = user; + this.webhookEvent = webhookEvent; + } + + handlePayload(payload) { + let response; + let outfit; + + switch (payload) { + case "F8-2019": + response = [ + Response.genText( + i18n.__("get_started.f8-welcome", { + userFirstName: this.user.firstName + }) + ), + Response.genText(i18n.__("get_started.f8-guidance")), + Response.genQuickReply(i18n.__("get_started.f8-help"), [ + { + title: i18n.__("menu.suggestion"), + payload: "CURATION" + }, + { + title: i18n.__("menu.help"), + payload: "CARE_HELP" + } + ]) + ]; + break; + case "SUMMER_COUPON": + response = [ + Response.genText( + i18n.__("leadgen.promo", { + userFirstName: this.user.firstName + }) + ), + Response.genGenericTemplate( + `${config.appUrl}/coupon.png`, + i18n.__("leadgen.title"), + i18n.__("leadgen.subtitle"), + [Response.genPostbackButton(i18n.__("leadgen.apply"), "COUPON_50")] + ) + ]; + break; + + case "COUPON_50": + outfit = `${this.user.gender}-${this.randomOutfit()}`; + + response = [ + Response.genText(i18n.__("leadgen.coupon")), + Response.genGenericTemplate( + `${config.appUrl}/styles/${outfit}.jpg`, + i18n.__("curation.title"), + i18n.__("curation.subtitle"), + [ + Response.genWebUrlButton( + i18n.__("curation.shop"), + `${config.shopUrl}/products/${outfit}` + ), + Response.genPostbackButton( + i18n.__("curation.show"), + "CURATION_OTHER_STYLE" + ), + Response.genPostbackButton( + i18n.__("curation.sales"), + "CARE_SALES" + ) + ] + ) + ]; + break; + + case "CURATION": + response = Response.genQuickReply(i18n.__("curation.prompt"), [ + { + title: i18n.__("curation.me"), + payload: "CURATION_FOR_ME" + }, + { + title: i18n.__("curation.someone"), + payload: "CURATION_SOMEONE_ELSE" + } + ]); + break; + + case "CURATION_FOR_ME": + case "CURATION_SOMEONE_ELSE": + response = Response.genQuickReply(i18n.__("curation.occasion"), [ + { + title: i18n.__("curation.work"), + payload: "CURATION_OCASION_WORK" + }, + { + title: i18n.__("curation.dinner"), + payload: "CURATION_OCASION_DINNER" + }, + { + title: i18n.__("curation.party"), + payload: "CURATION_OCASION_PARTY" + }, + { + title: i18n.__("curation.sales"), + payload: "CARE_SALES" + } + ]); + break; + + case "CURATION_OCASION_WORK": + // Store the user budget preference here + response = Response.genQuickReply(i18n.__("curation.price"), [ + { + title: "~ $20", + payload: "CURATION_BUDGET_20_WORK" + }, + { + title: "~ $30", + payload: "CURATION_BUDGET_30_WORK" + }, + { + title: "+ $50", + payload: "CURATION_BUDGET_50_WORK" + } + ]); + break; + + case "CURATION_OCASION_DINNER": + // Store the user budget preference here + response = Response.genQuickReply(i18n.__("curation.price"), [ + { + title: "~ $20", + payload: "CURATION_BUDGET_20_DINNER" + }, + { + title: "~ $30", + payload: "CURATION_BUDGET_30_DINNER" + }, + { + title: "+ $50", + payload: "CURATION_BUDGET_50_DINNER" + } + ]); + break; + + case "CURATION_OCASION_PARTY": + // Store the user budget preference here + response = Response.genQuickReply(i18n.__("curation.price"), [ + { + title: "~ $20", + payload: "CURATION_BUDGET_20_PARTY" + }, + { + title: "~ $30", + payload: "CURATION_BUDGET_30_PARTY" + }, + { + title: "+ $50", + payload: "CURATION_BUDGET_50_PARTY" + } + ]); + break; + + case "CURATION_BUDGET_20_WORK": + case "CURATION_BUDGET_30_WORK": + case "CURATION_BUDGET_50_WORK": + case "CURATION_BUDGET_20_DINNER": + case "CURATION_BUDGET_30_DINNER": + case "CURATION_BUDGET_50_DINNER": + case "CURATION_BUDGET_20_PARTY": + case "CURATION_BUDGET_30_PARTY": + case "CURATION_BUDGET_50_PARTY": + response = this.genCurationResponse(payload); + break; + + case "CURATION_OTHER_STYLE": + // Build the recommendation logic here + outfit = `${this.user.gender}-${this.randomOutfit()}`; + + response = Response.genGenericTemplate( + `${config.appUrl}/styles/${outfit}.jpg`, + i18n.__("curation.title"), + i18n.__("curation.subtitle"), + [ + Response.genWebUrlButton( + i18n.__("curation.shop"), + `${config.shopUrl}/products/${outfit}` + ), + Response.genPostbackButton( + i18n.__("curation.show"), + "CURATION_OTHER_STYLE" + ) + ] + ); + break; + } + + return response; + } + + genCurationResponse(payload) { + let occasion = payload.split("_")[3].toLowerCase(); + let budget = payload.split("_")[2].toLowerCase(); + let outfit = `${this.user.gender}-${occasion}`; + + let buttons = [ + Response.genWebUrlButton( + i18n.__("curation.shop"), + `${config.shopUrl}/products/${outfit}` + ), + Response.genPostbackButton( + i18n.__("curation.show"), + "CURATION_OTHER_STYLE" + ) + ]; + + if (budget === "50") { + buttons.push( + Response.genPostbackButton(i18n.__("curation.sales"), "CARE_SALES") + ); + } + + let response = Response.genGenericTemplate( + `${config.appUrl}/styles/${outfit}.jpg`, + i18n.__("curation.title"), + i18n.__("curation.subtitle"), + buttons + ); + + return response; + } + + randomOutfit() { + let occasion = ["work", "party", "dinner"]; + let randomIndex = Math.floor(Math.random() * occasion.length); + + return occasion[randomIndex]; + } +}; diff --git a/services/graph-api.js b/services/graph-api.js new file mode 100644 index 00000000..ffba05ca --- /dev/null +++ b/services/graph-api.js @@ -0,0 +1,283 @@ +/** + * Copyright 2019-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + * + * Messenger For Original Coast Clothing + * https://developers.facebook.com/docs/messenger-platform/getting-started/sample-apps/original-coast-clothing + */ + +"use strict"; + +// Imports dependencies +const request = require("request"), + camelCase = require("camelcase"), + config = require("./config"); + +module.exports = class GraphAPi { + static callSendAPI(requestBody) { + // Send the HTTP request to the Messenger Platform + request( + { + uri: `${config.mPlatfom}/me/messages`, + qs: { + access_token: config.pageAccesToken + }, + method: "POST", + json: requestBody + }, + (error, _res, body) => { + if (error) { + console.error("Unable to send message:", error); + } + } + ); + } + + static callMessengerProfileAPI(requestBody) { + // Send the HTTP request to the Messenger Profile API + + console.log(`Setting Messenger Profile for app ${config.appId}`); + request( + { + uri: `${config.mPlatfom}/me/messenger_profile`, + qs: { + access_token: config.pageAccesToken + }, + method: "POST", + json: requestBody + }, + (error, _res, body) => { + if (!error) { + console.log("Request sent:", body); + } else { + console.error("Unable to send message:", error); + } + } + ); + } + + static callSubscriptionsAPI() { + // Send the HTTP request to the Subscriptions Edge to configure your webhook + // You can use the Graph API's /{app-id}/subscriptions edge to configure and manage your app's Webhooks product + // https://developers.facebook.com/docs/graph-api/webhooks/subscriptions-edge + console.log( + `Setting app ${config.appId} callback url to ${config.webhookUrl}` + ); + request( + { + uri: `${config.mPlatfom}/${config.appId}/subscriptions`, + qs: { + access_token: config.appId + "|" + config.appSecret, + object: "page", + callback_url: config.webhookUrl, + verify_token: config.verifyToken, + fields: + "messages, messaging_postbacks, messaging_optins, message_deliveries, messaging_referrals", + include_values: "true" + }, + method: "POST" + }, + (error, _res, body) => { + if (!error) { + console.log("Request sent:", body); + } else { + console.error("Unable to send message:", error); + } + } + ); + } + + static callSubscribedApps() { + // Send the HTTP request to subscribe an app for Webhooks for Pages + // You can use the Graph API's /{page-id}/subscribed_apps edge to configure and manage your pages subscriptions + // https://developers.facebook.com/docs/graph-api/reference/page/subscribed_apps + console.log(`Subscribing app ${config.appId} to page ${config.pageId}`); + request( + { + uri: `${config.mPlatfom}/${config.pageId}/subscribed_apps`, + qs: { + access_token: config.pageAccesToken, + subscribed_fields: + "messages, messaging_postbacks, messaging_optins, message_deliveries, messaging_referrals" + }, + method: "POST" + }, + (error, _res, body) => { + if (error) { + console.error("Unable to send message:", error); + } + } + ); + } + + static async getUserProfile(senderPsid) { + try { + const userProfile = await this.callUserProfileAPI(senderPsid); + + for (const key in userProfile) { + const camelizedKey = camelCase(key); + const value = userProfile[key]; + delete userProfile[key]; + userProfile[camelizedKey] = value; + } + + return userProfile; + } catch (err) { + console.log("Fetch failed:", err); + } + } + + static callUserProfileAPI(senderPsid) { + return new Promise(function(resolve, reject) { + let body = []; + + // Send the HTTP request to the Graph API + request({ + uri: `${config.mPlatfom}/${senderPsid}`, + qs: { + access_token: config.pageAccesToken, + fields: "first_name, last_name, gender, locale, timezone" + }, + method: "GET" + }) + .on("response", function(response) { + // console.log(response.statusCode); + + if (response.statusCode !== 200) { + reject(Error(response.statusCode)); + } + }) + .on("data", function(chunk) { + body.push(chunk); + }) + .on("error", function(error) { + console.error("Unable to fetch profile:" + error); + reject(Error("Network Error")); + }) + .on("end", () => { + body = Buffer.concat(body).toString(); + // console.log(JSON.parse(body)); + + resolve(JSON.parse(body)); + }); + }); + } + + static getPersonaAPI() { + return new Promise(function(resolve, reject) { + let body = []; + + // Send the POST request to the Personas API + console.log(`Fetching personas for app ${config.appId}`); + + request({ + uri: `${config.mPlatfom}/me/personas`, + qs: { + access_token: config.pageAccesToken + }, + method: "GET" + }) + .on("response", function(response) { + // console.log(response.statusCode); + + if (response.statusCode !== 200) { + reject(Error(response.statusCode)); + } + }) + .on("data", function(chunk) { + body.push(chunk); + }) + .on("error", function(error) { + console.error("Unable to fetch personas:" + error); + reject(Error("Network Error")); + }) + .on("end", () => { + body = Buffer.concat(body).toString(); + // console.log(JSON.parse(body)); + + resolve(JSON.parse(body).data); + }); + }); + } + + static postPersonaAPI(name, profile_picture_url) { + let body = []; + + return new Promise(function(resolve, reject) { + // Send the POST request to the Personas API + console.log(`Creating a Persona for app ${config.appId}`); + + let requestBody = { + name: name, + profile_picture_url: profile_picture_url + }; + + request({ + uri: `${config.mPlatfom}/me/personas`, + qs: { + access_token: config.pageAccesToken + }, + method: "POST", + json: requestBody + }) + .on("response", function(response) { + // console.log(response.statusCode); + if (response.statusCode !== 200) { + reject(Error(response.statusCode)); + } + }) + .on("data", function(chunk) { + body.push(chunk); + }) + .on("error", function(error) { + console.error("Unable to create a persona:", error); + reject(Error("Network Error")); + }) + .on("end", () => { + body = Buffer.concat(body).toString(); + // console.log(JSON.parse(body)); + + resolve(JSON.parse(body).id); + }); + }).catch(error => { + console.error("Unable to create a persona:", error, body); + }); + } + + static callFBAEventsAPI(senderPsid, eventName) { + // Construct the message body + let requestBody = { + event: "CUSTOM_APP_EVENTS", + custom_events: JSON.stringify([ + { + _eventName: "postback_payload", + _value: eventName, + _origin: "original_coast_clothing" + } + ]), + advertiser_tracking_enabled: 1, + application_tracking_enabled: 1, + extinfo: JSON.stringify(["mb1"]), + page_id: config.pageId, + page_scoped_user_id: senderPsid + }; + + // Send the HTTP request to the Activities API + request( + { + uri: `${config.mPlatfom}/${config.appId}/activities`, + method: "POST", + form: requestBody + }, + (error, res, _body) => { + if (!error) { + console.log(`FBA event '${eventName}'`); + } else { + console.error(`Unable to send FBA event '${eventName}':` + error); + } + } + ); + } +}; diff --git a/services/order.js b/services/order.js new file mode 100644 index 00000000..ebf411c1 --- /dev/null +++ b/services/order.js @@ -0,0 +1,65 @@ +/** + * Copyright 2019-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + * + * Messenger For Original Coast Clothing + * https://developers.facebook.com/docs/messenger-platform/getting-started/sample-apps/original-coast-clothing + */ + +"use strict"; + +// Imports dependencies +const Response = require("./response"), + i18n = require("../i18n.config"), + config = require("./config"); + +module.exports = class Order { + static handlePayload(payload, _senderPsid) { + let response; + + switch (payload) { + case "TRACK_ORDER": + response = Response.genQuickReply(i18n.__("order.prompt"), [ + { + title: i18n.__("order.account"), + payload: "LINK_ORDER" + }, + { + title: i18n.__("order.search"), + payload: "SEARCH_ORDER" + }, + { + title: i18n.__("menu.help"), + payload: "CARE_ORDER" + } + ]); + break; + + case "SEARCH_ORDER": + response = Response.genText(i18n.__("order.number")); + break; + + case "ORDER_NUMBER": + response = Response.genImageTemplate( + `${config.appUrl}/order.png`, + i18n.__("order.status") + ); + break; + + case "LINK_ORDER": + response = [ + Response.genText(i18n.__("order.dialog")), + Response.genText(i18n.__("order.searching")), + Response.genImageTemplate( + `${config.appUrl}/order.png`, + i18n.__("order.status") + ) + ]; + break; + } + + return response; + } +}; diff --git a/services/profile.js b/services/profile.js new file mode 100644 index 00000000..2407d43f --- /dev/null +++ b/services/profile.js @@ -0,0 +1,178 @@ +/** + * Copyright 2019-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + * + * Messenger For Original Coast Clothing + * https://developers.facebook.com/docs/messenger-platform/getting-started/sample-apps/original-coast-clothing + */ + +"use strict"; + +// Imports dependencies +const GraphAPi = require("./graph-api"), + i18n = require("../i18n.config"), + config = require("./config"), + locales = i18n.getLocales(); + +module.exports = class Profile { + setWebhook() { + GraphAPi.callSubscriptionsAPI(); + GraphAPi.callSubscribedApps(); + } + + setThread() { + let profilePayload = { + ...this.getGetStarted(), + ...this.getGreeting(), + ...this.getPersistentMenu() + }; + + GraphAPi.callMessengerProfileAPI(profilePayload); + } + + setPersonas() { + let newPersonas = config.newPersonas; + + GraphAPi.getPersonaAPI() + .then(personas => { + for (let persona of personas) { + config.pushPersona({ + name: persona.name, + id: persona.id + }); + } + console.log(config.personas); + return config.personas; + }) + .then(existingPersonas => { + for (let persona of newPersonas) { + if (!(persona.name in existingPersonas)) { + GraphAPi.postPersonaAPI(persona.name, persona.picture) + .then(personaId => { + config.pushPersona({ + name: persona.name, + id: personaId + }); + console.log(config.personas); + }) + .catch(error => { + console.log("Creation failed:", error); + }); + } else { + console.log("Persona already exists for name:", persona.name); + } + } + }) + .catch(error => { + console.log("Creation failed:", error); + }); + } + + setGetStarted() { + let getStartedPayload = this.getGetStarted(); + GraphAPi.callMessengerProfileAPI(getStartedPayload); + } + + setGreeting() { + let greetingPayload = this.getGreeting(); + GraphAPi.callMessengerProfileAPI(greetingPayload); + } + + setPersistentMenu() { + let menuPayload = this.getPersistentMenu(); + GraphAPi.callMessengerProfileAPI(menuPayload); + } + + getGetStarted() { + return { + get_started: { + payload: "GET_STARTED" + } + }; + } + + getGreeting() { + let greetings = []; + + for (let locale of locales) { + greetings.push(this.getGreetingText(locale)); + } + + return { + greeting: greetings + }; + } + + getPersistentMenu() { + let menuItems = []; + + for (let locale of locales) { + menuItems.push(this.getMenuItems(locale)); + } + + return { + persistent_menu: menuItems + }; + } + + getGreetingText(locale) { + let param = locale === "en_US" ? "default" : locale; + + i18n.setLocale(locale); + + let localizedGreeting = { + locale: param, + text: i18n.__("profile.greeting", { + user_first_name: "{{user_first_name}}" + }) + }; + + console.log(localizedGreeting); + return localizedGreeting; + } + + getMenuItems(locale) { + let param = locale === "en_US" ? "default" : locale; + + i18n.setLocale(locale); + + let localizedMenu = { + locale: param, + composer_input_disabled: false, + call_to_actions: [ + { + title: i18n.__("menu.support"), + type: "nested", + call_to_actions: [ + { + title: i18n.__("menu.order"), + type: "postback", + payload: "TRACK_ORDER" + }, + { + title: i18n.__("menu.help"), + type: "postback", + payload: "CARE_HELP" + } + ] + }, + { + title: i18n.__("menu.suggestion"), + type: "postback", + payload: "CURATION" + }, + { + type: "web_url", + title: i18n.__("menu.shop"), + url: config.shopUrl, + webview_height_ratio: "full" + } + ] + }; + + console.log(localizedMenu); + return localizedMenu; + } +}; diff --git a/services/receive.js b/services/receive.js new file mode 100644 index 00000000..98515f6b --- /dev/null +++ b/services/receive.js @@ -0,0 +1,233 @@ +/** + * Copyright 2019-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + * + * Messenger For Original Coast Clothing + * https://developers.facebook.com/docs/messenger-platform/getting-started/sample-apps/original-coast-clothing + */ + +"use strict"; + +const Curation = require("./curation"), + Order = require("./order"), + Response = require("./response"), + Care = require("./care"), + Survey = require("./survey"), + GraphAPi = require("./graph-api"), + i18n = require("../i18n.config"); + +module.exports = class Receive { + constructor(user, webhookEvent) { + this.user = user; + this.webhookEvent = webhookEvent; + } + + // Check if the event is a message or postback and + // call the appropriate handler function + handleMessage() { + let event = this.webhookEvent; + + let responses; + + try { + if (event.message) { + let message = event.message; + + if (message.quick_reply) { + responses = this.handleQuickReply(); + } else if (message.attachments) { + responses = this.handleAttachmentMessage(); + } else if (message.text) { + responses = this.handleTextMessage(); + } + } else if (event.postback) { + responses = this.handlePostback(); + } else if (event.referral) { + responses = this.handleReferral(); + } + } catch (error) { + console.error(error); + responses = { + text: `An error has occured: '${error}'. We have been notified and will fix the issue shortly!` + }; + } + + if (Array.isArray(responses)) { + let delay = 0; + for (let response of responses) { + this.sendMessage(response, delay * 2000); + delay++; + } + } else { + this.sendMessage(responses); + } + } + + // Handles messages events with text + handleTextMessage() { + console.log( + "Recieved text:", + `${this.webhookEvent.message.text} for ${this.user.psid}` + ); + + // check greeting is here and is confident + let greeting = this.firstEntity(this.webhookEvent.message.nlp, "greetings"); + + let message = this.webhookEvent.message.text.trim().toLowerCase(); + + let response; + + if ( + (greeting && greeting.confidence > 0.8) || + message.includes("start over") + ) { + response = Response.genNuxMessage(this.user); + } else if (Number(message)) { + response = Order.handlePayload("ORDER_NUMBER"); + } else if (message.includes("#")) { + response = Survey.handlePayload("CSAT_SUGGESTION"); + } else if (message.includes(i18n.__("care.help").toLowerCase())) { + let care = new Care(this.user, this.webhookEvent); + response = care.handlePayload("CARE_HELP"); + } else { + response = [ + Response.genText( + i18n.__("fallback.any", { + message: this.webhookEvent.message.text + }) + ), + Response.genText(i18n.__("get_started.guidance")), + Response.genQuickReply(i18n.__("get_started.help"), [ + { + title: i18n.__("menu.suggestion"), + payload: "CURATION" + }, + { + title: i18n.__("menu.help"), + payload: "CARE_HELP" + } + ]) + ]; + } + + return response; + } + + // Handles mesage events with attachments + handleAttachmentMessage() { + let response; + + // Get the attachment + let attachment = this.webhookEvent.message.attachments[0]; + console.log("Received attachment:", `${attachment} for ${this.user.psid}`); + + response = Response.genQuickReply(i18n.__("fallback.attachment"), [ + { + title: i18n.__("menu.help"), + payload: "CARE_HELP" + }, + { + title: i18n.__("menu.start_over"), + payload: "GET_STARTED" + } + ]); + + return response; + } + + // Handles mesage events with quick replies + handleQuickReply() { + // Get the payload of the quick reply + let payload = this.webhookEvent.message.quick_reply.payload; + + return this.handlePayload(payload); + } + + // Handles postbacks events + handlePostback() { + // Get the payload of the postback + let payload = this.webhookEvent.postback.payload; + + return this.handlePayload(payload); + } + + // Handles referral events + handleReferral() { + // Get the payload of the postback + let payload = this.webhookEvent.referral.ref.toUpperCase(); + + return this.handlePayload(payload); + } + + handlePayload(payload) { + console.log("Received Payload:", `${payload} for ${this.user.psid}`); + + // Log CTA event in FBA + GraphAPi.callFBAEventsAPI(this.user.psid, payload); + + let response; + + // Set the response based on the payload + if ( + payload === "GET_STARTED" || + payload === "DEVDOCS" || + payload === "GITHUB" + ) { + response = Response.genNuxMessage(this.user); + } else if (payload.includes("CURATION") || payload.includes("COUPON")) { + let curation = new Curation(this.user, this.webhookEvent); + response = curation.handlePayload(payload); + } else if (payload.includes("CARE")) { + let care = new Care(this.user, this.webhookEvent); + response = care.handlePayload(payload); + } else if (payload.includes("ORDER")) { + response = Order.handlePayload(payload); + } else if (payload.includes("CSAT")) { + response = Survey.handlePayload(payload); + } else { + response = { + text: `This is a default postback message for payload: ${payload}!` + }; + } + + return response; + } + + sendMessage(response, delay = 0) { + // Check if there is delay in the response + if ("delay" in response) { + delay = response["delay"]; + delete response["delay"]; + } + + // Construct the message body + let requestBody = { + recipient: { + id: this.user.psid + }, + message: response + }; + + // Check if there is persona id in the response + if ("persona_id" in response) { + let persona_id = response["persona_id"]; + delete response["persona_id"]; + + requestBody = { + recipient: { + id: this.user.psid + }, + message: response, + persona_id: persona_id + }; + } + + setTimeout(() => GraphAPi.callSendAPI(requestBody), delay); + } + + firstEntity(nlp, name) { + return nlp && nlp.entities && nlp.entities[name] && nlp.entities[name][0]; + } +}; diff --git a/services/response.js b/services/response.js new file mode 100644 index 00000000..cb0045dd --- /dev/null +++ b/services/response.js @@ -0,0 +1,148 @@ +/** + * Copyright 2019-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + * + * Messenger For Original Coast Clothing + * https://developers.facebook.com/docs/messenger-platform/getting-started/sample-apps/original-coast-clothing + */ + +"use strict"; + +const i18n = require("../i18n.config"); + +module.exports = class Response { + static genQuickReply(text, quickReplies) { + let response = { + text: text, + quick_replies: [] + }; + + for (let quickReply of quickReplies) { + response["quick_replies"].push({ + content_type: "text", + title: quickReply["title"], + payload: quickReply["payload"] + }); + } + + return response; + } + + static genGenericTemplate(image_url, title, subtitle, buttons) { + let response = { + attachment: { + type: "template", + payload: { + template_type: "generic", + elements: [ + { + title: title, + subtitle: subtitle, + image_url: image_url, + buttons: buttons + } + ] + } + } + }; + + return response; + } + + static genImageTemplate(image_url, title, subtitle = "") { + let response = { + attachment: { + type: "template", + payload: { + template_type: "generic", + elements: [ + { + title: title, + subtitle: subtitle, + image_url: image_url + } + ] + } + } + }; + + return response; + } + + static genButtonTemplate(title, buttons) { + let response = { + attachment: { + type: "template", + payload: { + template_type: "button", + text: title, + buttons: buttons + } + } + }; + + return response; + } + + static genText(text) { + let response = { + text: text + }; + + return response; + } + + static genTextWithPersona(text, persona_id) { + let response = { + text: text, + persona_id: persona_id + }; + + return response; + } + + static genPostbackButton(title, payload) { + let response = { + type: "postback", + title: title, + payload: payload + }; + + return response; + } + + static genWebUrlButton(title, url) { + let response = { + type: "web_url", + title: title, + url: url + }; + + return response; + } + + static genNuxMessage(user) { + let welcome = this.genText( + i18n.__("get_started.welcome", { + userFirstName: user.firstName + }) + ); + + let guide = this.genText(i18n.__("get_started.guidance")); + + let curation = this.genQuickReply(i18n.__("get_started.help"), [ + { + title: i18n.__("menu.suggestion"), + payload: "CURATION" + }, + { + title: i18n.__("menu.help"), + payload: "CARE_HELP" + } + ]); + + return [welcome, guide, curation]; + } +}; diff --git a/services/survey.js b/services/survey.js new file mode 100644 index 00000000..ba5c0941 --- /dev/null +++ b/services/survey.js @@ -0,0 +1,73 @@ +/** + * Copyright 2019-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + * + * Messenger For Original Coast Clothing + * https://developers.facebook.com/docs/messenger-platform/getting-started/sample-apps/original-coast-clothing + */ + +"use strict"; + +// Imports dependencies +const Response = require("./response"), + i18n = require("../i18n.config"); + +module.exports = class Survey { + static genAgentRating(agent) { + let response = Response.genQuickReply( + i18n.__("survey.prompt", { + agentFirstName: agent + }), + [ + { + title: "😀", + payload: "CSAT_GOOD" + }, + { + title: "🙂", + payload: "CSAT_AVERAGE" + }, + { + title: "🙁", + payload: "CSAT_BAD" + } + ] + ); + + // This is only triggered 1 min after talking with an agent + response.delay = "60000"; + + return response; + } + + static handlePayload(payload) { + let response; + + switch (payload) { + case "CSAT_GOOD": + response = Response.genText(i18n.__("survey.positive")); + break; + + case "CSAT_AVERAGE": + response = Response.genText(i18n.__("survey.neutral")); + break; + + case "CSAT_BAD": + response = Response.genQuickReply(i18n.__("survey.negative"), [ + { + title: i18n.__("menu.help"), + payload: "CARE_HELP" + } + ]); + break; + + case "CSAT_SUGGESTION": + response = Response.genText(i18n.__("survey.suggestion")); + break; + } + + return response; + } +}; diff --git a/services/user.js b/services/user.js new file mode 100644 index 00000000..c609ad35 --- /dev/null +++ b/services/user.js @@ -0,0 +1,31 @@ +/** + * Copyright 2019-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + * + * Messenger For Original Coast Clothing + * https://developers.facebook.com/docs/messenger-platform/getting-started/sample-apps/original-coast-clothing + */ + +"use strict"; + +module.exports = class User { + constructor(psid) { + this.psid = psid; + this.firstName = ""; + this.lastName = ""; + this.locale = ""; + this.timezone = ""; + this.gender = "neutral"; + } + setProfile(profile) { + this.firstName = profile.firstName; + this.lastName = profile.lastName; + this.locale = profile.locale; + this.timezone = profile.timezone; + if (profile.gender) { + this.gender = profile.gender; + } + } +}; diff --git a/views/footer.ejs b/views/footer.ejs new file mode 100644 index 00000000..98709269 --- /dev/null +++ b/views/footer.ejs @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/views/header.ejs b/views/header.ejs new file mode 100644 index 00000000..73c8a7b4 --- /dev/null +++ b/views/header.ejs @@ -0,0 +1,18 @@ + + + + + + + + + Original Coast Clothing + + + + diff --git a/views/index.ejs b/views/index.ejs new file mode 100644 index 00000000..7d1d7196 --- /dev/null +++ b/views/index.ejs @@ -0,0 +1,29 @@ +<% include header %> + +
+

Original Coast Clothing

+ +

+ We’re open 7 days, so start your shopping experience now! +

+

+ This site is part of the Sample Messenger Bot experience of Original Coast + Clothing +

+ +
+ +<% include footer %> diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 00000000..a8bb382f --- /dev/null +++ b/yarn.lock @@ -0,0 +1,1647 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0.tgz#06e2ab19bdb535385559aabb5ba59729482800f8" + integrity sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA== + dependencies: + "@babel/highlight" "^7.0.0" + +"@babel/highlight@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.0.0.tgz#f710c38c8d458e6dd9a201afb637fcb781ce99e4" + integrity sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw== + dependencies: + chalk "^2.0.0" + esutils "^2.0.2" + js-tokens "^4.0.0" + +abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + +accepts@~1.3.5: + version "1.3.5" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.5.tgz#eb777df6011723a3b14e8a72c0805c8e86746bd2" + integrity sha1-63d99gEXI6OxTopywIBcjoZ0a9I= + dependencies: + mime-types "~2.1.18" + negotiator "0.6.1" + +acorn-jsx@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.0.1.tgz#32a064fd925429216a09b141102bfdd185fae40e" + integrity sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg== + +acorn@^6.0.7: + version "6.1.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.1.1.tgz#7d25ae05bb8ad1f9b699108e1094ecd7884adc1f" + integrity sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA== + +ajv@^6.5.5, ajv@^6.9.1: + version "6.10.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.0.tgz#90d0d54439da587cd7e843bfb7045f50bd22bdf1" + integrity sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg== + dependencies: + fast-deep-equal "^2.0.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ambi@^2.2.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/ambi/-/ambi-2.5.0.tgz#7c8e372be48891157e7cea01cb6f9143d1f74220" + integrity sha1-fI43K+SIkRV+fOoBy2+RQ9H3QiA= + dependencies: + editions "^1.1.1" + typechecker "^4.3.0" + +ansi-escapes@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" + integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= + +ansi-regex@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" + integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== + +ansi-styles@^3.2.0, ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= + +asn1@~0.2.3: + version "0.2.4" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" + integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== + dependencies: + safer-buffer "~2.1.0" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= + +astral-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" + integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== + +async@~1.5.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" + integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo= + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= + +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= + +aws4@^1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" + integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ== + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +bcrypt-pbkdf@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= + dependencies: + tweetnacl "^0.14.3" + +body-parser@1.18.3, body-parser@^1.18.3: + version "1.18.3" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.3.tgz#5b292198ffdd553b3a0f20ded0592b956955c8b4" + integrity sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ= + dependencies: + bytes "3.0.0" + content-type "~1.0.4" + debug "2.6.9" + depd "~1.1.2" + http-errors "~1.6.3" + iconv-lite "0.4.23" + on-finished "~2.3.0" + qs "6.5.2" + raw-body "2.3.3" + type-is "~1.6.16" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +bytes@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" + integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camelcase@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.2.0.tgz#e7522abda5ed94cc0489e1b8466610e88404cf45" + integrity sha512-IXFsBS2pC+X0j0N/GE7Dm7j3bsEBp+oTpb7F50dwEVX7rf3IgwO9XatnegTsDtniKCUtEJH4fSU6Asw7uoVLfQ== + +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= + +chalk@^2.0.0, chalk@^2.1.0, chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chardet@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" + integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== + +cli-cursor@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" + integrity sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU= + dependencies: + restore-cursor "^2.0.0" + +cli-width@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" + integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk= + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +combined-stream@^1.0.6, combined-stream@~1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.7.tgz#2d1d24317afb8abe95d6d2c0b07b57813539d828" + integrity sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w== + dependencies: + delayed-stream "~1.0.0" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +content-disposition@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" + integrity sha1-DPaLud318r55YcOoUXjLhdunjLQ= + +content-type@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" + integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= + +cookie@0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" + integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s= + +core-util-is@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + +cross-spawn@^6.0.5: + version "6.0.5" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== + dependencies: + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" + shebang-command "^1.2.0" + which "^1.2.9" + +csextends@^1.0.3: + version "1.2.0" + resolved "https://registry.yarnpkg.com/csextends/-/csextends-1.2.0.tgz#6374b210984b54d4495f29c99d3dd069b80543e5" + integrity sha512-S/8k1bDTJIwuGgQYmsRoE+8P+ohV32WhQ0l4zqrc0XDdxOhjQQD7/wTZwCzoZX53jSX3V/qwjT+OkPTxWQcmjg== + +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= + dependencies: + assert-plus "^1.0.0" + +debug@*, debug@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" + integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== + dependencies: + ms "^2.1.1" + +debug@2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +deep-is@~0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" + integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= + +depd@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= + +destroy@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" + integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +dotenv@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-6.2.0.tgz#941c0410535d942c8becf28d3f357dbd9d476064" + integrity sha512-HygQCKUBSFl8wKQZBSemMywRWcEDNidvNbjGVyZu3nbZ8qq9ubiPoGLMdRDpfSrpkkm9BXYFkpKxxFX38o/76w== + +eachr@^2.0.2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/eachr/-/eachr-2.0.4.tgz#466f7caa10708f610509e32c807aafe57fc122bf" + integrity sha1-Rm98qhBwj2EFCeMsgHqv5X/BIr8= + dependencies: + typechecker "^2.0.8" + +ecc-jsbn@~0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" + integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= + dependencies: + jsbn "~0.1.0" + safer-buffer "^2.1.0" + +editions@^1.1.1, editions@^1.3.3: + version "1.3.4" + resolved "https://registry.yarnpkg.com/editions/-/editions-1.3.4.tgz#3662cb592347c3168eb8e498a0ff73271d67f50b" + integrity sha512-gzao+mxnYDzIysXKMQi/+M1mjy/rjestjg6OPoYTtI+3Izp23oiGZitsl9lPDPiTGXbcSIk1iJWhliSaglxnUg== + +editions@^2.1.0, editions@^2.1.2: + version "2.1.3" + resolved "https://registry.yarnpkg.com/editions/-/editions-2.1.3.tgz#727ccf3ec2c7b12dcc652c71000f16c4824d6f7d" + integrity sha512-xDZyVm0A4nLgMNWVVLJvcwMjI80ShiH/27RyLiCnW1L273TcJIA25C4pwJ33AWV01OX6UriP35Xu+lH4S7HWQw== + dependencies: + errlop "^1.1.1" + semver "^5.6.0" + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= + +ejs@^2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.6.1.tgz#498ec0d495655abc6f23cd61868d926464071aa0" + integrity sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ== + +emoji-regex@^7.0.1: + version "7.0.3" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" + integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= + +errlop@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/errlop/-/errlop-1.1.1.tgz#d9ae4c76c3e64956c5d79e6e035d6343bfd62250" + integrity sha512-WX7QjiPHhsny7/PQvrhS5VMizXXKoKCS3udaBp8gjlARdbn+XmK300eKBAAN0hGyRaTCtRpOaxK+xFVPUJ3zkw== + dependencies: + editions "^2.1.2" + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +eslint-config-prettier@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-4.1.0.tgz#181364895899fff9fd3605fecb5c4f20e7d5f395" + integrity sha512-zILwX9/Ocz4SV2vX7ox85AsrAgXV3f2o2gpIicdMIOra48WYqgUnWNH/cR/iHtmD2Vb3dLSC3LiEJnS05Gkw7w== + dependencies: + get-stdin "^6.0.0" + +eslint-plugin-prettier@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.0.1.tgz#19d521e3981f69dd6d14f64aec8c6a6ac6eb0b0d" + integrity sha512-/PMttrarPAY78PLvV3xfWibMOdMDl57hmlQ2XqFeA37wd+CJ7WSxV7txqjVPHi/AAFKd2lX0ZqfsOc/i5yFCSQ== + dependencies: + prettier-linter-helpers "^1.0.0" + +eslint-scope@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" + integrity sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg== + dependencies: + esrecurse "^4.1.0" + estraverse "^4.1.1" + +eslint-utils@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.3.1.tgz#9a851ba89ee7c460346f97cf8939c7298827e512" + integrity sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q== + +eslint-visitor-keys@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d" + integrity sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ== + +eslint@^5.16.0: + version "5.16.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-5.16.0.tgz#a1e3ac1aae4a3fbd8296fcf8f7ab7314cbb6abea" + integrity sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg== + dependencies: + "@babel/code-frame" "^7.0.0" + ajv "^6.9.1" + chalk "^2.1.0" + cross-spawn "^6.0.5" + debug "^4.0.1" + doctrine "^3.0.0" + eslint-scope "^4.0.3" + eslint-utils "^1.3.1" + eslint-visitor-keys "^1.0.0" + espree "^5.0.1" + esquery "^1.0.1" + esutils "^2.0.2" + file-entry-cache "^5.0.1" + functional-red-black-tree "^1.0.1" + glob "^7.1.2" + globals "^11.7.0" + ignore "^4.0.6" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + inquirer "^6.2.2" + js-yaml "^3.13.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.3.0" + lodash "^4.17.11" + minimatch "^3.0.4" + mkdirp "^0.5.1" + natural-compare "^1.4.0" + optionator "^0.8.2" + path-is-inside "^1.0.2" + progress "^2.0.0" + regexpp "^2.0.1" + semver "^5.5.1" + strip-ansi "^4.0.0" + strip-json-comments "^2.0.1" + table "^5.2.3" + text-table "^0.2.0" + +espree@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-5.0.1.tgz#5d6526fa4fc7f0788a5cf75b15f30323e2f81f7a" + integrity sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A== + dependencies: + acorn "^6.0.7" + acorn-jsx "^5.0.0" + eslint-visitor-keys "^1.0.0" + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.1.tgz#406c51658b1f5991a5f9b62b1dc25b00e3e5c708" + integrity sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA== + dependencies: + estraverse "^4.0.0" + +esrecurse@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.1.tgz#007a3b9fdbc2b3bb87e4879ea19c92fdbd3942cf" + integrity sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ== + dependencies: + estraverse "^4.1.0" + +estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1: + version "4.2.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" + integrity sha1-De4/7TH81GlhjOc0IJn8GvoL2xM= + +esutils@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" + integrity sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs= + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= + +express@^4.16.4: + version "4.16.4" + resolved "https://registry.yarnpkg.com/express/-/express-4.16.4.tgz#fddef61926109e24c515ea97fd2f1bdbf62df12e" + integrity sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg== + dependencies: + accepts "~1.3.5" + array-flatten "1.1.1" + body-parser "1.18.3" + content-disposition "0.5.2" + content-type "~1.0.4" + cookie "0.3.1" + cookie-signature "1.0.6" + debug "2.6.9" + depd "~1.1.2" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "1.1.1" + fresh "0.5.2" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "~2.3.0" + parseurl "~1.3.2" + path-to-regexp "0.1.7" + proxy-addr "~2.0.4" + qs "6.5.2" + range-parser "~1.2.0" + safe-buffer "5.1.2" + send "0.16.2" + serve-static "1.13.2" + setprototypeof "1.1.0" + statuses "~1.4.0" + type-is "~1.6.16" + utils-merge "1.0.1" + vary "~1.1.2" + +extend@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +extendr@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/extendr/-/extendr-2.1.0.tgz#301aa0bbea565f4d2dc8f570f2a22611a8527b56" + integrity sha1-MBqgu+pWX00tyPVw8qImEahSe1Y= + dependencies: + typechecker "~2.0.1" + +external-editor@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.0.3.tgz#5866db29a97826dbe4bf3afd24070ead9ea43a27" + integrity sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA== + dependencies: + chardet "^0.7.0" + iconv-lite "^0.4.24" + tmp "^0.0.33" + +extract-opts@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/extract-opts/-/extract-opts-2.2.0.tgz#1fa28eba7352c6db480f885ceb71a46810be6d7d" + integrity sha1-H6KOunNSxttID4hc63GkaBC+bX0= + dependencies: + typechecker "~2.0.1" + +extsprintf@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= + +extsprintf@^1.2.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" + integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= + +fast-deep-equal@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" + integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= + +fast-diff@^1.1.2: + version "1.2.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" + integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== + +fast-json-stable-stringify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" + integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I= + +fast-levenshtein@~2.0.4: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + +figures@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" + integrity sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI= + dependencies: + escape-string-regexp "^1.0.5" + +file-entry-cache@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c" + integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g== + dependencies: + flat-cache "^2.0.1" + +finalhandler@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.1.tgz#eebf4ed840079c83f4249038c9d703008301b105" + integrity sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "~2.3.0" + parseurl "~1.3.2" + statuses "~1.4.0" + unpipe "~1.0.0" + +flat-cache@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" + integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA== + dependencies: + flatted "^2.0.0" + rimraf "2.6.3" + write "1.0.3" + +flatted@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.0.tgz#55122b6536ea496b4b44893ee2608141d10d9916" + integrity sha512-R+H8IZclI8AAkSBRQJLVOsxwAoHd6WC40b4QTNWIjzAa6BXOBfQcM587MXDTVPeYaopFNWHUFLx7eNmHDSxMWg== + +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= + +form-data@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" + integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + +forwarded@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" + integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= + +get-stdin@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b" + integrity sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g== + +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= + dependencies: + assert-plus "^1.0.0" + +glob@^7.1.2, glob@^7.1.3: + version "7.1.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" + integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@~6.0.4: + version "6.0.4" + resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22" + integrity sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI= + dependencies: + inflight "^1.0.4" + inherits "2" + minimatch "2 || 3" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^11.7.0: + version "11.11.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.11.0.tgz#dcf93757fa2de5486fbeed7118538adf789e9c2e" + integrity sha512-WHq43gS+6ufNOEqlrDBxVEbb8ntfXrfAUU2ZOpCxrBdGKW3gyv8mCxAfIBD0DroPKGrJ2eSsXsLtY9MPntsyTw== + +graceful-fs@*: + version "4.1.15" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" + integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA== + +har-schema@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= + +har-validator@~5.1.0: + version "5.1.3" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" + integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== + dependencies: + ajv "^6.5.5" + har-schema "^2.0.0" + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +http-errors@1.6.3, http-errors@~1.6.2, http-errors@~1.6.3: + version "1.6.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" + integrity sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0= + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.0" + statuses ">= 1.4.0 < 2" + +http-signature@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + +i18n@^0.8.3: + version "0.8.3" + resolved "https://registry.yarnpkg.com/i18n/-/i18n-0.8.3.tgz#2d8cf1c24722602c2041d01ba6ae5eaa51388f0e" + integrity sha1-LYzxwkciYCwgQdAbpq5eqlE4jw4= + dependencies: + debug "*" + make-plural "^3.0.3" + math-interval-parser "^1.1.0" + messageformat "^0.3.1" + mustache "*" + sprintf-js ">=1.0.3" + +iconv-lite@0.4.23: + version "0.4.23" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63" + integrity sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +iconv-lite@^0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +ignore@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" + integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== + +ignorefs@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/ignorefs/-/ignorefs-1.2.0.tgz#da59fb858976e4a5e43702ccd1f282fdbc9e5756" + integrity sha1-2ln7hYl25KXkNwLM0fKC/byeV1Y= + dependencies: + editions "^1.3.3" + ignorepatterns "^1.1.0" + +ignorepatterns@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/ignorepatterns/-/ignorepatterns-1.1.0.tgz#ac8f436f2239b5dfb66d5f0d3a904a87ac67cc5e" + integrity sha1-rI9DbyI5td+2bV8NOpBKh6xnzF4= + +import-fresh@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.0.0.tgz#a3d897f420cab0e671236897f75bc14b4885c390" + integrity sha512-pOnA9tfM3Uwics+SaBLCNyZZZbK+4PTu0OPZtLlMIrv17EdBoC15S9Kn8ckJ9TZTyKb3ywNE5y1yeDxxGA7nTQ== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + +inquirer@^6.2.2: + version "6.3.1" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.3.1.tgz#7a413b5e7950811013a3db491c61d1f3b776e8e7" + integrity sha512-MmL624rfkFt4TG9y/Jvmt8vdmOo836U7Y0Hxr2aFk3RelZEGX4Igk0KabWrcaaZaTv9uzglOqWh1Vly+FAWAXA== + dependencies: + ansi-escapes "^3.2.0" + chalk "^2.4.2" + cli-cursor "^2.1.0" + cli-width "^2.0.0" + external-editor "^3.0.3" + figures "^2.0.0" + lodash "^4.17.11" + mute-stream "0.0.7" + run-async "^2.2.0" + rxjs "^6.4.0" + string-width "^2.1.0" + strip-ansi "^5.1.0" + through "^2.3.6" + +ipaddr.js@1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.8.0.tgz#eaa33d6ddd7ace8f7f6fe0c9ca0440e706738b1e" + integrity sha1-6qM9bd16zo9/b+DJygRA5wZzix4= + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + +is-promise@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" + integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o= + +is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.0: + version "3.13.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" + integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema@0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" + integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= + +json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= + +jsprim@^1.2.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" + integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.2.3" + verror "1.10.0" + +levn@^0.3.0, levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +lodash@^4.17.11: + version "4.17.11" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" + integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== + +make-plural@^3.0.3, make-plural@~3.0.3: + version "3.0.6" + resolved "https://registry.yarnpkg.com/make-plural/-/make-plural-3.0.6.tgz#2033a03bac290b8f3bb91258f65b9df7e8b01ca7" + integrity sha1-IDOgO6wpC487uRJY9lud9+iwHKc= + optionalDependencies: + minimist "^1.2.0" + +math-interval-parser@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/math-interval-parser/-/math-interval-parser-1.1.0.tgz#dbeda5b06b3249973c6df6170fde2386f0afd893" + integrity sha1-2+2lsGsySZc8bfYXD94jhvCv2JM= + dependencies: + xregexp "^2.0.0" + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= + +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= + +messageformat@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/messageformat/-/messageformat-0.3.1.tgz#e58fff8245e9b3971799e5b43db58b3e9417f5a2" + integrity sha1-5Y//gkXps5cXmeW0PbWLPpQX9aI= + dependencies: + async "~1.5.2" + glob "~6.0.4" + make-plural "~3.0.3" + nopt "~3.0.6" + watchr "~2.4.13" + +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= + +mime-db@~1.38.0: + version "1.38.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.38.0.tgz#1a2aab16da9eb167b49c6e4df2d9c68d63d8e2ad" + integrity sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg== + +mime-types@^2.1.12, mime-types@~2.1.18, mime-types@~2.1.19: + version "2.1.22" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.22.tgz#fe6b355a190926ab7698c9a0556a11199b2199bd" + integrity sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog== + dependencies: + mime-db "~1.38.0" + +mime@1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" + integrity sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ== + +mimic-fn@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" + integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== + +"minimatch@2 || 3", minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimist@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= + +minimist@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= + +mkdirp@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= + dependencies: + minimist "0.0.8" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +ms@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== + +mustache@*: + version "3.0.1" + resolved "https://registry.yarnpkg.com/mustache/-/mustache-3.0.1.tgz#873855f23aa8a95b150fb96d9836edbc5a1d248a" + integrity sha512-jFI/4UVRsRYdUbuDTKT7KzfOp7FiD5WzYmmwNwXyUVypC0xjoTL78Fqc0jHUPIvvGD+6DQSPHIt1NE7D1ArsqA== + +mute-stream@0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" + integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= + +negotiator@0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" + integrity sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk= + +nice-try@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" + integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== + +nopt@~3.0.6: + version "3.0.6" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" + integrity sha1-xkZdvwirzU2zWTF/eaxopkayj/k= + dependencies: + abbrev "1" + +oauth-sign@~0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" + integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== + +on-finished@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" + integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= + dependencies: + ee-first "1.1.1" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +onetime@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" + integrity sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ= + dependencies: + mimic-fn "^1.0.0" + +optionator@^0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" + integrity sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q= + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.4" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + wordwrap "~1.0.0" + +os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parseurl@~1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3" + integrity sha1-/CidTtiZMRlGDBViUyYs3I3mW/M= + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-is-inside@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" + integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= + +path-key@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= + +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= + +path@^0.12.7: + version "0.12.7" + resolved "https://registry.yarnpkg.com/path/-/path-0.12.7.tgz#d4dc2a506c4ce2197eb481ebfcd5b36c0140b10f" + integrity sha1-1NwqUGxM4hl+tIHr/NWzbAFAsQ8= + dependencies: + process "^0.11.1" + util "^0.10.3" + +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= + +prettier-linter-helpers@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" + integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== + dependencies: + fast-diff "^1.1.2" + +prettier@1.17.0: + version "1.17.0" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.17.0.tgz#53b303676eed22cc14a9f0cec09b477b3026c008" + integrity sha512-sXe5lSt2WQlCbydGETgfm1YBShgOX4HxQkFPvbxkcwgDvGDeqVau8h+12+lmSVlP3rHPz0oavfddSZg/q+Szjw== + +process@^0.11.1: + version "0.11.10" + resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" + integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= + +progress@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + +proxy-addr@~2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.4.tgz#ecfc733bf22ff8c6f407fa275327b9ab67e48b93" + integrity sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA== + dependencies: + forwarded "~0.1.2" + ipaddr.js "1.8.0" + +psl@^1.1.24: + version "1.1.31" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.31.tgz#e9aa86d0101b5b105cbe93ac6b784cd547276184" + integrity sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw== + +punycode@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= + +punycode@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +qs@6.5.2, qs@~6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" + integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== + +range-parser@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" + integrity sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4= + +raw-body@2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.3.tgz#1b324ece6b5706e153855bc1148c65bb7f6ea0c3" + integrity sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw== + dependencies: + bytes "3.0.0" + http-errors "1.6.3" + iconv-lite "0.4.23" + unpipe "1.0.0" + +regexpp@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" + integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw== + +request@^2.88.0: + version "2.88.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" + integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.0" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + oauth-sign "~0.9.0" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.4.3" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +restore-cursor@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" + integrity sha1-n37ih/gv0ybU/RYpI9YhKe7g368= + dependencies: + onetime "^2.0.0" + signal-exit "^3.0.2" + +rimraf@2.6.3: + version "2.6.3" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" + integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== + dependencies: + glob "^7.1.3" + +run-async@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" + integrity sha1-A3GrSuC91yDUFm19/aZP96RFpsA= + dependencies: + is-promise "^2.1.0" + +rxjs@^6.4.0: + version "6.4.0" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.4.0.tgz#f3bb0fe7bda7fb69deac0c16f17b50b0b8790504" + integrity sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw== + dependencies: + tslib "^1.9.0" + +safe-buffer@5.1.2, safe-buffer@^5.0.1, safe-buffer@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safefs@^3.1.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/safefs/-/safefs-3.2.2.tgz#8170c1444d7038e08caea05a374fae2fa349e15c" + integrity sha1-gXDBRE1wOOCMrqBaN0+uL6NJ4Vw= + dependencies: + graceful-fs "*" + +"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +scandirectory@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/scandirectory/-/scandirectory-2.5.0.tgz#6ce03f54a090b668e3cbedbf20edf9e310593e72" + integrity sha1-bOA/VKCQtmjjy+2/IO354xBZPnI= + dependencies: + ignorefs "^1.0.0" + safefs "^3.1.2" + taskgroup "^4.0.5" + +semver@^5.5.0, semver@^5.5.1: + version "5.7.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b" + integrity sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA== + +semver@^5.6.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" + integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== + +send@0.16.2: + version "0.16.2" + resolved "https://registry.yarnpkg.com/send/-/send-0.16.2.tgz#6ecca1e0f8c156d141597559848df64730a6bbc1" + integrity sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw== + dependencies: + debug "2.6.9" + depd "~1.1.2" + destroy "~1.0.4" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "~1.6.2" + mime "1.4.1" + ms "2.0.0" + on-finished "~2.3.0" + range-parser "~1.2.0" + statuses "~1.4.0" + +serve-static@1.13.2: + version "1.13.2" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.2.tgz#095e8472fd5b46237db50ce486a43f4b86c6cec1" + integrity sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.2" + send "0.16.2" + +setprototypeof@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" + integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== + +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= + dependencies: + shebang-regex "^1.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= + +signal-exit@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" + integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= + +slice-ansi@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" + integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== + dependencies: + ansi-styles "^3.2.0" + astral-regex "^1.0.0" + is-fullwidth-code-point "^2.0.0" + +sprintf-js@>=1.0.3: + version "1.1.2" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673" + integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug== + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + +sshpk@^1.7.0: + version "1.16.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" + integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + bcrypt-pbkdf "^1.0.0" + dashdash "^1.12.0" + ecc-jsbn "~0.1.1" + getpass "^0.1.1" + jsbn "~0.1.0" + safer-buffer "^2.0.2" + tweetnacl "~0.14.0" + +"statuses@>= 1.4.0 < 2": + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= + +statuses@~1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087" + integrity sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew== + +string-width@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" + +string-width@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" + integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== + dependencies: + emoji-regex "^7.0.1" + is-fullwidth-code-point "^2.0.0" + strip-ansi "^5.1.0" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= + dependencies: + ansi-regex "^3.0.0" + +strip-ansi@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" + integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== + dependencies: + ansi-regex "^4.1.0" + +strip-json-comments@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +table@^5.2.3: + version "5.2.3" + resolved "https://registry.yarnpkg.com/table/-/table-5.2.3.tgz#cde0cc6eb06751c009efab27e8c820ca5b67b7f2" + integrity sha512-N2RsDAMvDLvYwFcwbPyF3VmVSSkuF+G1e+8inhBLtHpvwXGw4QRPEZhihQNeEN0i1up6/f6ObCJXNdlRG3YVyQ== + dependencies: + ajv "^6.9.1" + lodash "^4.17.11" + slice-ansi "^2.1.0" + string-width "^3.0.0" + +taskgroup@^4.0.5, taskgroup@^4.2.0: + version "4.3.1" + resolved "https://registry.yarnpkg.com/taskgroup/-/taskgroup-4.3.1.tgz#7de193febd768273c457730497024d512c27915a" + integrity sha1-feGT/r12gnPEV3MElwJNUSwnkVo= + dependencies: + ambi "^2.2.0" + csextends "^1.0.3" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= + +through@^2.3.6: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= + +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" + +tough-cookie@~2.4.3: + version "2.4.3" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" + integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ== + dependencies: + psl "^1.1.24" + punycode "^1.4.1" + +tslib@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" + integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ== + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= + dependencies: + safe-buffer "^5.0.1" + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= + +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= + dependencies: + prelude-ls "~1.1.2" + +type-is@~1.6.16: + version "1.6.16" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.16.tgz#f89ce341541c672b25ee7ae3c73dee3b2be50194" + integrity sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.18" + +typechecker@^2.0.8: + version "2.1.0" + resolved "https://registry.yarnpkg.com/typechecker/-/typechecker-2.1.0.tgz#d1c2093a54ff8a19f58cff877eeaa54f2242d383" + integrity sha1-0cIJOlT/ihn1jP+HfuqlTyJC04M= + +typechecker@^4.3.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/typechecker/-/typechecker-4.7.0.tgz#5249f427358f45b7250c4924fd4d01ed9ba435e9" + integrity sha512-4LHc1KMNJ6NDGO+dSM/yNfZQRtp8NN7psYrPHUblD62Dvkwsp3VShsbM78kOgpcmMkRTgvwdKOTjctS+uMllgQ== + dependencies: + editions "^2.1.0" + +typechecker@~2.0.1: + version "2.0.8" + resolved "https://registry.yarnpkg.com/typechecker/-/typechecker-2.0.8.tgz#e83da84bb64c584ccb345838576c40b0337db82e" + integrity sha1-6D2oS7ZMWEzLNFg4V2xAsDN9uC4= + +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= + +uri-js@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" + integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== + dependencies: + punycode "^2.1.0" + +util@^0.10.3: + version "0.10.4" + resolved "https://registry.yarnpkg.com/util/-/util-0.10.4.tgz#3aa0125bfe668a4672de58857d3ace27ecb76901" + integrity sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A== + dependencies: + inherits "2.0.3" + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= + +uuid@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" + integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== + +vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= + +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" + +watchr@~2.4.13: + version "2.4.13" + resolved "https://registry.yarnpkg.com/watchr/-/watchr-2.4.13.tgz#d74847bb4d6f90f61fe2c74f9f68662aa0e07601" + integrity sha1-10hHu01vkPYf4sdPn2hmKqDgdgE= + dependencies: + eachr "^2.0.2" + extendr "^2.1.0" + extract-opts "^2.2.0" + ignorefs "^1.0.0" + safefs "^3.1.2" + scandirectory "^2.5.0" + taskgroup "^4.2.0" + typechecker "^2.0.8" + +which@^1.2.9: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +wordwrap@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +write@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3" + integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig== + dependencies: + mkdirp "^0.5.1" + +xregexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-2.0.0.tgz#52a63e56ca0b84a7f3a5f3d61872f126ad7a5943" + integrity sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM=