[
  {
    "path": ".dockerignore",
    "content": "#\n# Hidden files ignore\n#\n.*\n\n#\n# Remove uneeded files from docker context\n#\n/api/node_modules\n/ui/node_modules\n/ui/dist\n/cli/**\n/deploy/**\n\n#\n# Config files to ignore\n#\n/ui/config/apiconfig.js\n/api/config/mailgunConfig.js\n"
  },
  {
    "path": ".gitignore",
    "content": "# Hidden files\n**/.*\n\n# lib / build folders folder\n**/node_modules\n**/bin\n**/dist\n**/gopath\n\n# UI build folder ignore\nui/build/*\n\n# deploy build files\ndeploy/firebase/functions\ndeploy/firebase/public\ndeploy/firebase/*.log\n\n# Remove out config files\napi/config/*.js\nui/config/apiconfig.js\n\n# Include sample config files\n!api/config/*.sample.js\n!api/config/cacheControl.js\n\n# Whitelist anchor files & .gitignore\n!.gitignore\n!.dockerignore\n!.travis.yml\n!**/.anchor"
  },
  {
    "path": ".travis.yml",
    "content": "sudo: false\nlanguage: go\n\n# Our CLI toolchain is based on node_js\nnode_js: \"8.10\"\n# Installation of subdependencies\nbefore_install:\n  # Install firebase tools\n  - npm install -g firebase-tools\n  # # Installing of uilicious toolchain\n  # - npm install -g uilicious-cli\n# Addons (for build debugging)\naddons:\n  apt:\n    packages:\n      - tree\n# Building and testing on travis\nscript:\n  - ./config.sh\n  - ./build.sh\n  - echo \"Displaying project file tree (for easy debugging)\" && tree -L 2\n# The actual deploy scripts\ndeploy:\n  - provider: script\n    skip_cleanup: true\n    script: cd ./deploy/firebase && ./deploy.sh --token \"$FIREBASE_TOKEN\" --project \"$FIREBASE_PROJECT\"\n    on:\n      branch: master\n# Dependencies caching (for NPM, and GO)\ncache:\n  directories:\n    - api/node_modules\n    - ui/node_modules\n    - cli/gopath\n"
  },
  {
    "path": "CODE-GUIDE.md",
    "content": "# Code Guide\nIf you are interested to developing Inboxkitten, this is a brief guide of how  Inboxkitten has been structured.\n\n## Main Components\n - API - that serves as the backbone to connect to MailGun\n - UI - the user interface that you see on <a href=\"https://inboxkitten.com\" target=\"_blank\">Inboxkitten</a> that interacts with the API\n - CLI - the command line tool that you can use to interact with the API\n\n### API\nUnder the `api` folder is where all the code resides for the API component. The API component utilizes `axios` to perform its requests to Mailgun.\n\n- The configuration settings of the API are all located under `config/`\n    - `mailgunConfig.js` is the configuration file that contains the keys and domains \n    - `mailgunConfig.sample.js` is the template that `./config.sh` used to write into `mailgunConfig.js`\n- The rest of the code resides in `src/`.\n    - The main point of entry in an ExpressJS setup is at `app-setup.js`. In this file, \n    - `mailgunReader.js` contains the underlying code that connects to Mailgun\n    - the `api` folder will contain the code that performs the validation of the params in the endpoint that the user called before sending over to `mailgunReader.js`.\n- The `test` folder contains the mocha test cases to check the `mailgunReader.js`.\n\nTo add any endpoints, it is recommended to create a prototype function in `mailgunReader.js` that performs the execution that connects to Mailgun. Following which, you should create the endpoint that user will be using as a new file under `src/api/` folder for easy maintenance.\n\n### UI\nThe UI component code is under `ui` folder. It is constructed using Vue.js for its frontend development and `axios` to perform to requests to API component.\n\n- The configuration settings of the UI are all located under `config/`\n    - `apiconfig.js` contains the configuration for connecting to API component as well as the domain to display on the UI.\n    - `apiconfig.sample.js` is the template used in `./config.sh` for writing into `apiconfig.js`\n    - The other configuration to be concerned would be the `shareConfig.js` where it is the settings for shareable features such as Twitter's tweeting and GitHub's fork.\n    - The other files are auto generated files by vue-cli.\n- The `src` folder contains the body for UI. It is separated into 3 folders.\n    - The `assets` will contain the images.\n    - The `components` will contain the components used in the UI.\n    - The `router` is an auto generated file but it is used to add subpaths to link to the components.\n    - The `scss` contains the styling used for Inboxkitten's UI.\n- The `dist` folder contains the files built using the `npm run build` command.\n- The `uilicious-test` is an uilicious test script that can be ran on [test.uilicious.com](https://test.uilicious.com) to check if your email has been received properly.\n\nThe main entrypoint will be the `App.vue` and by default the Vue router will direct to `landingpage.vue`.\n\n\n### CLI\nThe CLI is under the `cli` folder. There are only one file that performs the tasks to connect to the API component. It is `inboxkitten.go` under the `src` folder. The `go.sh` script is a custom go script that ensures the environment is within the `cli` folder."
  },
  {
    "path": "DEPLOY-GUIDE-LOCALHOST.md",
    "content": "# Developing on localhost / Custom deployment\n\nNote: You will still need to do the mail gun setup in the firebase guide.\n\nInstead of running `./config.sh`, you should setup the config files respectively for the deployment.\n\n## Running the api server\n\n**Configuring : api/config/mailgunConfig.js**\n\n```\nmodule.exports = {\n\t\"apiKey\" : \"<MAILGUN_API_KEY>\",\n\t\"emailDomain\" : \"<MAILGUN_EMAIL_DOMAIN>\",\n\t\"corsOrigin\" : \"http://localhost:8000\"\n}\n```\n\n+ MAILGUN_API_KEY : Mailgun private api key\n+ MAILGUN_EMAIL_DOMAIN : domain for mailgun \n+ UI_HOST : Url to the UI domain. `http://localhost:8000` is the default for the UI `npm run dev`, \n\n**Running the express.js API server**\n\n```\n\t# Assuming that you are on the root directory of Inboxkitten\n\t$ cd api\n\t\n\t# Start the server\n\t$ npm start\n```\n\nValidate your API server is online at `http://localhost:8000/api/v1/mail/list?recipient=hello-world`\n\nYou should see an empty array representing an empty inbox.\n\n## Running the ui server - in development mode\n\n**Configuring ui/config/apiconfig.js**\n```\nexport default {\n\tapiUrl: 'http://localhost:8000/api/v1/mail',\n\tdomain: '<MAILGUN_EMAIL_DOMAIN>'\n}\n```\n\n+ apiUrl : Api server to point to, `localhost:8000` is the default for the api server `npm start`\n+ MAILGUN_EMAIL_DOMAIN : domain for mailgun \n\n**Running the nodejs+webpack UI server**\n\n```\n\t# Assuming that you are on the root directory of Inboxkitten\n\t$ cd ui\n\t\n\t# If you do not have a http server, you can install one\n\t$ npm run dev\n```\n\nYou can now access it on `http://localhost:8000` and enjoy your kitten-ventures.\n\n## Running cli\n\nThis is built using the `./build.sh` script\n\n```\n\t# Assuming that you are on the root directory of Inboxkitten\n\t$ cd cli\n\t\n\t# You can immediately execute the executable\n\t$ ./bin/inboxkitten\n\t\n\t# Retrieving list of email\n\t$ ./bin/inboxkitten list exampleEmailName\n\t\n\t# Retrieving individual email\n\t$ ./bin/inboxkitten get sw eyJwIjpmYWxzZSwiayI6IjI3YzVkNmZkLTk5ZjQtNGY5MC1iYTM4LThiNDBhNTJmNzA1OCIsInMiOiI0NzFhZjYxYjA4IiwiYyI6InRhbmtiIn0=\n\t\n\t# Target different api endpoint\n\t$ ./bin/inboxkitten -api http://localhost:8000/api/v1 (list|get) [params]             \n```\nTo run without compilation of `inboxkitten.go` in the src/ folder\n```\n\t$ ./go.sh run src/inboxkitten.go\n```\n\n## Calling the API using curl\nIf you have your API running on port `8000`,\n```\n\t# Get list of email\n\t$ curl localhost:8000/api/v1/mail/list\\?recipient=hard-dust-64\n\n\t# Get individual email\n\t$ curl localhost:8000/api/v1/mail/list\\?mailKey=se-eyJwIjpmYWxzZSwiayI6ImVlMWNiMTAzLWZhZjMtNDg3Ni04MjI2LWE1YmE1ZTU3YzMxMiIsInMiOiI3NTdhNTY5ZGFkIiwiYyI6InRhbmtiIn0=\n```\n\nIf you have it hosted on the net, change the endpoint to where you have hosted it on :)"
  },
  {
    "path": "DEPLOY-GUIDE-SERVERLESS.md",
    "content": "# Serverless Deployment Guide\n\nFollow the 5 steps guide below to get started on Firebase!\n\n- [Step 0 - Clone Me](#step-0---clone-me)\n- [Step 1 - Setup Serverless provider](#step-1---mailgun--firebase-signup)\n- [Step 2 - Configuration](#step-2---configuration)\n- [Step 3 - Build the package](#step-3---build-the-package)\n- [Step 4 - Deployment](#step-4---deployment)\n\n> Also do let us know how we can help make this better 😺\n\n## Step 0 - Clone Me\n\n```\n\t$ git clone https://github.com/uilicious/inboxkitten.git\n```\n\n### Step 1a - Setup Firebase\n\n1. Go to <a href=\"https://firebase.google.com\" target=\"_blank\">Firebase</a> and click on `Get Started`.\n2. Sign in with your favorite Google account.\n3. Click on `Add Project` and create your own firebase inboxkitten project.\n4. Remember the project ID  \n\nOn your local machine where your InboxKitten is located at,\n```\n\t# Go to the root folder of InboxKitten\n\t$ cd <the place where you clone your inboxkitten>\n\t\n\t# Ensure that firebase CLI tool is installed\n\t$ npm install -g firebase-tools\n\t\n\t# Login to your firebase account\n\t$ firebase login\n\t\n\t# Set your firebase project\n\t$ firebase use --add <project name that you remembered>\n```\n\nOR\n\n### Step 1b - Setup Cloudflare Workers\n\n1. Go to <a href=\"https://cloudflare.com\" target=\"_blank\">Cloudflare</a> and signup with a domain.\n2. Setup cloudflare worker and get an API key\n___\n\n## Step 2 - Configuration\n\nIn the root directory of Inboxkitten, run the following command\n```\n\t$ ./config.sh\n```\n\nDuring the run time of `./config.sh`, there are three environment variables that is being used to set the configuration for your configuration files.\n\n1. `MAILGUN_EMAIL_DOMAIN` - any custom domain that you owned or the default domain in Mailgun\n2. `WEBSITE_DOMAIN`  - any custom domain that you owned. If you use your default firebase url, it will be `<Your project>.firebaseapp.com`\n3. `MAILGUN_API_KEY` - retrieve the api key from your Mailgun account\n\n<img src=\"./assets/configuration.png\" alt=\"configuration\" width=\"500px\"/>\n\n___\n\n## Step 3 - Build the package\n\n```\n\t$ ./build.sh\n```\n\n`./build.sh` will package the three components to be ready for deployment.\n\n___\n\n## Step 4 - Deployment\n\nFor API deployment on Firebase:\n\n```\n\t# Run the deployment script\n\t$ ./deploy/firebase/deploy.sh \n```\n\nFor API deployment on Cloudflare:\n\n```\n\t# Run the deployment script\n\t$ ./deploy/cloudflare/deploy.sh \n```"
  },
  {
    "path": "Dockerfile",
    "content": "#-------------------------------------------------------\n#\n# Base alpine images with all the runtime os dependencies\n#\n# Note that the node-sass is broken with node 12\n# https://github.com/nodejs/docker-node/issues/1028\n#\n#-------------------------------------------------------\n\n# Does basic node, and runtime dependencies\nFROM node:14-alpine AS baseimage\nRUN apk add --no-cache gettext\nRUN mkdir -p /application/\n# WORKDIR /application/\n\n#-------------------------------------------------------\n#\n# code builders (used by dockerbuilders)\n#\n#-------------------------------------------------------\n\n# Install dependencies for some NPM modules\nFROM baseimage AS codebuilder\n# RUN apk add --no-cache make gcc g++ python\n\n#-------------------------------------------------------\n#\n# Docker builders (also resets node_modules)\n#\n# Note each major dependency is compiled seperately\n# so as to isolate the impact of each code change\n#\n#-------------------------------------------------------\n\n# Build the API\n# with reseted node_modules\nFROM codebuilder AS apibuilder\n# copy and download dependencies\nCOPY api/package.json /application/api-mods/package.json\nRUN cd /application/api-mods/ && npm install\n# copy source code\nCOPY api /application/api/\nRUN rm -rf /application/api/node_modules\n# merge in dependnecies\nRUN cp -r /application/api-mods/node_modules /application/api/node_modules\nRUN ls /application/api/\n\n# Build the UI\n# with reseted node_modules\nFROM codebuilder AS uibuilder\n# copy and reset the code\nCOPY ui  /application/ui/\nRUN rm -rf /application/ui/node_modules\nRUN rm -rf /application/ui/dist\nRUN cd /application/ui  && ls && npm install\n# Lets do the UI build\nRUN cp /application/ui/config/apiconfig.sample.js /application/ui/config/apiconfig.js\nRUN cd /application/ui && npm run build\n\n# Entry script \n# & Permission reset\nFROM codebuilder AS entrypointbuilder\nCOPY docker-entrypoint.sh  /application/docker-entrypoint.sh\nRUN chmod +x /application/docker-entrypoint.sh\n\n#-------------------------------------------------------\n#\n# Full Docker application\n#\n#-------------------------------------------------------\nFROM baseimage as inboxkitten\n\n# Copy over the built files\nCOPY --from=apibuilder        /application/api                  /application/api\nCOPY --from=uibuilder         /application/ui/dist              /application/ui-dist\nCOPY --from=entrypointbuilder /application/docker-entrypoint.sh /application/docker-entrypoint.sh\n\n# Debugging logging\n# RUN ls /application/./\n# RUN ls /application/ui-dist\n# RUN ls /application/api\n\n# Expose the server port\nEXPOSE 8000\n\n#\n# Configurable environment variable\n#\nENV MAILGUN_EMAIL_DOMAIN=\"\"\nENV MAILGUN_API_KEY=\"\"\nENV WEBSITE_DOMAIN=\"\"\n\n# Setup the workdir\nWORKDIR \"/application/\"\n\n# Setup the entrypoint\nENTRYPOINT [ \"/application/docker-entrypoint.sh\" ]\nCMD []\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2018 Uilicious Private Limited.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "[![inboxkitten header](./ui/static/inbox-kitten-opengraph.jpg)](https://inboxkitten.com)\n\n# Open-Source Disposable Email - Served by Serverless Kittens\n\n[![Build Status](https://travis-ci.org/uilicious/inboxkitten.svg?branch=master)](https://travis-ci.org/uilicious/inboxkitten)\n\n[Inboxkitten](https://inboxkitten.com) is an open-source disposable email service that you can freely deploy adopt on your own!\n\nVisit [our site](https://inboxkitten.com) to give a spin, or ...\n\n# Docker Deployment Guide\n\nIts one simple line - to use our prebuilt docker container.\n\nNote you will need to [setup your mailgun account first](#setup-mailgun)\n\n```\n# PS: you should modify this for your use case\ndocker run \\\n\t-e MAILGUN_EMAIL_DOMAIN=\"<email-domain>\" \\\n\t-e MAILGUN_API_KEY=\"<api-key>\" \\\n\t-e WEBSITE_DOMAIN=\"localhost:8000\" \\\n\t-p 8000:8000 \\\n\tuilicious/inboxkitten\n```\n\nAnd head over to port 8000 - for your inboxkitten\n\n# Other Deployment Options\n\n- [Serverless deployment guide (for cloudflare/firebase)](./DEPLOY-GUIDE-SERVERLESS.md)\n- [localhost/custom deployment/configuration guide](./DEPLOY-GUIDE-LOCALHOST)\n\n# Support us on product hunt 🚀\n\n+ https://www.producthunt.com/posts/inboxkitten\n\n# Somewhat related blog / articles\n\n+ [The Stack : Making a free open-source disposable email service prototype (inboxkitten.com) in 14 hours](https://dev.to/picocreator/the-stack-making-a-free-open-source-disposable-email-service-prototype-inboxkittencom-in-14-hours-206g)\n+ [What I have learnt from a 14 hours project](https://dev.to/jmtiong/what-i-have-learnt-from-a-14-hours-project-2joo)\n+ [Development timeline](https://blog.uilicious.com/development-timeline-for-inboxkitten-com-lessons-learnt-e802a2f0a47c)\n\n# Other References\n\n- [Coding Guide](./CODE-GUIDE.md)\n\n# Looking for sponsor\n\nNote: Due to this project rather heavy traffic usage, a good half sadly spam/bot related, we are looking for a hosting sponsor / sponsor to subsidise running cost\n___\n\n## How to Setup Mailgun - and get your free API key\n\n### Mailgun\nTo sign up for a Mailgun account, go to the <a href=\"https://signup.mailgun.com/new/signup\" target=\"_blank\">signup</a> page.\n\n> 2021 Udpate: Inbound routing for mailgun, now requires any paid account (starting at $35/month) see : https://www.mailgun.com/pricing/\n\n#### Custom Domain\n```\n\t1. Click on `Add New Domain` button under your Domains panel. \n\t2. Follow the steps accordingly\n```\n> You can use the default domain that was provided by Mailgun if you do not have your own domain.\n\n#### Routes Configuration\nAfter setting up your domain, in order for you to receive email, you have to configure the routes. <a href=\"https://documentation.mailgun.com/en/latest/quickstart-receiving.html\" target=\"_blank\">Routes</a> act as rules that will filter through all the incoming mails and execute actions on matched conditions.\n\nIn your Routes panel, simply click on `Create Route` button and follow the steps accordingly.\n\n<img src=\"./assets/mailgun_create_route.png\" alt=\"Mailgun Route\" width=\"600px\"/>\n\n> The above route will match all names ending with `@inboxkitten.com`, store them in the storage that mailgun provides (only for 3 days) and stop processing any other rules once this route is matched. \n\n#### Mailgun API Key\nYou can locate your Mailgun API key by clicking on the domain that you are managing. In it you can see your API key.\n\n<img src=\"./assets/mailgun_api_key.png\" alt=\"Mailgun API key\" width=\"500px\"/>\n\nOr you can go to the security settings and locate the API key there.\n\n<img src=\"./assets/mailgun_api_key_2.png\" alt=\"Mailgun API key\" width=\"500px\"/>\n\n___\n"
  },
  {
    "path": "api/app.js",
    "content": "// Cache control settings\nconst cacheControl = require(\"./config/cacheControl\");\n\n// app package loading\nlet app = require(\"./src/app-setup\");\n\n// Setup the routes\napp.get(\"/api/v1/mail/list\",    require(\"./src/api/mailList\"));\napp.get(\"/api/v1/mail/getInfo\", require(\"./src/api/mailGetInfo\"));\napp.get(\"/api/v1/mail/getHtml\", require(\"./src/api/mailGetHtml\"));\n\n// Legacy fallback behaviour - \n// Note this is to be deprecated (after updating UI)\napp.get(\"/api/v1/mail/getKey\",  require(\"./src/api/mailGetInfo\"));\n\n// Static regex \nconst staticRegex = /static\\/(js|css|img)\\/(.+)\\.([a-zA-Z0-9]+)\\.(css|js|png|gif)/g;\n\n// Static folder hosting with cache control\n// See express static options: https://expressjs.com/en/4x/api.html#express.static\napp.use( app.express.static(\"public\", {\n\tetag: true,\n\tsetHeaders: function (res, path, stat) {\n\t\tif( staticRegex.test(path) ) {\n\t\t\tres.set('cache-control', cacheControl.immutable);\n\t\t} else {\n\t\t\tres.set('cache-control', cacheControl.static   );\n\t\t}\n\t}\n}) )\n\n// Custom 404 handling - use index.html\napp.use(function(req, res) {\n\tres.set('cache-control', cacheControl.static)\n\tres.sendFile(__dirname + '/public/index.html');\n});\n\n// Setup the server\nvar server = app.listen(8000, function () {\n\tconsole.log(\"app running on port.\", server.address().port);\n});\n"
  },
  {
    "path": "api/cloudflare.js",
    "content": "/**\n * Cloudflare fetch result handling\n */\naddEventListener('fetch', event => {\n\tevent.respondWith(handleFetchEvent(event))\n})\n\nconst KittenRouter = require(\"kittenrouter\");\nconst kittenRouterConfig = require(\"./config/kittenRouterConfig\") || {};\nconst router = new KittenRouter(kittenRouterConfig);\n\nasync function handleFetchEvent(event) {\n\n\t// Get the request object\n\tlet req = event.request;\n\t// Get the request URL\n\tlet url = new URL(req.url)\n\n\t// Does the CORS options hanlding\n\tif (req.method === \"OPTIONS\") {\n\t\treturn require(\"./src/cloudflare-api/optionsHandler\")(req)\n\t} \n\n\t// Get the pathname\n\tlet pathname = url.pathname;\n\t\n\t// Does API processing\n\tif(pathname === \"/api/v1/mail/list\"){\n\t\treturn require(\"./src/cloudflare-api/mailList\")(url)\n\t} else if(pathname === \"/api/v1/mail/getKey\") {\n\t\treturn require(\"./src/cloudflare-api/mailGetKey\")(url)\n\t} else if (pathname === \"/api/v1/mail/getHtml\") {\n\t\treturn require(\"./src/cloudflare-api/mailGetHtml\")(url)\n\t} else if (pathname.startsWith(\"/api/\")) {\n\t\t// Throw an exception for invalid API endpoints\n\t\treturn new Response('Invalid endpoint - ' + pathname, { status: 400, statusText: 'INVALID_ENDPOINT' });\n\t}\n\n\t// KittenRouter handling\n\tif( \n\t\tpathname === \"\" || pathname === \"/\" ||\n\t\tpathname.startsWith(\"/inbox/\") || \n\t\tpathname.startsWith(\"/static/css\") || \n\t\tpathname.startsWith(\"/static/img\") || \n\t\tpathname.startsWith(\"/static/js\")\n\t) {\n\t\treturn router.handleFetchEvent(event);\n\t}\n\t\n\t// Throw an exception for invalid file request\n\treturn new Response('Invalid filepath - ' + url.pathname, { status: 404, statusText: 'UNKNOWN_FILEPATH' });\n}\n"
  },
  {
    "path": "api/config/cacheControl.js",
    "content": "/**\n * Configure the various level of cache controls\n */\nmodule.exports = {\n    // Frequent changing dynamic content\n    \"dynamic\"  : \"public, max-age=1, max-stale=5, stale-while-revalidate=10, stale-if-error=86400\",\n    \n    // Rarely changing static content\n    // with very aggressive caching\n    \"static\"   : \"public, max-age=60, max-stale=120, stale-while-revalidate=3600, stale-if-error=86400\",\n\n    // Immutable content\n    \"immutable\": \"public, max-age=36000, max-stale=72000, stale-while-revalidate=360000, stale-if-error=864000\"\n}"
  },
  {
    "path": "api/config/kittenRouterConfig.sample.js",
    "content": "//\n// Routing and logging options\n//\nmodule.exports = {\n\n\t// logging endpoint to use\n\tlog : [\n\t\t{\n\t\t\t// Currently only elasticsearch is supported, scoped here for future alternatives\n\t\t\t// One possible option is google analytics endpoint\n\t\t\ttype : \"elasticsearch\",\n\n\t\t\t//\n\t\t\t// Elasticsearch index endpoint \n\t\t\t//\n\t\t\turl : \"https://elasticsearch-server.secret-domain.com/\",\n\n\t\t\t//\n\t\t\t// Authorization header (if needed)\n\t\t\t//\n\t\t\tbasicAuthToken : \"user:pass\",\n\n\t\t\t//\n\t\t\t// Index prefix for storing data, this is before the \"YYYY.MM\" is attached\n\t\t\t//\n\t\t\tindexPrefix : \"test-data-\",\n\n\t\t\t// Enable logging of the full ipv4/6\n\t\t\t//\n\t\t\t// Else it mask (by default) the last digit of IPv4 address\n\t\t\t// or the \"network\" routing for IPv6\n\t\t\t// see : https://www.haproxy.com/blog/ip-masking-in-haproxy/\n\t\t\tlogTrueIP : false,\n\n\t\t\t// @TODO support\n\t\t\t// Additional cookies to log\n\t\t\t//\n\t\t\t// Be careful not to log \"sensitive\" cookies, that can compromise security\n\t\t\t// typically this would be seesion keys.\n\t\t\t// cookies : [\"__cfduid\", \"_ga\", \"_gid\", \"account_id\"]\n\t\t}\n\t],\n\n\t// Routing rules to evaluate, starting from 0 index\n\t// these routes will always be processed in sequence\n\troute : [\n\t\t// Lets load all requests to commonshost first\n\t\t\"commonshost.inboxkitten.com\",\n\n\t\t// If it fails, we fallback to firebase\n\t\t\"firebase.inboxkitten.com\"\n\t],\n\n\t// Set to true to disable fallback to origin host \n\t// when all routes fails\n\tdisableOriginFallback : false,\n}"
  },
  {
    "path": "api/config/mailgunConfig.sample.js",
    "content": "//\n// API key and valid mailgun domain supported (using sandbox)\n//\nmodule.exports = {\n\t\"apiKey\"      : \"${MAILGUN_API_KEY}\",\n\t\"emailDomain\" : \"${MAILGUN_EMAIL_DOMAIN}\",\n\t//\"corsOrigin\"  : \"*\"\n}\n"
  },
  {
    "path": "api/firebase.js",
    "content": "/**\n * This is configured for use within firebase cloud function (or GCP cloud functions)\n * And will be automatically renamed into index.js by the build script\n * \n * See : https://firebase.google.com/docs/functions/http-events\n */\n\n// Dependencies loading\nconst functions = require('firebase-functions');\nlet app         = require(\"./src/app-setup\");\n\n// Setup the routes - for mail list / get\napp.get(\"/api/v1/mail/list\",   require(\"./src/api/mailList\"));\napp.get(\"/api/v1/mail/getKey\", require(\"./src/api/mailGetKey\"));\napp.get(\"/api/v1/mail/getHtml\", require(\"./src/api/mailGetHtml\"));\n\n// Expose the HTTP on request\nexports.firebase_api_v1 = functions.https.onRequest(app);\n"
  },
  {
    "path": "api/package.json",
    "content": "{\n  \"name\": \"inboxkitten-api\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"dependencies\": {\n    \"axios\": \"^0.18.0\",\n    \"body-parser\": \"^1.18.3\",\n    \"cors\": \"^2.8.5\",\n    \"express\": \"^4.16.3\",\n    \"firebase-admin\": \"^6.0.0\",\n    \"firebase-functions\": \"^2.0.5\",\n    \"kittenrouter\": \"^1.0.3\",\n    \"validator\": \"^10.7.0\"\n  },\n  \"devDependencies\": {\n    \"delay\": \"^4.0.0\",\n    \"mailgun-js\": \"^0.20.0\",\n    \"md5\": \"^2.2.1\",\n    \"mocha\": \"^5.2.0\",\n    \"shortid\": \"^2.2.13\",\n    \"uuid\": \"^3.3.2\",\n    \"webpack\": \"^4.30.0\",\n    \"webpack-cli\": \"^3.3.0\"\n  },\n  \"scripts\": {\n    \"test\": \"mocha\",\n    \"start\": \"node app.js\",\n    \"build-cloudflare\": \"webpack --mode production cloudflare.js\"\n  },\n  \"author\": \"\",\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "api/public/.gitignore",
    "content": "*\n!_anchor.txt\n!.gitignore"
  },
  {
    "path": "api/public/_anchor.txt",
    "content": "This directory is used for static file serving via the API"
  },
  {
    "path": "api/src/api/mailGetHtml.js",
    "content": "// Loading mailgun reader and config\nconst mailgunReader = require(\"../mailgunReader\");\nconst mailgunConfig = require(\"../../config/mailgunConfig\");\nconst cacheControl  = require(\"../../config/cacheControl\");\n\nconst reader = new mailgunReader(mailgunConfig);\n\n/**\n * Get and return the etatic email HTML content from the mailgun API, given the mailKey\n *\n * @param {*} req\n * @param {*} res\n */\nmodule.exports = function(req, res){\n\n\tlet region = req.query.region\n\tlet key = req.query.key\n\t\n\tif (region == null || region === \"\"){\n\t\treturn res.status(400).send('{ \"error\" : \"No `region` param found\" }');\n\t}\n\n\tif (key == null || key === \"\"){\n\t\treturn res.status(400).send('{ \"error\" : \"No `key` param found\" }');\n\t}\n\n\treader.getKey({region, key}).then(response => {\n\t\tlet body = response[\"body-html\"] || response[\"body-plain\"]\n\t\tif( body === undefined || body == null) {\n\t\t\tbody = 'The kittens found no messages :('\n\t\t}\n\n\t\t// Add JS injection to force all links to open as a new tab\n\t\t// instead of opening inside the iframe\n\t\tbody += '<script>' +\n\t\t\t'let linkArray = document.getElementsByTagName(\"a\");' +\n\t\t\t'for (let i=0; i<linkArray.length; ++i) { linkArray[i].target=\"_blank\"; }' +\n\t\t\t// eslint-disable-next-line\n\t\t\t'<\\/script>'\n\n\t\tres.set('cache-control', cacheControl.static)\n\t\tres.status(200).send(body)\n\t})\n\t.catch(e => {\n\t\tconsole.error(`Error getting mail HTML for /${region}/${key}: `, e)\n\t\tres.status(500).send(\"{error: '\"+e+\"'}\")\n\t});\n}\n"
  },
  {
    "path": "api/src/api/mailGetInfo.js",
    "content": "// Loading mailgun reader and config\nconst mailgunReader = require(\"../mailgunReader\");\nconst mailgunConfig = require(\"../../config/mailgunConfig\");\nconst cacheControl  = require(\"../../config/cacheControl\");\n\nconst reader = new mailgunReader(mailgunConfig);\n\n/**\n * Get and return the static email header details from the mailgun API given the mailKey\n *\n * @param {*} req\n * @param {*} res\n */\nmodule.exports = function(req, res){\n\n\tlet region = req.query.region\n\tlet key = req.query.key\n\t\n\tif (region == null || region === \"\"){\n\t\treturn res.status(400).send('{ \"error\" : \"No `region` param found\" }');\n\t}\n\n\tif (key == null || key === \"\"){\n\t\treturn res.status(400).send('{ \"error\" : \"No `key` param found\" }');\n\t}\n\t\n\treader.getKey({region, key}).then(response => {\n\t\tlet emailDetails = {}\n\n\t\t// Format and extract the name of the user\n\t\tlet [name, ...rest] = formatName(response.from)\n\t\temailDetails.name = name\n\n\t\t// Extract the rest of the email domain after splitting\n\t\tif (rest[0].length > 0) {\n\t\t\temailDetails.emailAddress = ' <' + rest\n\t\t}\n\n\t\t// Extract the subject of the response\n\t\temailDetails.subject = response.subject\n\n\t\t// Extract the recipients\n\t\temailDetails.recipients = response.recipients\n\n\t\t// Return with cache control\n\t\tres.set('cache-control', cacheControl.static)\n\t\tres.status(200).send(emailDetails)\n\t})\n\t.catch(e => {\n\t\tconsole.error(`Error getting mail metadata info for /${region}/${key}: `, e)\n\t\tres.status(500).send(\"{error: '\"+e+\"'}\")\n\t});\n}\n\nfunction formatName (sender) {\n\tlet [name, ...rest] = sender.split(' <')\n\treturn [name, rest]\n}\n"
  },
  {
    "path": "api/src/api/mailGetUrl.js",
    "content": "// Loading mailgun reader and config\nconst mailgunReader = require(\"../mailgunReader\");\nconst mailgunConfig = require(\"../../config/mailgunConfig\");\nconst cacheControl  = require(\"../../config/cacheControl\");\n\nconst reader = new mailgunReader(mailgunConfig);\n\n/**\n * Get and return the URL link from the mailgun API - for the mail gcontent\n * \n * NOTE - this is to be deprecated\n *\n * @param {*} req\n * @param {*} res\n */\nmodule.exports = function(req, res){\n\tlet params = req.query\n\tlet url = params.url\n\tif (url == null || url === \"\"){\n\t\t res.status(400).send('{ \"error\" : \"No `url` param found\" }');\n\t}\n\n\treader.getUrl(url).then(response => {\n\t\tres.set('cache-control', cacheControl.static)\n\t\tres.status(200).send(response)\n\t})\n\t.catch(e => {\n\t\tconsole.error(\"Error: \", error)\n\t\tres.status(500).send(\"{error: '\"+e+\"'}\")\n\t});\n}\n"
  },
  {
    "path": "api/src/api/mailList.js",
    "content": "// Loading mailgun reader and config\nconst mailgunReader = require(\"../mailgunReader\");\nconst mailgunConfig = require(\"../../config/mailgunConfig\");\nconst cacheControl  = require(\"../../config/cacheControl\");\n\nconst reader = new mailgunReader(mailgunConfig);\n\n/**\n * Mail listing API, returns the list of emails\n *\n * @param {*} req\n * @param {*} res\n */\nmodule.exports = function(req, res) {\n    let params = req.query;\n\n    // recipient may be:\n    // only the username, e.g. \"john.doe\"\n    // or the full email, e.g. \"john.doe@domain.com\"\n    let recipient = params.recipient;\n\n    // Check if recipient is empty\n    if (!recipient) {\n        return res.status(400).send({ error: \"No valid `recipient` param found\" });\n    }\n\n    // Trim leading and trailing whitespace\n    recipient = recipient.trim();\n\n    // If recipient ends with `\"@\"+mailgunConfig.emailDomain`, remove it\n    let pos = recipient.indexOf(\"@\" + mailgunConfig.emailDomain);\n    if (pos >= 0) {\n        recipient = recipient.substring(0, pos);\n    }\n\n    // Validate recipient\n    try {\n        recipient = validateUsername(recipient);\n    } catch (e) {\n        return res.status(400).send({ error: \"Invalid email\" });\n    }\n\n    // Empty check\n    if (!recipient) {\n        return res.status(400).send({ error: \"No valid `recipient` param found\" });\n    }\n\n    reader.recipientEventList(recipient + \"@\" + mailgunConfig.emailDomain)\n        .then(response => {\n            res.set('cache-control', cacheControl.dynamic);\n            res.status(200).send(response.items);\n        })\n        .catch(e => {\n            console.error(`Error getting list of messages for \"${recipient}\":`, e);\n            res.status(500).send({ error: e.toString() });\n        });\n};\n\n/**\n * Strictly validate username, rejecting any username that does not conform to the standards\n * @param {*} username username to be validated\n * @returns {string} Validated username\n */\nfunction validateUsername(username) {\n\n    // Step 1: Trim leading and trailing whitespaces\n    username = username.trim();\n\n    // Step 2: Throw error if the sanitized string is empty\n    if (username.length < 3) {\n        throw new Error(\"Invalid email.\");\n    }\n\n    // Step 2: Block the domain itself\n    if(username.toLowerCase() == mailgunConfig.emailDomain) {\n        throw new Error(\"Invalid email.\");\n    }\n\n    // Step 3: Check for disallowed characters\n    // Allowed characters: alphanumeric, dot (.), underscore (_), hyphen (-), plus (+)\n    const disallowedChars = /[^a-zA-Z0-9._+-]/g;\n    if (disallowedChars.test(username)) {\n        throw new Error(\"Invalid email.\");\n    }\n\n    // Step 4: Ensure that the username does not only contains symbols, but at least one alphanumeric character\n    if (!/[a-zA-Z0-9]/.test(username)) {\n        throw new Error(\"Invalid email.\");\n    }\n\n    // Step 5: Reject problematic inputs\n    if (/^[-._]+$/.test(username) || username === '-' || username === '_' || username === '.') {\n        throw new Error(\"Invalid email: Username cannot consist solely of special characters.\");\n    }\n\n    // Step 6: Check for consecutive special characters\n    if (/[._-]{2,}/.test(username)) {\n        throw new Error(\"Invalid email: Username cannot contain consecutive special characters.\");\n    }\n\n    // Step 6: Ensure that the username starts and end with an alphanumeric character instead of a symbol\n    if (/^[._+-]/.test(username) || /[._+-]$/.test(username)) {\n        throw new Error(\"Invalid email.\");\n    }\n\n    // Step 7: Pure numeric usernames are disallowed\n    if ((/^[0-9]*$/.test(username)) == true) {\n        throw new Error(\"Invalid email.\");\n    }\n\n    // Step 8: Ensure that the username starts or end with an alphabetical character\n    // This mitigate numeric iterations, but does permit numericalpha sets\n    if ( /^[0-9][a-zA-Z]/.test(username) || /[a-zA-Z][0-9]$/.test(username) ) {\n        // does nothing\n    } else if (!(/^[a-zA-Z]/.test(username) || /[a-zA-Z]$/.test(username))) {\n        throw new Error(\"Invalid email.\");\n    }\n\n    return username;\n}\n"
  },
  {
    "path": "api/src/app-setup.js",
    "content": "\n// Dependencies loading\nconst express       = require(\"express\");\nconst bodyParser    = require(\"body-parser\");\nconst cors          = require('cors');\nconst mailgunConfig = require(\"../config/mailgunConfig\");\n\n// Initializing the express app\nconst app = express();\n\n// Allow cross site requests (for now)\napp.use(cors());\n\n// Setup JSON encoding\napp.use(bodyParser.json());\napp.use(bodyParser.urlencoded({ extended: true }));\n\n// Add an easy way to get the express module\napp.express = express;\n\n// Export the app module, for actual server deployment (on X)\nmodule.exports = app;\n"
  },
  {
    "path": "api/src/cloudflare-api/KittenRouter.js",
    "content": "/**\n * KittenRouter is a utility class used to \n * \n * - log request, for monitoring purposes (with ip masking for GDPR)\n * - reroute fetch requests an alternative \"origin\" endpoint\n * \t- automatically failover on request failure / timeout\n * \t- logging of failed requests\n * \t- fallback to default cloudflare \"origin\" endpoint\n * \n * And as the name implies, is a sub project for InboxKitten.\n */\n\n/**\n * Useful various refrence documentation, on how this whole class should be implemented\n * \n * Cloudflare refrence documentation\n * - https://blog.cloudflare.com/logs-from-the-edge/\n * - https://developers.cloudflare.com/workers/reference/cache-api/\n * - https://blog.cloudflare.com/introducing-the-workers-cache-api-giving-you-control-over-how-your-content-is-cached/\n * - https://support.cloudflare.com/hc/en-us/articles/200170986-How-does-Cloudflare-handle-HTTP-Request-headers-\n * - https://support.cloudflare.com/hc/en-us/articles/202494830-Pseudo-IPv4-Supporting-IPv6-addresses-in-legacy-IPv4-applications\n * \n * Webworker documentation (for fetch API)\n * - https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch\n * - https://developer.mozilla.org/en-US/docs/Web/API/Request\n * - https://developer.mozilla.org/en-US/docs/Web/API/Response\n */\n\n//---------------------------------------------------------------------------------------------\n//\n// The following is an example config object, used to setup KittenRouter\n// and kinda serve as a semi-functional spec of how KittenRouter is expected to work.\n//\n//---------------------------------------------------------------------------------------------\n\n/*\nconst exampleConfig = {\n\n\t// logging endpoint to use\n\tlog : [\n\t\t{\n\t\t\t// Currently only elasticsearch is supported, scoped here for future alternatives\n\t\t\t// One possible option is google analytics endpoint\n\t\t\ttype : \"elasticsearch\",\n\n\t\t\t//\n\t\t\t// Elasticsearch index endpoint \n\t\t\t//\n\t\t\turl : \"https://elasticsearch-server.secret-domain.com/\",\n\n\t\t\t//\n\t\t\t// Authorization header (if needed)\n\t\t\t//\n\t\t\tauthUser : \"user\",\n\t\t\tauthPass : \"pass\",\n\n\t\t\t//\n\t\t\t// Index prefix for storing data, this is before the \"YYYY.MM\" is attached\n\t\t\t//\n\t\t\tindexPrefix : \"test-data-\",\n\n\t\t\t// Enable logging of the full ipv4/6\n\t\t\t//\n\t\t\t// Else it mask (by default) the last digit of IPv4 address\n\t\t\t// or the \"network\" routing for IPv6\n\t\t\t// see : https://www.haproxy.com/blog/ip-masking-in-haproxy/\n\t\t\tlogTrueIP : false,\n\n\t\t\t// @TODO support\n\t\t\t// Additional cookies to log\n\t\t\t//\n\t\t\t// Be careful not to log \"sensitive\" cookies, that can compromise security\n\t\t\t// typically this would be seesion keys.\n\t\t\t// cookies : [\"__cfduid\", \"_ga\", \"_gid\", \"account_id\"]\n\t\t}\n\t],\n\n\t// Routing rules to evaluate, starting from 0 index\n\t// these routes will always be processed in sequence\n\troute : [\n\n\t\t// Lets load all requests to commonshost first\n\t\t\"commonshost.inboxkitten.com\",\n\n\t\t// If it fails, we fallback to firebase\n\t\t\"firebase.inboxkitten.com\"\n\n\t\t// // Object based route definitions\n\t\t// //-----------------------------------------------------------------\n\t\t// {\n\t\t// \t// \"\" host routing will match all\n\t\t// \treqHost : [\"\"],\n\t\t// \t// Routing prefix to check for, note that \"\" will match all\n\t\t// \treqPrefix : [\"\"],\n\t\t\n\t\t// \t// Origin servers to route to\n\t\t// \thost : \"host-endpoint-c\",\n\t\t// \tport : 443,\n\t\t// \tprotocol : \"https\",\n\t\t// \t// Matching by country\n\t\t// \t// Country codes : https://support.cloudflare.com/hc/en-us/articles/205072537-What-are-the-two-letter-country-codes-for-the-Access-Rules-\n\t\t// \tcountry : [\n\t\t// \t\t\"SG\"\n\t\t// \t],\n\t\t// \t// Matching by region\n\t\t// \tregion : [\n\t\t// \t\t\"Europe\"\n\t\t// \t],\n\t\t// \t// Timeout to abort request on\n\t\t\t\n\t\t// \t// Fetching sub options (like cache overwrite)\n\t\t// \tfetchConfig : { cf: { cacheEverything: true } }\n\t\n\t\t// \t// @TODO (consider support for nested object based origin decleration?)\n\t\t// \t// Might be useful for some region based routing or maybe crazier?\n\t\t// \t// Racing origin requests and terminating the \"slower copy\"\n\t\t// \t//-------------------------------------------------------------------------\n\t\t// }\n\t],\n\n\t// Set to true to disable fallback to origin host \n\t// when all routes fails\n\tdisableOriginFallback : false,\n\n\t// @TODO support default timeout to process a request in milliseconds\n\t// defaultOriginTimeout : 10000, // 10,000 ms = 10 s\n\n\t// @TODO crazier caching options to consider\n\t// - KeyValue caching (probably pointless, cost wise)\n\t// - In memory caching (with a limit of 500 objects + 10 kb per object?)\n\t// See : https://developers.cloudflare.com/workers/writing-workers/storing-data/\n}\n*/\n\n//---------------------------------------------------------------------------------------------\n//\n// Logging internal logic\n//\n//---------------------------------------------------------------------------------------------\n\n// Simple regex to validate for an ipv4\nconst ipv4_simpleRegex = /^[0-9a-z]{1,3}\\.[0-9a-z]{1,3}\\.[0-9a-z]{1,3}\\.[0-9a-z]{1,3}$/i;\n\n/**\n * Extract from a request, various possible ip properties (in cludflare) \n * for a valid ipv4 address\n * \n * @param {Request} request object to extract from\n * @param {Boolean} logTrueIP (defaul false) used to log unmasked ip if true\n * \n * @return {String} ipv4 string if found, else a blank string of \"\"\n */\nfunction getIPV4(request, logTrueIP = false) {\n\tlet headers = request.headers;\n\tlet ip = headers.get('cf-pseudo-ipv4') || headers.get('cf-connecting-ipv4') || headers.get('cf-connecting-ip') || '';\n\tip = ip.trim();\n\n\t// If ipv4 validation failed, return blank\n\tif(ip === '' || !ipv4_simpleRegex.test(ip)) {\n\t\treturn \"\";\n\t}\n\n\t// Assume that its a valid ipv4\n\t// return immediately if no ipmasking is done\n\tif(logTrueIP == false) {\n\t\treturn ip;\n\t}\n\n\t// Time to perform ip masking\n\tlet ip_split = ip.split(\".\");\n\tip_split[3] = \"xxx\";\n\treturn ip_split.join(\".\");\n}\n\n/**\n * Extract from a request, various possible ip properties (in cludflare) \n * for a valid ipv6 address\n * \n * @param {Request} request object to extract from\n * @param {Boolean} logTrueIP (defaul false) used to log unmasked ip if true\n * \n * @return {String} ipv6 string if found, else a blank string of \"\"\n */\nfunction getIPV6(request, logTrueIP = false) {\n\tlet headers = request.headers;\n\tlet ip = headers.get('cf-connecting-ipv6') || headers.get('cf-connecting-ip') || '';\n\tip = ip.trim();\n\n\t// If ipv4 validation passes, return blank\n\tif(ip === '' || ipv4_simpleRegex.test(ip)) {\n\t\treturn \"\";\n\t}\n\n\t// Assume that its an ipv6\n\t// return immediately if no ipmasking is done\n\tif(logTrueIP == false) {\n\t\treturn ip;\n\t}\n\n\t// Time to perform ip masking\n\tlet ip_split = ip.split(\":\");\n\tfor(let i=2; i<ip_split.length; ++i) {\n\t\tip_split[i] = \"xxxx\"\n\t}\n\treturn ip_split.join(\":\");\n}\n\n// Log request with a single config map\nasync function logRequestWithConfigMap(logConfig, request, response, routeType, routeCount) {\n\t// Does nothing if logconfig is null\n\tif( logConfig == null || logConfig.url == null || logConfig.url.length <= 0 ) {\n\t\treturn null;\n\t}\n\n\t// Index YYYY.MM formating\n\tlet indexYearAndMonth = (new Date()).toISOString().substr(0,7).replace(\"-\",\".\");\n\tlet indexPrefix = logConfig.indexPrefix || \"KittenRouter-log-\";\n\n\t// The full POST request URL\n\tlet fullLoggingURL = logConfig.url.trim();\n\tif( !fullLoggingURL.endsWith(\"/\") ) {\n\t\tfullLoggingURL = fullLoggingURL+\"/\";\n\t}\n\tfullLoggingURL = fullLoggingURL+logConfig.indexPrefix+indexYearAndMonth+\"/_doc/\";\n\n\t// Trueip logging flag\n\tlet logTrueIP = logConfig.logTrueIP || false;\n\n\t// The data to log\n\tlet data = {\n\t\t'timestamp': (new Date()).toISOString(),\n\n\t\t'route.type':  routeType,\n\t\t'route.count': routeCount,\n\n\t\t'req.url':        request.url,\n\t\t'req.referer':    request.referrer || '',\n\t\t'req.method':     request.method,\n\n\t\t'req.ipv4':          getIPV4(request, logTrueIP),\n\t\t'req.ipv6':          getIPV6(request, logTrueIP),\n\t\t'req.host':          request.headers.get('host') || '',\n\t\t'req.user-agent':    request.headers.get('user-agent') || '',\n\t\t'req.country-code':  request.headers.get('cf-ipcountry') || '',\n\n\t\t'req.cf.ray':     request.headers.get('cf-ray') || '',\n\t\t'req.cf.colo':    (request.cf && request.cf.colo) || '',\n\t\t'req.tlsVersion': (request.cf && request.cf.tlsVersion) || '',\n\t\t'req.tlsCipher':  (request.cf && request.cf.tlsCipher) || '',\n\n\t\t'res.status':           response.status,\n\t\t'res.url':              response.url,\n\t\t'res.server':           response.headers.get('server') || '',\n\t\t'res.via':              response.headers.get('via') || '',\n\t\t'res.content-type':     response.headers.get('content-type') || '',\n\t\t'res.content-encoding': response.headers.get('content-encoding') || '',\n\t\t'res.content-length':   response.headers.get('content-length') || '',\n\t\t'res.cache-control':    response.headers.get('cache-control') || '',\n\t\t'res.cf.cache-status':  response.headers.get('cf-cache-status') || '',\n\t\t'res.cf.ray':           response.headers.get('cf-ray') || '',\n\n\t\t'config.logTrueIP': logTrueIP\n\t};\n\n\t// // Cookies to log\n\t// let cookieNames = logConfig.cookies;\n\t// if( cookieNames != null && cookieNames.length > 0 ) {\n\t// \tlet cookieJar = request.headers.get(\"Cookie\")\n\t// \t// @TODO : iterate cookies and log relevent keys\n\t// }\n\n\t// Lets prepare the headers\n\tlet logHeaders = {\n\t\t'Content-Type': 'application/json',\n\t};\n\n\t// Lets handle authentication\n\tif( logConfig.basicAuthToken && logConfig.basicAuthToken.length > 0 ) {\n\t\tlogHeaders[\"Authorization\"] = \"Basic \"+btoa(logConfig.basicAuthToken);\n\t}\n\n\t// The elasticsearch POST request to perform for logging\n\tlet logResult = await fetch(\n\t\tfullLoggingURL, {\n\t\tmethod: 'POST',\n\t\tbody: JSON.stringify(data),\n\t\theaders: new Headers(logHeaders)\n\t})\n\n\t// Log the log?\n\tif( logConfig.consoleDebug ) {\n\t\tconsole.log(\"KittenRouter logging response\", logResult);\n\t}\n\n\t// Return the result\n\treturn logResult;\n}\n\n// Log request with a config array\nasync function logRequestWithConfigArray(configArr, request, response, routeType, routeCount) {\n\t// Does nothing if configArr is null\n\tif( configArr == null || configArr.length <= 0 ) {\n\t\treturn null;\n\t}\n\n\t// Lets iterate the config\n\tlet promiseArray = [];\n\tfor(let i=0; i<configArr.length; ++i) {\n\t\tpromiseArray[i] = logRequestWithConfigMap(configArr[i], request, response, routeType, routeCount);\n\t}\n\n\t// Return with a single promise object\n\treturn Promise.all(promiseArray);\n}\n\n//---------------------------------------------------------------------------------------------\n//\n// Utility functions\n//\n//---------------------------------------------------------------------------------------------\n\n/**\n * Clones a URL object, with an origin string\n * \n * @param {URL} inURL to clone over\n * @param {String} originHostStr to overwrite host with\n * \n * @return {URL} cloned URL object with new origin host\n */\nfunction cloneUrlWithNewOriginHostString(inURL, originHostStr) {\n\tlet ret = new URL(inURL);\n\tret.host = originHostStr;\n\treturn ret;\n}\n\n/**\n * Generate a Response object, containing an error\n * @param {String} errorCode for the error\n * @param {String} errorMsg containing error details\n * @param {int} httpCode (default=500) for response object \n */\nfunction setupResponseError(errorCode, errorMsg, httpCode = 500) {\n\tlet ret = new Response(\n\t\tJSON.stringify({\n\t\t\terror: {\n\t\t\t\tcode : errorCode,\n\t\t\t\tmessage : errorMsg\n\t\t\t}\n\t\t}),\n\t\t{ \n\t\t\tstatus: httpCode, \n\t\t\tstatusText: errorCode, \n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t//\"KittenRouterException\": \"true\"\n\t\t\t}\n\t\t}\n\t);\n\tret._isKittenRouterException = true;\n\treturn ret;\n}\n\n/**\n * Lets do a oversimplified check if a response object is valid\n * if the response code is 200~399\n * \n * @param {*} resObj \n * \n * @return true if its a valid response object\n */\nfunction isGoodResponseObject(resObj) {\n\treturn resObj != null && resObj.status >= 200 && resObj.status < 400;\n}\n\n/**\n * Check if the given response object is a KittenRouter exception\n * @param {Response} resObj to validate\n * \n * @return true if its a KittenRouter exception \n */\nfunction isKittenRouterException(resObj) {\n\treturn resObj != null && resObj._isKittenRouterException;\n\t// resObj.headers && resObj.headers.get(\"KittenRouterException\") == \"true\";\n}\n\n//---------------------------------------------------------------------------------------------\n//\n// Routing internal logic\n//\n//---------------------------------------------------------------------------------------------\n\n/**\n * Makes a request, with a different origin host\n * \n * @param {String} originHostStr to overwrite host with\n * @param {Request} inRequest to use \n * \n * @return {Response} object of the request\n */\n// Process a routing request, and return its response object\nasync function processOriginRoutingStr(originHostStr, inRequest) {\n\treturn fetch( //\n\t\tcloneUrlWithNewOriginHostString(inRequest.url,originHostStr), //\n\t\tinRequest //\n\t);\n}\n\n// // Process a routing request, and return its response object\n// async function processOriginRouting(origin, inRequest) {\n// \tif( (typeof origin) === \"string\" ) {\n// \t\treturn processOriginRoutingStr(origin, inRequest);\n// \t}\n// \tthrow \"Object based routing config is NOT yet supported\";\n// }\n\n/**\n * Process a request, and perform the required route request and logging\n * This DOES NOT handle the fetch fallback\n * \n * @param {Object} configObj conataining both the .route, and .log array config\n * @param {*} fetchEvent provided from cloudflare, attaches logging waitUntil\n * @param {Request} inRequest to process \n * \n * @return {Response} if a valid route with result is found, else return final route request failure (if any), else return null\n */\nasync function processRoutingRequest( configObj, fetchEvent, inRequest ) {\n\t// Lets get the route, and log array first\n\tlet routeArray = configObj.route;\n\tlet logArray = configObj.log;\n\n\t// Return null, on empty routeArray\n\tif( routeArray == null || routeArray.length <= 0 ) {\n\t\treturn null;\n\t}\n\n\t// setup the variable for response object\n\tlet resObj = null;\n\n\t// Lets iterate the routes\n\tfor( let i=0; i<routeArray.length; ++i ) {\n\t\tlet route = routeArray[i];\n\n\t\t// Route string processing\n\t\tif( (typeof route) === \"string\" ) {\n\t\t\t// Lets handle string origins\n\t\t\tresObj = await processOriginRoutingStr( route, inRequest );\n\n\t\t\t// Lets log \n\t\t\tfetchEvent.waitUntil( logRequestWithConfigArray( logArray, inRequest, resObj, \"ROUTE_REQUEST\", i) );\n\n\t\t\t// If its a valid response, return it\n\t\t\tif( isGoodResponseObject(resObj) ) {\n\t\t\t\treturn resObj;\n\t\t\t}\n\n\t\t\t// Lets continue to next route\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Throw an exception on an unknown route type\n\t\treturn setupResponseError(\"UNKNOWN_CONFIG\", \"Object based route config is not yet supported\");\n\t}\n\n\treturn resObj;\n}\n\n/**\n * Process the fetch event as it comes from cloudflare\n * \n * @param {Object} configObj for the KittenRouter.config\n * @param {*} fetchEvent provided from cloudflare\n * \n * @return {Response} if a valid route with result is found, else the last route error (if applicable)\n */\nasync function processFetchEvent( configObj, fetchEvent ) {\n\t// Lets unpack out the request\n\tlet inReq = fetchEvent.request;\n\tlet resObj = null;\n\n\t// Lets process the routing request\n\t//----------------------------------------------------------------------\n\n\t// Lets try to get a response from a route\n\tresObj = await processRoutingRequest( configObj, fetchEvent, inReq );\n\n\t// Lets return the response object if its valid\n\t// We do an oversimilified assumption that its valid \n\t// if the response code is 200~399\n\tif( isGoodResponseObject(resObj) ) {\n\t\treturn resObj;\n\t}\n\n\t// Throw and show results from setupResponseError (for direct feedback loop)\n\tif( isKittenRouterException(resObj) ) {\n\t\treturn resObj;\n\t}\n\n\t// At this point all routes are assumed to have failed\n\t// As such we will attempt to do a fallback to the default origin\n\t//----------------------------------------------------------------------\n\n\t// Origin fallback disabled, return last response, or a hard error\n\tif( configObj.disableOriginFallback ) {\n\t\t// No response object returned by routes : assume no valid routes\n\t\tif( resObj == null ) {\n\t\t\treturn setupResponseError(\"NO_VALID_ROUTE\", \"No valid route found in config\");\n\t\t}\n\n\t\t// Else return the last failed route response\n\t\treturn resObj;\n\t}\n\n\t// Lets fetch the cloudflare origin request, log it, and return its result instead\n\tresObj = await fetch(inReq);\n\tfetchEvent.waitUntil( logRequestWithConfigArray( configObj.log, inReq, resObj, \"ORIGIN_FALLBACK\", -1) );\n\treturn resObj;\n}\n\n//---------------------------------------------------------------------------------------------\n//\n// Class implementation (and public interface)\n//\n//---------------------------------------------------------------------------------------------\n\nclass KittenRouter {\n\t\n\t/**\n\t * Setup KittenRouter instance with the given config\n\t * \n\t * @param inConfig for configuring KittenRouter\n\t */\n\tconstructor(inConfig) {\n\t\tthis.config = inConfig || {}; // fallback for blank object (make certain things easier)\n\t}\n\n\t/**\n\t * Request handling function, and routing\n\t * \n\t * @param  fetchEvent from cloudflare to process\n\t * \n\t * @return response object for cloudflare\n\t */\n\tasync handleFetchEvent(fetchEvent) {\n\t\treturn await processFetchEvent(this.config, fetchEvent);\n\t}\n}\n\n// Export out the KittenRouter class, if possible\n// Skipped if used directly in cloudflare worker\nmodule.exports = KittenRouter;\n\n//---------------------------------------------------------------------------------------------\n//\n// Quick and dirty sample implementation (for cloudflare debugging)\n//\n//---------------------------------------------------------------------------------------------\n\n/*\n//\n// If module does not exist, this is probably a cloudflare debugging session\n// Lets do this =)\n//\nif( this.module == null ) {\n\t// KittenRouter setup\n\tconst router = new KittenRouter({\n\n\t // logging endpoint to use\n\t log : [\n\t\t{\n\t\t  // Currently only elasticsearch is supported, scoped here for future alternatives\n\t\t  // One possible option is google analytics endpoint\n\t\t  type : \"elasticsearch\",\n\n\t\t\t//\n\t\t\t// Elasticsearch index endpoint \n\t\t\t//\n\t\t  url : \"https://inboxkitten.logging.com/\",\n\n\t\t\t//\n\t\t\t// Authorization header (if needed)\n\t\t\t//\n\t\t\tbasicAuthToken : \"elasticsearch:password\",\n\n\t\t\t//\n\t\t\t// Index prefix for storing data, this is before the \"YYYY.MM\" is attached\n\t\t\t//\n\t\t\tindexPrefix : \"test-data-\",\n\n\t\t\t// Enable logging of the full ipv4/6\n\t\t\t//\n\t\t\t// Else it mask (by default) the last digit of IPv4 address\n\t\t\t// or the \"network\" routing for IPv6\n\t\t\t// see : https://www.haproxy.com/blog/ip-masking-in-haproxy/\n\t\t\tlogTrueIP : false,\n\n\t\t\t// @TODO support\n\t\t\t// Additional cookies to log\n\t\t\t//\n\t\t\t// Be careful not to log \"sensitive\" cookies, that can compromise security\n\t\t\t// typically this would be seesion keys.\n\t\t\t// cookies : [\"__cfduid\", \"_ga\", \"_gid\", \"account_id\"]\n\t\t}\n\t ],\n\n\t\troute: [\n\t\t\t\"commonshost.inboxkitten.com\",\n\t\t\t\"firebase.inboxkitten.com\"\n\t\t]\n\t});\n\n\t// Cloudflare fetch result handling\n\taddEventListener('fetch', event => {\n\t\tevent.respondWith(router.handleFetchEvent(event))\n\t});\n}\n*/"
  },
  {
    "path": "api/src/cloudflare-api/mailGetHtml.js",
    "content": "/**\n * Making a curl request that looks like\n * curl -X POST --data 'key=world' example.com\n * or\n * curl -X POST --form 'key=world' example.com\n */\n\nlet config = require(\"../../config/mailgunConfig\")\n\nmodule.exports = async function(url) {\n    let mailKey = url.searchParams.get('mailKey')\n\tif (mailKey == null || mailKey === \"\"){\n\n\t\treturn new Response(\"{error: 'No `mailKey` param found'}\",\n\t\t\t{ status: 400, statusText: 'INVALID_PARAMETER', headers: {\n\t\t\t\t\"Content-Type\": \"application/json\"\n\t\t\t} \n\t\t});\n\t}\n\n\t// Setup the authentication option object\n    let authenticationKey = this.btoa(\"api:\" + config.apiKey);\n\n\tlet _authOption = {\n\t\theaders: {\n\t\t\t\"Authorization\" : \"BASIC \" + authenticationKey\n\t\t}\n\t};\n\n\ttry {\n\t\t// Initialize the variables\n\t\tlet prefix, key;\n\t\t\t\n\t\t// Lets check for newer key format\n\t\tif( mailKey.length > 37 ) {\n\t\t\t// Handle newer key format\n\t\t\tlet pt = fullKey.lastIndexOf(\"-\", fullKey.length - 36);\n\t\t\tprefix = fullKey.slice(0,pt);\n\t\t\tkey = fullKey.slice(pt+1);\n\t\t} else {\n\t\t\t// Fallback to original logic\n\t\t\tlet pt = fullKey.lastIndexOf(\"-\");\n\t\t\tprefix = fullKey.slice(0,pt);\n\t\t\tkey = fullKey.slice(pt+1);\n\t\t}\n\t\t\n        // slice the mailgunApi to include the region\n        let apiUrl = \"https://api.mailgun.net/v3\"\n        apiUrl = apiUrl.replace(\"://\", \"://\"+prefix+\".\")\n        let urlWithParams = apiUrl+\"/domains/\" + config.emailDomain + \"/messages/\"+key;\n        const response = await fetchGet(urlWithParams, _authOption);\n        \n        let body = response[\"body-html\"] || response[\"body-plain\"]\n\t\tif( body === undefined || body == null) {\n\t\t\tbody = 'The kittens found no messages :('\n\t\t}\n\n\t\t// Add JS injection to force all links to open as a new tab\n\t\t// instead of opening inside the iframe\n\t\tbody += '<script>' +\n\t\t\t'let linkArray = document.getElementsByTagName(\"a\");' +\n\t\t\t'for (let i=0; i<linkArray.length; ++i) { linkArray[i].target=\"_blank\"; }' +\n\t\t\t// eslint-disable-next-line\n\t\t\t'<\\/script>'\n\n\t\tlet responseInit = {\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": \"text/html\"\n\t\t\t\t//@TODO : Consider caching here?\n\t\t\t}\n\t\t}\n\t\treturn new Response(body, responseInit)\n\t} catch (err) {\n\t\treturn new Response(\"{error: '\"+err+\"'}\",\n\t\t\t{ status: 400, statusText: 'INVALID_PARAMETER', headers: {\n\t\t\t\t\"Content-Type\": \"application/json\"\n\t\t\t} \n\t\t});\n\t}\n}\n\n\n/**\n* Simple fetch get, with response data\n* @param {String} urlWithParams\n* @param {Object} options\n*/\nvar fetchGet = function(urlWithParams, options){\n\treturn new Promise(function(resolve, reject){\n\t\tfetch(urlWithParams, options).then(response => {\n\t\t\tresolve(response.json())\n\t\t}).catch(e => {\n\t\t\treject(e)\n\t\t})\n\t})\n}\n"
  },
  {
    "path": "api/src/cloudflare-api/mailGetKey.js",
    "content": "\n/**\n * Making a curl request that looks like\n * curl -X POST --data 'key=world' example.com\n * or\n * curl -X POST --form 'key=world' example.com\n */\nlet config = require(\"../../config/mailgunConfig\")\n\nmodule.exports = async function(url) {\n    let mailKey = url.searchParams.get('mailKey')\n\tif (mailKey == null || mailKey === \"\"){\n\t\treturn new Response(\"{error: 'No `mailKey` param found'}\",\n\t\t\t{ status: 400, statusText: 'INVALID_PARAMETER', headers: {\n\t\t\t\t\"Content-Type\": \"application/json\"\n\t\t\t} \n\t\t});\n\t}\n\n\t// Setup the authentication option object\n    let authenticationKey = this.btoa(\"api:\" + config.apiKey);\n\n\tlet _authOption = {\n\t\theaders: {\n\t\t\t\"Authorization\" : \"BASIC \" + authenticationKey\n\t\t}\n\t};\n\n\ttry {\n\t\t// Initialize the variables\n\t\tlet prefix, key;\n\t\t\t\n\t\t// Lets check for newer key format\n\t\tif( mailKey.length > 37 ) {\n\t\t\t// Handle newer key format\n\t\t\tlet pt = fullKey.lastIndexOf(\"-\", fullKey.length - 36);\n\t\t\tprefix = fullKey.slice(0,pt);\n\t\t\tkey = fullKey.slice(pt+1);\n\t\t} else {\n\t\t\t// Fallback to original logic\n\t\t\tlet pt = fullKey.lastIndexOf(\"-\");\n\t\t\tprefix = fullKey.slice(0,pt);\n\t\t\tkey = fullKey.slice(pt+1);\n\t\t}\n\t\t\n        // slice the mailgunApi to include the region\n        let apiUrl = \"https://api.mailgun.net/v3\"\n        apiUrl = apiUrl.replace(\"://\", \"://\"+prefix+\".\")\n        let urlWithParams = apiUrl+\"/domains/\" + config.emailDomain + \"/messages/\"+key;\n\t\tconst response = await fetchGet(urlWithParams, _authOption);\n\n        let emailDetails = {}\n\n        // Format and extract the name of the user\n        let [name, ...rest] = formatName(response.from)\n        emailDetails.name = name\n\n        // Extract the rest of the email domain after splitting\n        if (rest[0].length > 0) {\n            emailDetails.emailAddress = ' <' + rest\n        }\n\n        // Extract the subject of the response\n        emailDetails.subject = response.subject\n\n        // Extract the recipients\n        emailDetails.recipients = response.recipients\n\n\t\tlet responseInit = {\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": \"application/json\"\n\t\t\t}\n\t\t}\n\t\treturn new Response(JSON.stringify(emailDetails), responseInit)\n\t} catch (err) {\n\t\treturn new Response(\"{error: '\"+err+\"'}\",\n\t\t\t{ status: 400, statusText: 'INVALID_PARAMETER', headers: {\n\t\t\t\t\"Content-Type\": \"application/json\"\n\t\t\t} \n\t\t});\n\t}\n}\n\n\n/**\n* Simple fetch get, with response data\n* @param {String} urlWithParams\n* @param {Object} options\n*/\nlet fetchGet = function(urlWithParams, options){\n\treturn new Promise(function(resolve, reject){\n\t\tfetch(urlWithParams, options).then(response => {\n\t\t\tresolve(response.json())\n\t\t}).catch(e => {\n\t\t\treject(e)\n\t\t})\n\t})\n}\n\nfunction formatName (sender) {\n\tlet [name, ...rest] = sender.split(' <')\n\treturn [name, rest]\n}\n"
  },
  {
    "path": "api/src/cloudflare-api/mailList.js",
    "content": "/**\n * Making a curl request that looks like\n * curl -X POST --data 'key=world' example.com\n * or\n * curl -X POST --form 'key=world' example.com\n */\nlet config = require(\"../../config/mailgunConfig\")\n\nmodule.exports = async function(url) {\n\tlet recipient = url.searchParams.get('recipient')\n\tif (recipient == null){\n\t\treturn new Response(\"{error: 'No `recipient` param found'}\",\n\t\t\t{ status: 400, statusText: 'INVALID_PARAMETER', headers: {\n\t\t\t\t\"Content-Type\": \"application/json\"\n\t\t\t} \n\t\t});\n\t}\n\n\t// strip off all @domain if there is any\n\tif(recipient.indexOf(\"@\") >= 0){\n\t\trecipient = recipient.substring(0, recipient.indexOf(\"@\"))\n\t}\n\n\t// Setup the authentication option object\n\tlet authenticationKey = this.btoa(\"api:\" + config.apiKey);\n\n\tlet _authOption = {\n\t\theaders: {\n\t\t\t\"Authorization\" : \"BASIC \" + authenticationKey\n\t\t}\n\t};\n\n\ttry {\n\t\tconst postData = await fetchGet(\"https://api.mailgun.net/v3/\" + config.emailDomain + \"/events?recipient=\"+recipient+\"@\"+config.emailDomain, _authOption);\n\t\tlet responseInit = {\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": \"application/json\"\n\t\t\t}\n\t\t}\n\t\treturn new Response(JSON.stringify(postData.items), responseInit)\n\t} catch (err) {\n\t\treturn new Response(\"{error: '\"+err+\"'}\",\n\t\t\t{ status: 400, statusText: 'INVALID_PARAMETER', headers: {\n\t\t\t\t\"Content-Type\": \"application/json\"\n\t\t\t} \n\t\t});\n\t}\n}\n\n\n/**\n* Simple fetch get, with response data\n* @param {String} urlWithParams\n* @param {Object} options\n*/\nvar fetchGet = function(urlWithParams, options){\n\treturn new Promise(function(resolve, reject){\n\t\tfetch(urlWithParams, options).then(response => {\n\t\t\tresolve(response.json())\n\t\t}).catch(e => {\n\t\t\treject(e)\n\t\t})\n\t})\n}\n"
  },
  {
    "path": "api/src/cloudflare-api/optionsHandler.js",
    "content": "/**\n * CORS Options handling for cloudflare api\n */\nconst config = require(\"../../config/mailgunConfig\")\n\nconst corsHeaders = {\n\t\"Access-Control-Allow-Origin\": config.corsOrigin,\n\t\"Access-Control-Allow-Methods\": \"GET, POST, OPTIONS\",\n\t\"Access-Control-Allow-Headers\": \"Content-Type\",\n}\n\nmodule.exports = async function optionsHandler(request) {\n\t// Handle CORS pre-flight request.\n\tif (\n\t\trequest.headers.get(\"Origin\") !== null &&\n\t\trequest.headers.get(\"Access-Control-Request-Method\") !== null &&\n\t\trequest.headers.get(\"Access-Control-Request-Headers\") !== null\n\t) {\n\t\treturn new Response(null, {\n\t\t\theaders: corsHeaders\n\t\t});\n\t} \n\n\t// Handle standard OPTIONS request.\n\treturn new Response(null, {\n\t\theaders: {\n\t\t\t\"Allow\": \"GET, POST, OPTIONS\",\n\t\t}\n\t});\n}"
  },
  {
    "path": "api/src/mailgunReader.js",
    "content": "// AXIOS dependencies\nconst axios = require(\"axios\");\n\n/**\n* Simple axois get, with response data\n* @param {String} urlWithParams\n* @param {Object} options\n*/\nvar axiosGet = function(urlWithParams, options){\n\treturn new Promise(function(resolve, reject){\n\t\t// console.log(urlWithParams);\n\t\taxios.get(urlWithParams, options).then(response => {\n\t\t\tresolve(response.data)\n\t\t}).catch(e => {\n\t\t\t// console.log(e);\n\t\t\treject(e)\n\t\t})\n\t})\n}\n\n/**\n* Simple MailgunApi accessor class for reading event stream, and saved emails\n*\n* Example usage\n* ```\n* let reader = new mailgunReader( { apiKey:\"api-*****\", emailDomain:\"inboxkitten.com\" })\n*\n* // Returns a list of email recieve events\n* reader.recipientEventList(\"some-domain.inboxkitten.com\");\n*\n* // Get and return the email json\n* reader.getRecipentEmail(\"some-email-id\");\n* ```\n*/\nlet mailgunReader = function mailgunReader(config) {\n\n\t// The config object being used\n\tthis._config = config;\n\n\t// Validate the config for required parameters\n\tif( this._config.apiKey == null || this._config.apiKey.length <= 0 ) {\n\t\tthrow new Error(\"Missing config.apiKey\");\n\t}\n\tif( this._config.emailDomain == null || this._config.emailDomain.length <= 0 ) {\n\t\tthrow new Error(\"Missing config.emailDomain\");\n\t}\n\n\t// Default mailgun domain if not used\n\tthis._config.mailgunApi = this._config.mailgunApi || \"https://api.mailgun.net/v3\";\n\n\t// Setup the authentication option object\n\tthis._authOption = {\n\t\tauth: {\n\t\t\tusername : \"api\",\n\t\t\tpassword : this._config.apiKey\n\t\t}\n\t};\n}\n\n/**\n * Validate the request email against list of domains\n *\n * @param {String} email\n */\nmailgunReader.prototype.recipientEmailValidation = function recipientEmailValidation(email) {\n\t// @TODO - the validation\n\treturn true;\n}\n\n/**\n * Get and return a list of email events\n *\n * See : https://documentation.mailgun.com/en/latest/api-events.html#event-structure\n *\n * @param {String} email\n *\n * @return  Promise object, returning list of email events\n */\nmailgunReader.prototype.recipientEventList = function recipientEventList(email) {\n\t// Validate email format\n\tif( !this.recipientEmailValidation(email) ) {\n\t\treturn Promise.reject(\"Invalid email format : \"+email);\n\t}\n\n\t// Compute the listing url\n\tlet urlWithParams = this._config.mailgunApi+\"/\"+this._config.emailDomain+\"/events?recipient=\"+email;\n\n\t// Lets get and return it with a promise\n\treturn axiosGet(urlWithParams, this._authOption);\n}\n\n/**\n * Validate the url parameter for a valid mailgun api URL.\n * This is to safeguard the getURL from api key leakage\n *\n * @param {String} url\n */\nmailgunReader.prototype.getUrlValidation = function getUrlValidation(email) {\n\t// @TODO - the validation\n\treturn true;\n}\n\n/**\n * Get the content of URL and return it, using the mailgun key.\n * This is useful for stored emails returned by the event stream.\n *\n * @param {String} url\n */\nmailgunReader.prototype.getUrl = function getUrl(url) {\n\t// Validate the URL\n\tif( !this.getUrlValidation(url) ) {\n\t\treturn Promise.reject(\"Invalid getUrl request : \"+url);\n\t}\n\n\t// Lets get and return it with a promise\n\treturn axiosGet(url, this._authOption);\n}\n\n/**\n * Get the content of URL and return it, using the mailgun key.\n * This is useful for stored emails returned by the event stream.\n *\n * @param {String} url\n */\nmailgunReader.prototype.getKey = function getKey({region, key}) {\n\n\t// Inject the region to the mailgunApi\n\tlet apiUrl = this._config.mailgunApi\n\tapiUrl = apiUrl.replace(\"://\", \"://storage-\" + region + \".\")\n\tlet urlWithParams = apiUrl + \"/domains/\" + this._config.emailDomain + \"/messages/\" + key;\n\t\n\t// Lets get and return it with a promise\n\treturn axiosGet(urlWithParams, this._authOption);\n}\n\n// Export the mailgunReader class\nmodule.exports = mailgunReader;\n"
  },
  {
    "path": "api/test/mailgunReader.test.js",
    "content": "// Dependencies loading\nconst assert  = require('assert');\nconst delay   = require('delay');\nconst md5     = require('md5');\nconst uuidv4  = require('uuid/v4');\nconst shortid = require('shortid');\n\n// MailgunReader class\nconst mailgunReader = require(\"../src/mailgunReader\");\nconst mailgunConfig = require(\"../config/mailgunConfig\");\n\n// MailgunReader instance\nconst reader = new mailgunReader(mailgunConfig);\n\n// Does the test suite\ndescribe('mailgunReader', function() {\n\n\t//\n\t// Testing for valid \"404\" errors\n\t//\n\tdescribe('empty-guid-inbox-requests', function() {\n\t\t// when using a uuid (unless collision occured D=)\n\t\tit('should return empty event list', async () => {\n\t\t\t\n\t\t\t// Get the id to validate\n\t\t\tlet id = md5(uuidv4());\n\t\t\tassert.notEqual(id, null);\n\n\t\t\t// Get the list of emails (as item)\n\t\t\tlet eventListObj = await reader.recipientEventList(id+\"@\"+mailgunConfig.emailDomain);\n\t\t\tassert.notEqual(eventListObj, null);\n\t\t\tassert.equal( eventListObj.items.length , 0);\n\t\t});\n\t});\n\n\t//\n\t// Testing the sending, listing and reading of emails\n\t//\n\tdescribe('send-list-recieve', function() {\n\t\t// Get the emails to send and recieve from\n\t\tlet sender = md5(uuidv4())+\"@\"+mailgunConfig.emailDomain;\n\t\tlet reciever = md5(uuidv4())+\"@\"+mailgunConfig.emailDomain;\n\t\tlet emailContent = md5(uuidv4());\n\n\t\t// Test timeout to use\n\t\tlet thirtySeconds = 30 * 1000;\n\n\t\t// Sending of email\n\t\tit('sending-of-email', async () => {\n\n\t\t\t// Initialize the mailgun sender and its data\n\t\t\tlet mailgunSender = require('mailgun-js')({apiKey: mailgunConfig.apiKey, domain: mailgunConfig.emailDomain});\n\t\t\tlet data = {\n\t\t\t\tfrom : sender,\n\t\t\t\tto : reciever,\n\t\t\t\tsubject : \"Testing keys\",\n\t\t\t\ttext : emailContent\n\t\t\t}\n\n\t\t\t// Send the email in a promise\n\t\t\tlet sendPromise = new Promise(function(good,bad) {\n\t\t\t\tmailgunSender.messages().send(data,function(error, body) {\n\t\t\t\t\tif( error ) {\n\t\t\t\t\t\tconsole.log(error);\n\t\t\t\t\t\tbad(error);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tgood(body);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t});\n\n\t\t\t// Wait for sending to complete\n\t\t\tlet sendPromiseResult = await sendPromise;\n\t\t}).timeout(thirtySeconds);\n\n\t\t// The email event to use\n\t\tlet emailEvent = null;\n\n\t\t// Listing of email\n\t\tit('listing-of-email', async () => {\n\n\t\t\t// Loop and get email event (there might be significant time delay)\n\t\t\twhile(emailEvent == null) {\n\t\t\t\t// Lets not spam\n\t\t\t\tawait delay(2500);\n\n\t\t\t\t// Get the list of emails (as item)\n\t\t\t\tlet eventListObj = await reader.recipientEventList(reciever);\n\t\t\t\tassert.notEqual(eventListObj, null);\n\n\t\t\t\t// Continue loop if length is 0\n\t\t\t\tif( eventListObj.items.length == 0 ) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Get the email event\n\t\t\t\t//assert.equal(eventListObj.items.length, 1);\n\t\t\t\temailEvent = eventListObj.items[0];\n\t\t\t\tassert.notEqual(emailEvent, null);\n\t\t\t}\n\n\t\t}).timeout(thirtySeconds * 5);\n\n\t\t// Listing of email\n\t\tit('reading-of-email', async () => {\n\t\t\tassert.notEqual(emailEvent, null);\n\n\t\t\t// Get the email URL\n\t\t\tlet emailUrl = emailEvent.storage.url;\n\t\t\tassert.notEqual(emailUrl, null);\n\t\t\t\n\t\t\t// Lets get the email content\n\t\t\tlet content = await reader.getUrl(emailUrl);\n\t\t\tassert.notEqual(content, null);\n\t\t\t\n\t\t\t// Assert content exists with GUID, \n\t\t\t// which is definitive proof that the test work,\n\t\t\t// unless multiple suns started exploding\n\t\t\tassert.equal( content[\"stripped-html\"].indexOf(emailContent) >= 0, true );\n\t\t});\n\t});\n\n\n});"
  },
  {
    "path": "api/webpack.config.js",
    "content": "module.exports = {\n\toptimization: {\n\t\t// We no not want to minimize our code.\n\t\tminimize: false\n\t}\n}"
  },
  {
    "path": "build.sh",
    "content": "#!/bin/bash\n\n# Deploy will terminate on any error\nset -e\n\n# Project directory detection\nprojectDir=\"`dirname \\\"$0\\\"`\"\ncd \"$projectDir\" || exit 1\nprojectDir=\"`pwd`\"\necho \">> Assuming project directory of : $projectDir\"\n\n# Build the UI + CLI\necho \">> Building the UI (NPM install + run build)\"\ncd \"$projectDir/ui\"\nnpm install;\nnpm run build;\n\n# Build the API\necho \">> Building the API (NPM install)\"\ncd \"$projectDir/api\"\nnpm install;\n# npm run build;\n\n# Build the CLI\necho \">> Building the CLI\"\ncd \"$projectDir/cli\"\nmake build;\n"
  },
  {
    "path": "cli/Makefile",
    "content": "# Change this to your respective main file\nmainfile=\"inboxkitten.go\"\noutfile=\"inboxkitten\"\n\n# The go build command\ndependencies:\n\t./go.sh get ./src/...\n\nbuild:\n\tmkdir -p bin\n\t./go.sh build -o \"bin/$(outfile)\" \"src/$(mainfile)\" \n\nclean:\n\trm -fr ./bin/*\n\t./go.sh clean\n"
  },
  {
    "path": "cli/README.md",
    "content": "# What is go.sh\n\n> TLDR: Its a bash script that \"rewrites\" the `$GOPATH` to the folder holding `go.sh` sub `gopath` folder, and configuring `GOBIN` within it.\n\nFor the uninformed, `$GOPATH` is the global environment variable in which GO compile its projects and dependencies. Which by default is a shared user directory,\nwhich is suppose to hold _all_ your, dependencies, and project code. This is great.... assuming all you go code works in harmony.\n\nHowever the world is not a perfect harmony, something as simple as trying to clone an older version of the same project for debugging, while keeping the newer copy.\nAnd having a 100 different projects in the same workspace clashing with one another. Becomes a huge chore quickly. \n\nWith `go.sh` this whole project repository more self contained, and its files and build function like most other software projects (NPM, ant, gradle ...)\nDown to having duplicate dependencies of possibly different versions across multiple projects.\n\nSo yea _insert swear word_ I hate `$GOPATH`\n\nFor summary on issue\n\n+ https://www.reddit.com/r/golang/comments/7h02zn/whats_the_point_of_gopath/\n+ https://www.reddit.com/r/golang/comments/40ps8k/really_wrestling_with_gopath/\n+ https://news.ycombinator.com/item?id=14763493\n\nAlternative Workarounds\n\n+ https://github.com/getstream/vg\n\n# Help : I do not have go installed.\n\nUnfortunately, installing go is frankly kinda a multistep pain.\nSee the guides for more details.\n\nFor ubuntu : https://www.digitalocean.com/community/tutorials/how-to-install-go-1-6-on-ubuntu-16-04\nFor macos  : https://ahmadawais.com/install-go-lang-on-macos-with-homebrew/\n\nFor ubuntu you probably would need `sudo apt-get install build-essential` for make file support as well"
  },
  {
    "path": "cli/go.sh",
    "content": "#!/bin/bash\n\n# Working directory\nworkingDir=\"`dirname \\\"$0\\\"`\"\ncd \"$workingDir\" || exit 1\n\n# GOPATH overwrite\nWORKING_PWD=`pwd $workingDir`\nmkdir -p \"$WORKING_PWD/gopath/src\"\nexport GOPATH=\"$WORKING_PWD/GOPATH\"\nexport GOBIN=\"$GOPATH/bin\"\n\n# Execute go with all other arguments\ngo $@\n"
  },
  {
    "path": "cli/src/inboxkitten.go",
    "content": "package main\n\n//---------------------------------------------------------------------------------------\n//\n// Dependencies import\n//\n//---------------------------------------------------------------------------------------\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"os\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"time\"\n\t\"log\"\n\t\"bytes\"\n\t\"encoding/json\"\n\t// \"github.com/hokaccha/go-prettyjson\"\n)\n\n//---------------------------------------------------------------------------------------\n//\n// Utility functions\n//\n//---------------------------------------------------------------------------------------\n\nfunc jsonPrettifier( raw string ) string {\n\tbuf := new(bytes.Buffer)\n\tjson.Indent(buf, []byte(raw), \"\", \"  \")\n\treturn buf.String()\n}\n\n//---------------------------------------------------------------------------------------\n//\n// Http Request functions\n//\n//---------------------------------------------------------------------------------------\n\nfunc doGetRequest( url string ) []byte {\n\tclient := http.Client{\n\t\tTimeout: time.Second * 2,\n\t};\n\n\treq, err := http.NewRequest(http.MethodGet, url, nil);\n\tif err != nil {\n\t\tlog.Fatal(err);\n\t}\n\n\tres, getErr := client.Do(req);\n\tif getErr != nil {\n\t\tlog.Fatal(getErr);\n\t}\n\t\n\tbody, readErr := ioutil.ReadAll(res.Body);\n\tif readErr != nil {\n\t\tlog.Fatal(readErr);\n\t}\n\n\treturn body;\n}\n\n//---------------------------------------------------------------------------------------\n//\n// Main CLI functions\n//\n// Note most of the CLI coding was done with referencing to :\n//\n// https://gobyexample.com/command-line-arguments\n// https://gobyexample.com/command-line-flags\n// https://blog.rapid7.com/2016/08/04/build-a-simple-cli-tool-with-golang/\n//\n//---------------------------------------------------------------------------------------\n\n//\n// Main CLI\n//\nfunc main() {\n\n\t// The api url with default value\n\tvar apiDefault = \"https://us-central1-ulicious-inbox-kitten.cloudfunctions.net/api-v1\"\n\tvar api string\n\tflag.StringVar(&api, \"api\", apiDefault, \"URL to inbox kitten API\")\n\n\t// Parse all the flags\n\tflag.Parse();\n\t\n\t// Output the api URL if its custom\n\tif( api != apiDefault ) {\n\t\tfmt.Printf(\"Using Custom API: %s \\n\", api)\n\t}\n\n\t// Post flag processing args\n\tvar flagArgs = flag.Args();\n\n\t// Verify that a subcommand has been provided\n\t// flagArgs[0] is the main command\n\t// flagArgs[1] will be the subcommand\n\tvar missingCommandError = \"`list [email]` or `get [emailid]` subcommand is required\\n\";\n\tif len(flagArgs) <= 0 {\n\t\tfmt.Fprintf(os.Stderr, missingCommandError);\n\t\tflag.PrintDefaults();\n\t\tos.Exit(1);\n\t}\n\n\t// The list and get command\n\tgetCommand := flag.NewFlagSet(\"get\", flag.ExitOnError)\n\tlistCommand := flag.NewFlagSet(\"list\", flag.ExitOnError)\n\n\t// Switch on the subcommand\n\t// Parse the flags for appropriate FlagSet\n\t// FlagSet.Parse() requires a set of arguments to parse as input\n\t// flagArgs[2:] will be all arguments starting after the subcommand at flagArgs[1]\n\tswitch flagArgs[0] {\n\t\tcase \"list\":\n\t\t\tlistCommand.Parse(flagArgs[1:])\n\t\tcase \"get\":\n\t\t\tgetCommand.Parse(flagArgs[1:])\n\t\tdefault:\n\t\t\tfmt.Fprintf(os.Stderr, missingCommandError);\n\t\t\tflag.PrintDefaults();\n\t\t\tos.Exit(1);\n\t}\n\n\t//\n\t// Processing of LIST command\n\t//\n\tif listCommand.Parsed() {\n\t\tvar listArgs = listCommand.Args();\n\t\tif len(listArgs) <= 0 {\n\t\t\tfmt.Fprintf(os.Stderr, \"`list [email]` missing email parameter\\n\");\n\t\t\tos.Exit(1);\n\t\t}\n\n\t\tvar email = listArgs[0];\n\t\t\n\t\tvar urlWithParams = api+\"/mail/list?recipient=\"+email\n\t\tvar body = doGetRequest(urlWithParams);\n\t\tfmt.Println( jsonPrettifier( string(body) ) );\n\t}\n\n\t//\n\t// Processing of GET command\n\t//\n\tif getCommand.Parsed() {\n\t\tvar getArgs = getCommand.Args();\n\t\tif len(getArgs) <= 1 {\n\t\t\tfmt.Fprintf(os.Stderr, \"`get [region] [key]` missing region and key parameter\\n\");\n\t\t\tos.Exit(1);\n\t\t}\n\n\t\tvar region = getArgs[0];\n\t\tvar key = getArgs[1];\n\n\t\tvar urlWithParams = api+\"/mail/getKey?mailKey=\"+region+\"-\"+key;\n\t\tvar body = doGetRequest(urlWithParams);\n\n\t\tfmt.Println( jsonPrettifier( string(body) ) );\n\t}\n}\n\n"
  },
  {
    "path": "config.sh",
    "content": "#!/bin/bash\n\n# Deploy will terminate on any error\nset -e\n\n# Firebase Working directory\nprojectDir=\"`dirname \\\"$0\\\"`\"\ncd \"$projectDir\" || exit 1\nprojectDir=\"`pwd`\"\n\n#\n# Scanning and installing dependencies\n# (Assuming a mac)\n#\nif [ -z \"$(which envsubst)\" ]; then\n\tif [ -z \"$(which brew)\" ]; then\n\t\techo \">> envsubst not detected : please install =[\"\n\telse\n\t\techo \">> envsubst not detected : using brew to install\"\n\t\tbrew install gettext\n\t\tbrew link --force gettext \n\tfi\nfi\nif [ -z \"$(which npm)\" ]; then\n\techo \">> NPM not detected : please install =[\"\n\texit 1;\nfi\nif [ -z \"$(which node)\" ]; then\n\techo \">> node not detected : please install =[\"\n\texit 1;\nfi\nif [ -z \"$(which go)\" ]; then\n\techo \">> go not detected : please install =[\"\n\texit 1;\nfi\n\n#\n# Getting the various configuration settings from command line / environment variable\n#\nif [ -z \"$MAILGUN_EMAIL_DOMAIN\" ]; then\n\techo \">> Please type in your MAILGUN_EMAIL_DOMAIN (eg: inboxkitten.com)\";\n\tread -p '>> MAILGUN_EMAIL_DOMAIN : ' MAILGUN_EMAIL_DOMAIN;\nelse\n\techo \">> Detected MAILGUN_EMAIL_DOMAIN env variable : $MAILGUN_EMAIL_DOMAIN\";\nfi\n\nif [ -z \"$WEBSITE_DOMAIN\" ]; then\n\techo \">> Please type in your WEBSITE_DOMAIN (eg: inboxkitten.com)\";\n\tread -p '>> WEBSITE_DOMAIN : ' WEBSITE_DOMAIN;\nelse\n\techo \">> Detected WEBSITE_DOMAIN env variable : $WEBSITE_DOMAIN\";\nfi\n\nif [ -z \"$MAILGUN_API_KEY\" ]; then\n\techo \">> Please type in your MAILGUN_API_KEY\";\n\tread -sp '>> MAILGUN_API_KEY : ' MAILGUN_API_KEY;\n\techo \"\";\nelse\n\techo \">> Detected MAILGUN_API_KEY env variable : [intentionally redacted]\";\nfi\n\n#\n# Exporting variables, for envsubst support\n#\nexport MAILGUN_EMAIL_DOMAIN=\"$MAILGUN_EMAIL_DOMAIN\"\nexport MAILGUN_API_KEY=\"$MAILGUN_API_KEY\"\nexport WEBSITE_DOMAIN=\"$WEBSITE_DOMAIN\"\n\n#\n# Applying the configuration\n#\necho \">> Applying config settings\"\ncat \"$projectDir/api/config/mailgunConfig.sample.js\" | envsubst > \"$projectDir/api/config/mailgunConfig.js\"\ncat \"$projectDir/ui/config/apiconfig.sample.js\" | envsubst > \"$projectDir/ui/config/apiconfig.js\""
  },
  {
    "path": "deploy/cloudflare/deploy.sh",
    "content": "#!/bin/bash\n\n# Deploy will terminate on any error\nset -e\n\n# cloudflare Working directory\ncloudflareDir=\"`dirname \\\"$0\\\"`\"\ncd \"$cloudflareDir\" || exit 1\ncloudflareDir=\"`pwd`\"\n\n# Project directory detection\nprojectDir=\"$(cd \"$cloudflareDir/../..\"; pwd)\"\necho \">> Assuming project directory of : $projectDir\"\n\n# Clearing out cloudflare public / functions folder\nrm -rf \"$cloudflareDir/dist/\"; \nmkdir -p \"$cloudflareDir/dist/\";\n\n# Transfering files into fire base deploy folder\necho \">> Preparing build files for api cloudflare upload\"\ncd \"$projectDir/api/\"\nnpm run build-cloudflare\ncp -a \"$projectDir/api/dist/.\" \"$cloudflareDir/dist/\"\n\n# Add in commit hash, to help debug deployment build\ngit rev-parse HEAD > \"$cloudflareDir/dist/GITHASH\"\n\n# Debug for file tree\ncd \"$cloudflareDir\"\nif [ ! -z \"DISPLAY_DEPLOY_FILE_TREE\" ]; then\n\ttree -L 3;\nfi\n\n#\n# Getting the various configuration settings from command line / environment variable\n#\n\nif [ -z \"$CLOUDFLARE_EMAIL\" ]; then\n\techo \">> Please type in your CLOUDFLARE_EMAIL (eg: admin@yourdomain.com)\";\n\tread -p '>> CLOUDFLARE_EMAIL : ' CLOUDFLARE_EMAIL;\nelse\n\techo \">> Detected CLOUDFLARE_EMAIL env variable : $CLOUDFLARE_EMAIL\";\nfi\n\nif [ -z \"$CLOUDFLARE_API_KEY\" ]; then\n\techo \">> Please type in your CLOUDFLARE_API_KEY\";\n\tread -sp '>> CLOUDFLARE_API_KEY : ' CLOUDFLARE_API_KEY;\n\techo \"\";\nelse\n\techo \">> Detected CLOUDFLARE_API_KEY env variable : [intentionally redacted]\";\nfi\n\nif [ -z \"$CLOUDFLARE_ZONE_ID\" ]; then\n\techo \">> Please type in your CLOUDFLARE_ZONE_ID\";\n\tread -sp '>> CLOUDFLARE_ZONE_ID : ' CLOUDFLARE_ZONE_ID;\n\techo \"\";\nelse\n\techo \">> Detected CLOUDFLARE_ZONE_ID env variable : [intentionally redacted]\";\nfi\n\nif [ -z \"$MAILGUN_EMAIL_DOMAIN\" ]; then\n\techo \">> Please type in your MAILGUN_EMAIL_DOMAIN (eg: inboxkitten.com)\";\n\tread -p '>> MAILGUN_EMAIL_DOMAIN : ' MAILGUN_EMAIL_DOMAIN;\nelse\n\techo \">> Detected MAILGUN_EMAIL_DOMAIN env variable : $MAILGUN_EMAIL_DOMAIN\";\nfi\n#\n# Exporting variables, for envsubst support\n#\nexport MAILGUN_EMAIL_DOMAIN=\"$MAILGUN_EMAIL_DOMAIN\"\nexport CLOUDFLARE_ZONE_ID=\"$CLOUDFLARE_ZONE_ID\"\nexport CLOUDFLARE_API_KEY=\"$CLOUDFLARE_API_KEY\"\nexport CLOUDFLARE_EMAIL=\"$CLOUDFLARE_EMAIL\"\n\n# Calling cloudflare deploy, with parameters passing forward\necho \">> Deploying to cloudflare\"\necho\ncurl -X PUT \"https://api.cloudflare.com/client/v4/zones/\"$CLOUDFLARE_ZONE_ID\"/workers/script\" -H \"X-Auth-Email:$CLOUDFLARE_EMAIL\" -H \"X-Auth-Key:$CLOUDFLARE_API_KEY\" -H \"Content-Type:application/javascript\" --data-binary \"@./dist/main.js\"\n\nread -p \">> Set up route on cloudflare? (yes/no)\" CLOUDFLARE_SETUP_ROUTE;\nif [ \"$CLOUDFLARE_SETUP_ROUTE\" == \"yes\" ]; then\n    echo \">> Setting route on cloudflare\"\n    curl -X POST \"https://api.cloudflare.com/client/v4/zones/\"$CLOUDFLARE_ZONE_ID\"/workers/filters\" -H \"X-Auth-Email:$CLOUDFLARE_EMAIL\" -H \"X-Auth-Key:$CLOUDFLARE_API_KEY\" -H \"Content-type: application/json\" -d '{\"pattern\": \"'$MAILGUN_EMAIL_DOMAIN'/api/*\", \"enabled\": true}'\nfi\n\necho \">> Cloudflare script completed\""
  },
  {
    "path": "deploy/firebase/deploy.sh",
    "content": "#!/bin/bash\n\n# Deploy will terminate on any error\nset -e\n\n# Firebase Working directory\nfirebaseDir=\"`dirname \\\"$0\\\"`\"\ncd \"$firebaseDir\" || exit 1\nfirebaseDir=\"`pwd`\"\n\n# Project directory detection\nprojectDir=\"$(cd \"$firebaseDir/../..\"; pwd)\"\necho \">> Assuming project directory of : $projectDir\"\n\n# Clearing out firebase public / functions folder\nrm -rf \"$firebaseDir/functions/\"; \nrm -rf \"$firebaseDir/public/\"; \nmkdir -p \"$firebaseDir/public/cli/\";\nmkdir -p \"$firebaseDir/functions/\";\n\n# Transfering files into fire base deploy folder\necho \">> Preparing build files for firebase upload\"\ncp -a \"$projectDir/ui/dist/.\" \"$firebaseDir/public/\"\ncp -a \"$projectDir/cli/bin/.\" \"$firebaseDir/public/cli/\"\ncp -a \"$projectDir/api/.\" \"$firebaseDir/functions/\"\n\n# Reconfigure the API function for firebase\ncp \"$firebaseDir/functions/firebase.js\" \"$firebaseDir/functions/index.js\"\n\n# Add in commit hash, to help debug deployment build\ngit rev-parse HEAD > \"$firebaseDir/public/GITHASH\"\n\n# Debug for file tree\ncd \"$firebaseDir\"\nif [ ! -z \"DISPLAY_DEPLOY_FILE_TREE\" ]; then\n\ttree -L 3;\nfi\n\n# Calling firebase deploy, with parameters passing forward\necho \">> Deploying to firebase\"\nfirebase deploy $@\n"
  },
  {
    "path": "deploy/firebase/firebase.json",
    "content": "{\n\t\"hosting\": {\n\t\t\"public\": \"public\",\n\t\t\"ignore\": [\n\t\t\t\"firebase.json\",\n\t\t\t\"**/.*\",\n\t\t\t\"**/node_modules/**\"\n\t\t],\n\t\t\"rewrites\": [\n\t\t\t{\n\t\t\t\t\"source\": \"/api/v1/mail/**\",\n\t\t\t\t\"function\": \"firebase_api_v1\"\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"source\": \"**\",\n\t\t\t\t\"destination\": \"/index.html\"\n\t\t\t}\n\t\t]\n\t}\n}\n"
  },
  {
    "path": "docker-dev-build.sh",
    "content": "#!/bin/bash\n\ndocker stop $(docker ps -a | grep $(basename \"$PWD\") | awk '{print $1}'); \ndocker rm $(docker ps -a | grep $(basename \"$PWD\") | awk '{print $1}');\ndocker build -t $(basename \"$PWD\") .  \n# docker run -d -P --name $(basename \"$PWD\") $(basename \"$PWD\");"
  },
  {
    "path": "docker-entrypoint.sh",
    "content": "#!/bin/sh\n\n#\n# Entrypoint start\n#\necho \">>---------------------------------------------------------------------\"\necho \">> Starting inboxkitten container : Get Mail Nyow!\"\necho \">>---------------------------------------------------------------------\"\n\n#\n# Getting the various configuration settings from command line / environment variable\n#\nif [ -z \"$MAILGUN_EMAIL_DOMAIN\" ]; then\n\techo \"[FATAL ERROR] Missing MAILGUN_EMAIL_DOMAIN (eg: inboxkitten.com)\";\n    exit 1;\nelse\n\techo \">> Detected MAILGUN_EMAIL_DOMAIN env variable : $MAILGUN_EMAIL_DOMAIN\";\nfi\n\nif [ -z \"$MAILGUN_API_KEY\" ]; then\n\techo \"[FATAL ERROR] Missing MAILGUN_API_KEY\";\n\texit 1;\nelse\n\techo \">> Detected MAILGUN_API_KEY env variable : [intentionally redacted]\";\nfi\n\nif [ -z \"$WEBSITE_DOMAIN\" ]; then\n\techo \">> Missing WEBSITE_DOMAIN, using MAILGUN_EMAIL_DOMAIN : $MAILGUN_EMAIL_DOMAIN\"\n    export WEBSITE_DOMAIN=\"$MAILGUN_EMAIL_DOMAIN\"\nelse\n\techo \">> Detected WEBSITE_DOMAIN env variable : $WEBSITE_DOMAIN\";\nfi\n\n#\n# End of env variable checks\n# Moving to config setups\n#\necho \">>---------------------------------------------------------------------\"\n\n# Debug check\n# ls /application/\n\n#\n# Setup the UI\n#\necho \">> Setting up UI\"\n\n# Clone the files\nrm -rf /application/api/public/\nmkdir /application/api/public/\ncp -r /application/ui-dist/* /application/api/public/\n\n# Debug check\n# ls /application/api/public/\n\n# Search token (so that it does not get character substituted)\nTOKEN_MAILGUN_EMAIL_DOMAIN='${MAILGUN_EMAIL_DOMAIN}'\nTOKEN_WEBSITE_DOMAIN='${WEBSITE_DOMAIN}'\n\n# Find and replace\nfind /application/api/public/ -type f -exec sed -i \"s/$TOKEN_MAILGUN_EMAIL_DOMAIN/$MAILGUN_EMAIL_DOMAIN/g\" {} +\nfind /application/api/public/ -type f -exec sed -i \"s/$TOKEN_WEBSITE_DOMAIN/$WEBSITE_DOMAIN/g\" {} +\n\n#\n# Setup the API\n#\necho \">> Setting up API config\"\ncat \"/application/api/config/mailgunConfig.sample.js\" | envsubst > \"/application/api/config/mailgunConfig.js\"\n\n#\n# Start the server\n#\necho \">>---------------------------------------------------------------------\"\necho \">> Starting the server\"\necho \">>---------------------------------------------------------------------\"\ncd /application/api/\nnpm start\n"
  },
  {
    "path": "docker-notes.md",
    "content": "# Docker build process\n\n## inside the respective folder (eg: ./ssh/)\ndocker build -t {tagname} .\ndocker build -t $(basename \"$PWD\") .\n\n## running the build\ndocker run -d -P --name {tagname} {container_name}\ndocker run -d -P --name $(basename \"$PWD\") $(basename \"$PWD\")\n\n-d : detach mode\n-P : auto publish all ports\n-e : set env key=value pairs\n\n## adding listener port\ndocker port containername XPortNum\n\n# Useful command chains\n\n## build, run\ndocker build -t $(basename \"$PWD\") . && docker run -d -P --name $(basename \"$PWD\") $(basename \"$PWD\");\n\n## nuke all, build, run\ndocker rm $(docker ps -a -q); docker build -t $(basename \"$PWD\") . && docker run -d -P --name $(basename \"$PWD\") $(basename \"$PWD\");\n\n## stop all, nuke all, build, run\ndocker stop $(docker ps -aq); docker rm $(docker ps -a -q); docker build -t $(basename \"$PWD\") . && docker run -d -P --name $(basename \"$PWD\") $(basename \"$PWD\");\n\n# Stop current \"basename\", Nuke it, Build it, Run it\n```\ndocker stop $(docker ps -a | grep $(basename \"$PWD\") | awk '{print $1}'); \ndocker rm $(docker ps -a | grep $(basename \"$PWD\") | awk '{print $1}');\ndocker build -t $(basename \"$PWD\") . && \ndocker run -d -P --name $(basename \"$PWD\") $(basename \"$PWD\");\n```\n\nOr as a single line\n\n`docker stop $(docker ps -a | grep $(basename \"$PWD\") | awk '{print $1}'); docker rm $(docker ps -a | grep $(basename \"$PWD\") | awk '{print $1}'); docker build -t $(basename \"$PWD\") . && docker run -d -P --name $(basename \"$PWD\") $(basename \"$PWD\");`\n\n# Docker commands\n\n## Delete all containers\ndocker rm $(docker ps -a -q)\n## Delete all images\ndocker rmi $(docker images -q)\n\n\n# ENTRYPOINT vs CMD\n\n- ENTRYPOINT is runs WITH cmd,\n- CMD is commonly overwritten by user\n- Technically user can overwrite ENTRYPOINT with additional command \"flags\"\n- Final executed command : ENTRYPOINT CMD\n"
  },
  {
    "path": "ui/.gitignore",
    "content": ".DS_Store\nnode_modules/\n/dist/\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Editor directories and files\n.idea\n.vscode\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n"
  },
  {
    "path": "ui/README.md",
    "content": "# inboxkitten\n\n> A personal disposable email ui\n\n## Build Setup\n\n``` bash\n# install dependencies\nnpm install\n\n# serve with hot reload at localhost:8080\nnpm run dev\n\n# build for production with minification\nnpm run build\n\n# build for production and view the bundle analyzer report\nnpm run build --report\n```\n\nFor a detailed explanation on how things work, check out the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader).\n"
  },
  {
    "path": "ui/commonshost.js",
    "content": "let commonsHostConfig = {\n\troot: './index.html',\n\tfallback: { 200: './index.html' },\n\tdirectories: { trailingSlash: 'never' },\n\taccessControl: { allowOrigin: '*' },\n\theaders: [\n\t\t{ fields: { 'X-Frame-Options': 'deny' } }\n\t\t// {\n\t\t// \turi: '/static/{dir}/{filename}.{hash}.{type}',\n\t\t// \tfields: {\n\t\t// \t\t'Cache-Control': 'public, max-age=31536000, immutable'\n\t\t// \t}\n\t\t// }\n\t],\n\tmanifest: [{ get: '/index.html', push: '/favicon.ico' }]\n}\n\nmodule.exports = {\n\thosts: [\n\t\tObject.assign({ domain: 'inboxkitten.com' }, commonsHostConfig),\n\t\tObject.assign({ domain: 'commonshost-raw.inboxkitten.com' }, commonsHostConfig),\n\t\tObject.assign({ domain: 'commonshost.inboxkitten.com' }, commonsHostConfig)\n\t]\n}"
  },
  {
    "path": "ui/config/apiconfig.sample.js",
    "content": "export default {\n\tapiUrl: '//${WEBSITE_DOMAIN}/api/v1/mail',\n\tdomain: '${MAILGUN_EMAIL_DOMAIN}'\n}\n"
  },
  {
    "path": "ui/config/carbonads.js",
    "content": "module.exports = {\n\t\"enable\": true,\n\t\"carbon_ad_src\": \"https://cdn.carbonads.com/carbon.js?serve=CK7D5KQW&placement=inboxkittencom\",\n\t\"placement\": [\"InboxKittenLanding\"]\n}"
  },
  {
    "path": "ui/config/dev.env.js",
    "content": "'use strict'\nconst merge = require('webpack-merge')\nconst prodEnv = require('./prod.env')\n\nmodule.exports = merge(prodEnv, {\n  NODE_ENV: '\"development\"'\n})\n"
  },
  {
    "path": "ui/config/index.js",
    "content": "'use strict'\n// Template version: 1.3.1\n// see http://vuejs-templates.github.io/webpack for documentation.\n\nconst path = require('path')\n\nmodule.exports = {\n  dev: {\n\n    // Paths\n    assetsSubDirectory: 'static',\n    assetsPublicPath: '/',\n    proxyTable: {},\n\n    // Various Dev Server settings\n    host: 'localhost', // can be overwritten by process.env.HOST\n    port: 8000, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined\n    autoOpenBrowser: false,\n    errorOverlay: true,\n    notifyOnErrors: true,\n    poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-\n\n    // Use Eslint Loader?\n    // If true, your code will be linted during bundling and\n    // linting errors and warnings will be shown in the console.\n    useEslint: true,\n    // If true, eslint errors and warnings will also be shown in the error overlay\n    // in the browser.\n    showEslintErrorsInOverlay: false,\n\n    /**\n     * Source Maps\n     */\n\n    // https://webpack.js.org/configuration/devtool/#development\n    devtool: 'cheap-module-eval-source-map',\n\n    // If you have problems debugging vue-files in devtools,\n    // set this to false - it *may* help\n    // https://vue-loader.vuejs.org/en/options.html#cachebusting\n    cacheBusting: true,\n\n    cssSourceMap: true\n  },\n\n  build: {\n    // Template for index.html\n    index: path.resolve(__dirname, '../dist/index.html'),\n\n    // Paths\n    assetsRoot: path.resolve(__dirname, '../dist'),\n    assetsSubDirectory: 'static',\n    assetsPublicPath: '/',\n\n    /**\n     * Source Maps\n     */\n\n    productionSourceMap: true,\n    // https://webpack.js.org/configuration/devtool/#production\n    devtool: '#source-map',\n\n    // Gzip off by default as many popular static hosts such as\n    // Surge or Netlify already gzip all static assets for you.\n    // Before setting to `true`, make sure to:\n    // npm install --save-dev compression-webpack-plugin\n    productionGzip: false,\n    productionGzipExtensions: ['js', 'css'],\n\n    // Run the build command with an extra argument to\n    // View the bundle analyzer report after build finishes:\n    // `npm run build --report`\n    // Set to `true` or `false` to always turn it on or off\n    bundleAnalyzerReport: process.env.npm_config_report\n  }\n}\n"
  },
  {
    "path": "ui/config/prod.env.js",
    "content": "'use strict'\nmodule.exports = {\n  NODE_ENV: '\"production\"'\n}\n"
  },
  {
    "path": "ui/config/shareConfig.js",
    "content": "export default {\n  githubForkLink: \"https://github.com/uilicious/inboxkitten/fork\",\n  githubAriaLabel: \"Fork uilicious/inboxkitten on GitHub\",\n  tweetMessage: \"InboxKitten is amazing! Check it out here at\",\n  tweetDataUrl: \"https://inboxkitten.com\",\n  mainURL: \"https://inboxkitten.com\",\n  enabled: true\n}\n"
  },
  {
    "path": "ui/index.html",
    "content": "<!DOCTYPE html>\n<html>\n\t<head>\n\t\t<meta charset=\"utf-8\">\n\t\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\" />\n\t\t<meta property=\"og:title\" content=\"InboxKitten\"/>\n\t\t<meta property=\"og:url\" content=\"https://inboxkitten.com\"/>\n\t\t<meta property=\"og:type\" content=\"website\"/>\n\t\t<meta property=\"og:description\" content=\"An Open-source Disposable Email (powered by serverless kittens 🐱)\"/>\n\t\t<meta property=\"og:image\" content=\"https://inboxkitten.com/static/inbox-kitten-opengraph.jpg\"/>\n\n\t\t<meta property=\"twitter:card\" content=\"summary\"/>\n\t\t<meta property=\"twitter:title\" content=\"InboxKitten\"/>\n\t\t<meta property=\"twitter:url\" content=\"https://inboxkitten.com\"/>\n\t\t<meta property=\"twitter:site:id\" content=\"@inboxkitten\"/>\n\t\t<meta property=\"twitter:description\" content=\"An Open-source Disposable Email (powered by serverless kittens 🐱)\"/>\n\t\t<meta property=\"twitter:image\" content=\"https://inboxkitten.com/static/inbox-kitten-opengraph.jpg\"/>\n\t\t<title>inboxkitten</title>\n\t\t<!-- favicon -->\n\t\t<link rel=\"shortcut icon\" href=\"/static/favicon.ico\" type=\"image/x-icon\" />\n\t\t<!--<script async defer src=\"https://buttons.github.io/buttons.js\"></script>-->\n\t\t<!--<script async src=\"https://platform.twitter.com/widgets.js\" charset=\"utf-8\"></script>-->\n\t</head>\n\t<body>\n\t\t<div id=\"app\"></div>\n\t\t<script type=\"module\" src=\"/src/main.js\"></script>\n\t\t<!-- built files will be auto injected -->\n\t</body>\n</html>\n"
  },
  {
    "path": "ui/package.json",
    "content": "{\n  \"name\": \"inboxkitten-ui\",\n  \"version\": \"1.1.0\",\n  \"type\": \"module\",\n  \"description\": \"UI for Inboxkitten - A personal disposable email\",\n  \"keywords\": [\n    \"inboxkitten\"\n  ],\n  \"author\": \"uilicious\",\n  \"license\": \"MIT\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"dependencies\": {\n    \"axios\": \"^1.5.0\",\n    \"clipboard\": \"^2.0.11\",\n    \"jquery\": \"^3.7.1\",\n    \"moment\": \"^2.29.4\",\n    \"normalize.css\": \"^8.0.1\",\n    \"primer-tooltips\": \"^2.0.0\",\n    \"random-words\": \"^2.0.0\",\n    \"sass\": \"^1.68.0\",\n    \"vue\": \"^2.7.14\",\n    \"vue-router\": \"^3.6.5\",\n    \"vue-spinner\": \"^1.0.4\",\n    \"vuescroll\": \"^4.18.0\",\n    \"vuex\": \"^3.6.2\"\n  },\n  \"devDependencies\": {\n    \"@vitejs/plugin-vue2\": \"^2.2.0\",\n    \"vite\": \"^4.4.5\"\n  }\n}\n"
  },
  {
    "path": "ui/src/App.vue",
    "content": "<template>\n\t<div id=\"app\">\n\t\t<!-- Github corners -->\n\t\t<a href=\"https://github.com/uilicious/inboxkitten\" class=\"github-corner\" aria-label=\"View source on Github\" target=\"_blank\" style=\"z-index:99;\"><svg width=\"80\" height=\"80\" viewBox=\"0 0 250 250\" style=\"fill:#151513; color:#fff; position: absolute; border: 0; right: 0;\" aria-hidden=\"true\"><path d=\"M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z\"></path><path d=\"M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2\" fill=\"currentColor\" style=\"transform-origin: 130px 106px;\" class=\"octo-arm\"></path><path d=\"M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z\" fill=\"currentColor\" class=\"octo-body\"></path></svg></a>\n\t\t<!-- Product hunt banner -->\n\t\t<!-- <div>\n\t\t\t<a href=\"https://www.producthunt.com/posts/inboxkitten\" target=\"_blank\" class=\"product-hunt\">\n\t\t\t\t<img src=\"@/assets/product-hunt-240.png\" class=\"ph-icon\"/>\n\t\t\t\t<p>Support our product hunt launch here</p>\n\t\t\t</a>\n\t\t</div> -->\n\t\t<!-- <div style=\"position:fixed; top:3vh; right:2vw; z-index:10;\" v-if=\"isShare\"> -->\n\t\t\t<!--<a href=\"https://twitter.com/intent/tweet?ref_src=twsrc%5Etfw\" class=\"twitter-hashtag-button\" data-size=\"large\" :data-text=\"tweetMsg\" :data-url=\"tweetDataUrl\" data-show-count=\"false\">Tweet</a>-->\n\t\t\t<!--<a class=\"github-button\" :href=\"githubLink\" data-size=\"large\" :aria-label=\"githubAriaLabel\">Fork</a>-->\n\t\t<!-- </div> -->\n\t\t<router-view class=\"app-router-view\"/>\n\t</div>\n</template>\n\n<script>\nimport shareConfig from '@/../config/shareConfig.js'\n\nexport default {\n\tname: 'App',\n\tdata: () => {\n\t\treturn {\n\t\t\tgithubLink: shareConfig.githubForkLink,\n\t\t\tgithubAriaLabel: shareConfig.githubAriaLabel,\n\t\t\ttweetDataUrl: shareConfig.tweetDataUrl,\n\t\t\ttweetMsg: shareConfig.tweetMessage,\n\t\t\tisShare: shareConfig.enabled\n\t\t}\n\t}\n}\n</script>\n\n<style lang=\"scss\" rel=\"stylesheet/scss\">\n\n\t@import url(\"https://unpkg.com/purecss@1.0.0/build/pure-min.css\");\n\t@import \"scss/_producthunt.scss\";\n\t@import \"scss/_common.scss\";\n\n\t#app {\n\t\tfont-family: 'Avenir', Helvetica, Arial, sans-serif;\n\t\t-webkit-font-smoothing: antialiased;\n\t\t-moz-osx-font-smoothing: grayscale;\n\t\ttext-align: center;\n\t\tcolor: #2c3e50;\n\t\twidth:100vw;\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t}\n\n\t.app-router-view {\n\t\tflex:1;\n\t}\n\n\t// Github corner styling\n\t.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}\n\n\t@media only screen and (max-width:800px) {\n\t\t.github-corner {\n\t\t\tvisibility: hidden;\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "ui/src/components/CarbonAds.vue",
    "content": "<template>\n\t<div id=\"_carbon_ads_div\"></div>\n</template>\n\n<script>\n\timport config from '@/../config/carbonads.js'\n\texport default {\n\t\tname: 'CarbonAds',\n\t\tdata: () => {\n\t\t\treturn {\n\t\t\t\tplacement: ''\n\t\t\t}\n\t\t},\n\t\tmounted () {\n\t\t\t// // Skip if disabled\n\t\t\t// if (!config.enable) {\n\t\t\t// \treturn\n\t\t\t// }\n\n\t\t\t// // Does a placement check if configured\n\t\t\t// if (config.placementList) {\n\t\t\t// \tlet placementList = config.placementList\n\t\t\t// \tif (placementList.indexOf(this.placement) < 0) {\n\t\t\t// \t\t// No placement found, rejects\n\t\t\t// \t\treturn\n\t\t\t// \t}\n\t\t\t// }\n\n\t\t\t// Lets do the script injection\n\t\t\tlet scriptTag = document.createElement('script')\n\t\t\tscriptTag.setAttribute('src', config.carbon_ad_src)\n\t\t\tscriptTag.setAttribute('type', 'text/javascript')\n\t\t\tscriptTag.setAttribute('async', 'async')\n\t\t\tscriptTag.setAttribute('id', '_carbonads_js')\n\n\t\t\tdocument.getElementById('_carbon_ads_div').appendChild(scriptTag)\n\t\t}\n\t}\n</script>\n<style lang=\"scss\" rel=\"stylesheet/scss\">\n#carbonads {\n\tdisplay: block;\n\toverflow: hidden;\n\tpadding: 10px;\n\tbox-shadow: 0 1px 3px hsla(0, 0%, 0%, .05);\n\tborder-radius: 4px;\n\tfont-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', Helvetica, Arial, sans-serif;\n\tline-height: 1.5;\n\tmax-width: 300px;\n\tfont-size: 12px;\n\tbackground-color: #fff;\n\tfloat:left;\n\tz-index: 4;\n\tposition: absolute;\n}\n\n#carbonads a {\n\ttext-decoration: none;\n}\n\n#carbonads span {\n\tposition: relative;\n\tdisplay: block;\n\toverflow: hidden;\n}\n\n.carbon-img {\n\tfloat: left;\n\tmargin-right: 1em;\n}\n\n.carbon-img img {\n\tdisplay: block;\n}\n\n.carbon-text {\n\tdisplay: block;\n\tfloat: left;\n\tmax-width: calc(100% - 130px - 1em);\n\ttext-align: left;\n\tcolor: #637381;\n}\n\n.carbon-poweredby {\n\tposition: absolute;\n\tleft: 142px;\n\tbottom: 0;\n\tdisplay: block;\n\tfont-size: 8px;\n\tcolor: #c5cdd0;\n\tfont-weight: 500;\n\ttext-transform: uppercase;\n\tline-height: 1;\n\tletter-spacing: 1px;\n}\n\n@media only screen and (max-width:1300px) and (min-width: 760px) {\n\t#carbonads {\n\tdisplay: block;\n\toverflow: hidden;\n\tfont-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen-Sans, Ubuntu, Cantarell, \"Helvetica Neue\", sans-serif;\n\tpadding: 1em;\n\tbackground: #fff;\n\ttext-align: center;\n\tline-height: 1.5;\n\tfont-size: 14px;\n\tmax-width: 130px;\n\t}\n\n\t#carbonads a {\n\tcolor: inherit;\n\ttext-decoration: none;\n\t}\n\n\t#carbonads a:hover {\n\tcolor: inherit;\n\t}\n\n\t#carbonads span {\n\tdisplay: block;\n\toverflow: hidden;\n\t}\n\n\t.carbon-img {\n\tdisplay: block;\n\tmargin: 0 auto 8px;\n\tline-height: 1;\n\t}\n\n\t.carbon-text {\n\tdisplay: block;\n\tmargin-bottom: 8px;\n\ttext-align: left;\n\tcolor: #637381;\n\tmax-width:130px;\n\t}\n\n\t.carbon-poweredby {\n\ttext-transform: uppercase;\n\tdisplay: block;\n\tfont-size: 10px;\n\tletter-spacing: 1px;\n\tline-height: 1;\n\t}\n}\n\n@media only screen and (max-width:800px){\n\t#carbonads {\n\t\tposition:fixed;\n\t\twidth:100vw;\n\t\tmax-width: 100vw;\n\t\tz-index: 99;\n\t}\n}\n\n</style>\n"
  },
  {
    "path": "ui/src/components/NavBar.vue",
    "content": "<template>\n\t<nav class=\"nav\">\n\t\t<div class=\"back-button\" @click=\"backAPage\"><i class=\"fa fa-arrow-left fa-3x\"/></div>\n\t\t<div class=\"logo-box\">\n\t\t\t<img class=\"logo\" src=\"@/assets/logo_no_text.svg\" @click=\"goMainPage\"/>\n\t\t</div>\n\n\t\t\t<form v-on:submit.prevent=\"\" class=\"form-box\">\n\t\t\t\t<input class=\"input-email\" name=\"email\" aria-label=\"email\" type=\"text\" v-model=\"email\" id=\"email-input\"/>\n\t\t\t\t<div class=\"domain-text\" id=\"div-domain\" data-clipboard-target=\"#email-input\">@{{domain}}</div>\n\t\t\t\t<input type=\"submit\" class=\"submit\" value=\"Go!\" @click=\"changeInbox\"/>\n\t\t\t\t<button class=\"refresh\" @click=\"emitRefresh\">Refresh</button>\n\t\t\t</form>\n\t</nav>\n</template>\n\n<script>\n\timport config from '@/../config/apiconfig.js'\n\timport 'normalize.css'\n\timport $ from 'jquery'\n\timport ClipboardJS from 'clipboard'\n\n\texport default {\n\t\tname: 'NavBar',\n\t\tdata: () => {\n\t\t\treturn {\n\t\t\t\temail: ''\n\t\t\t}\n\t\t},\n\t\tcomputed: {\n\t\t\tdomain () {\n\t\t\t\treturn config.domain\n\t\t\t}\n\t\t},\n\t\tmounted () {\n\t\t\tthis.email = this.$route.params.email\n\t\t\tif (this.email === '') {\n\t\t\t\tthis.goMainPage()\n\t\t\t}\n\n\t\t\tthis.$clipboard = []\n\n\t\t\tlet self = this\n\n\t\t\tthis.$clipboard[0] = new ClipboardJS('#div-domain', {\n\t\t\t\ttext: function (trigger) {\n\t\t\t\t\tif (self.email.includes('@' + config.domain)) {\n\t\t\t\t\t\treturn self.email\n\t\t\t\t\t}\n\t\t\t\t\treturn self.email + '@' + config.domain\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tthis.$clipboard[0].on('success', function (e) {\n\t\t\t\t$('#email-input').select()\n\t\t\t\t$('#div-domain').addClass('tooltipped tooltipped-s')\n\t\t\t\t$('#div-domain').attr('aria-label', 'Copied!')\n\t\t\t\t$('#div-domain').on('mouseleave', function () {\n\t\t\t\t\t$('#div-domain').removeClass('tooltipped tooltipped-s')\n\t\t\t\t\t$('#div-domain').removeAttr('aria-label')\n\t\t\t\t})\n\t\t\t})\n\t\t},\n\t\tbeforeDestroy () {\n\t\t\tif (this.$clipboard !== null) {\n\t\t\t\tthis.$clipboard.forEach((cb) => {\n\t\t\t\t\tcb.destroy()\n\t\t\t\t})\n\t\t\t}\n\t\t},\n\t\tmethods: {\n\t\t\tgoMainPage () {\n\t\t\t\tthis.$router.push({\n\t\t\t\t\tname: 'Kitten Land'\n\t\t\t\t})\n\t\t\t},\n\t\t\temitRefresh () {\n\t\t\t\tthis.$eventHub.$emit('refresh', '')\n\t\t\t},\n\t\t\tchangeInbox () {\n\t\t\t\tthis.$router.push({\n\t\t\t\t\tname: 'List',\n\t\t\t\t\tparams: {\n\t\t\t\t\t\temail: this.email\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\tthis.$eventHub.$emit('refreshInbox', {email: this.email})\n\t\t\t},\n\t\t\tbackAPage () {\n\t\t\t\tif (this.$route.name === 'List') {\n\t\t\t\t\tthis.$router.push({\n\t\t\t\t\t\tname: 'Kitten Land'\n\t\t\t\t\t})\n\t\t\t\t} else {\n\t\t\t\t\tthis.$router.push({\n\t\t\t\t\t\tname: 'List'\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\" rel=\"stylesheet/scss\">\n\t@import \"primer-tooltips/index.scss\";\n\t@import \"@/scss/_color.scss\";\n\t.nav {\n\t\tbackground: #36D1DC;  /* fallback for old browsers */\n\t\tbackground: -webkit-linear-gradient(to right, #5B86E5, #36D1DC);  /* Chrome 10-25, Safari 5.1-6 */\n\t\tbackground: linear-gradient(to right, #5B86E5, #36D1DC); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */\n\t\twidth: 100vw;\n\t\theight: 10rem;\n\t\ttext-align: left;\n\t\tpadding-top:2rem;\n\t\tpadding-bottom:2rem;\n\t\ttop: 0;\n\n\t\t.logo-box{\n\t\t\twidth:100%;\n\t\t\theight:2rem;\n\t\t\ttext-align: center;\n\t\t\tvertical-align: center;\n\t\t\tdisplay: block;\n\t\t\toverflow: hidden;\n\n\t\t\t.logo {\n\t\t\t\twidth:6rem;\n\t\t\t}\n\t\t\t.logo:hover {\n\t\t\t\tcursor: pointer;\n\t\t\t}\n\t\t}\n\n\t\t.back-button {\n\t\t\tposition:absolute;\n\t\t\tpadding-left:30rem;\n\t\t\tpadding-top:3rem;\n\t\t\tpadding-right:3rem;\n\t\t\tpadding-bottom:3rem;\n\t\t\tcursor: pointer;\n\t\t\tmargin:0;\n\t\t}\n\t}\n\n\t//\n\t// Email form box\n\t//\n\n\t.form-box {\n\t\tdisplay:flex;\n\t\tflex-direction: row;\n\t\tjustify-content: center;\n\t\talign-items: stretch;\n\n\t\t.domain-text {\n\t\t\tdisplay: none;\n\t\t\twidth:14rem;\n\t\t}\n\n\t\t.input-email {\n\t\t\ttext-align: center;\n\t\t\tborder: 3px solid black;\n\t\t}\n\t\t.submit {\n\t\t\tbackground: $cta-base;\n\t\t\tcolor: $cta-base-text;\n\t\t\tborder: 3px solid black;\n\t\t\tborder-left-width: 0;\n\t\t}\n\n\t\t.submit:hover {\n\t\t\tbackground-color: $cta-hover;\n\t\t\tcolor: $cta-hover-text;\n\t\t}\n\t\t.refresh {\n\t\t\tbackground: #005CFF;\n\t\t\tcolor: $cta-base-text;\n\t\t\tborder: 3px solid black;\n\t\t\tborder-left-width: 0;\n\t\t}\n\n\t\t.refresh:hover {\n\t\t\tbackground-color: $cta-hover;\n\t\t\tcolor: $cta-hover-text;\n\t\t}\n\t}\n\n\t@media (min-width: 760px){ // IPad and above\n\t\t.nav{\n\t\t\t.logo-box{\n\t\t\t\theight:6rem;\n\t\t\t\t.logo{\n\t\t\t\t\twidth:16rem;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t.back-button {\n\t\t\t\tpadding-left:3rem;\n\t\t\t}\n\t\t}\n\n\t\t.form-box{\n\t\t\t.domain-text{\n\t\t\t\tdisplay: inline-block;\n\t\t\t\tbackground:white;\n\t\t\t\ttext-align: center;\n\t\t\t\tmargin: 0;\n\t\t\t\tpadding:0.2rem; // need help on this\n\t\t\t\tvertical-align: middle;\n\t\t\t\tborder: 3px solid black;\n\t\t\t\tborder-left-width: 0;\n\t\t\t\tbackground-color: $domain-base;\n\t\t\t\tcursor: pointer;\n\t\t\t\twidth:10rem;\n\t\t\t}\n\t\t}\n\t}\n\n\t@media (max-width: 800px) { // IPad portrait\n\t\t.nav{\n\t\t\t.back-button{\n\t\t\t\tpadding:3rem;\n\t\t\t\tpadding-left:3rem;\n\t\t\t}\n\t\t}\n\t}\n\n\t@media (max-width:760px){ // Smartphones\n\t\t.nav {\n\t\t\theight:4rem;\n\n\t\t\t.back-button {\n\t\t\t\tpadding: 1rem;\n\t\t\t\tpadding-left: 2rem;\n\n\t\t\t\tfont-size: 10px;\n\t\t\t}\n\t\t}\n\n\t\t.form-box{\n\t\t\t.input-email{\n\t\t\t\twidth: 9rem;\n\t\t\t}\n\t\t\t.refresh {\n\t\t\t\tdisplay:none;\n\t\t\t}\n\t\t}\n\t}\n\n\t@media (max-width: 320px){ // IPhone 5/SE\n\n\t\t.form-box{\n\t\t\t.input-email{\n\t\t\t\twidth: 7rem;\n\t\t\t}\n\t\t}\n\t}\n\n</style>\n"
  },
  {
    "path": "ui/src/components/mail/inbox.vue",
    "content": "<template>\n  <div class=\"wrapper\">\n    <nav-bar class=\"nav-bar\"></nav-bar>\n    <router-view class=\"inbox\"></router-view>\n  </div>\n</template>\n\n<script>\n  import NavBar from '../NavBar.vue'\n\n  export default {\n    name: 'inbox',\n    components: {\n      NavBar: NavBar\n    }\n  }\n</script>\n<style>\n  .wrapper {\n    display: flex;\n    flex-direction: column;\n    width:100%;\n    height:100%;\n  }\n  .nav-bar{\n  }\n  .inbox{\n    flex: 1;\n    z-index:9;\n  }\n\n</style>\n"
  },
  {
    "path": "ui/src/components/mail/message_detail.vue",
    "content": "<template>\n\t<div class=\"message-details\">\n\t\t<div class=\"subject\">{{emailContent.subject}}</div>\n\t\t<div class=\"meta-info\">\n\t\t\t<div class=\"left\">\n\t\t\t\t<div class=\"sender\"><b>{{emailContent.name}}</b>{{emailContent.emailAddress}}</div>\n\t\t\t\t<div class=\"to\">to: {{emailContent.recipients}}</div>\n\t\t\t</div>\n\t\t\t<div class=\"date\">{{emailContent.Date}}</div>\n\t\t</div>\n\t\t<iframe id=\"message-content\" scrolling=\"yes\" :src=\"src\"></iframe>\n\t</div>\n</template>\n\n<script>\n\timport 'normalize.css'\n\timport config from '@/../config/apiconfig.js'\n\timport axios from 'axios'\n\n\texport default {\n\t\tname: 'MessageDetail',\n\t\tdata: () => {\n\t\t\treturn {\n\t\t\t\temailContent: {},\n\t\t\t\tsrc: ''\n\t\t\t}\n\t\t},\n\t\tmounted () {\n\t\t\tif (this.$route.params.key === undefined) {\n\t\t\t\tthis.$router.push({\n\t\t\t\t\tname: 'Kitten Land'\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tthis.getMessage()\n\t\t\tthis.$eventHub.$on('refresh', this.getMessage)\n\t\t},\n\t\tbeforeDestroy () {\n\t\t\tthis.$eventHub.$off('refresh', this.getMessage)\n\t\t},\n\t\tmethods: {\n\t\t\tgetMessage () {\n\t\t\t\tlet region = this.$route.params.region\n\t\t\t\tlet key = this.$route.params.key\n\t\t\t\tthis.src = `${config.apiUrl}/getHtml?region=${region}&key=${key}`\n\t\t\t\taxios.get(`${config.apiUrl}/getKey?region=${region}&key=${key}`)\n\t\t\t\t\t.then(res => {\n\t\t\t\t\t\tthis.emailContent = res.data\n\t\t\t\t\t}).catch((e) => {\n\t\t\t\t\t\tthis.emailContent.name = 'Kitten Squads'\n\t\t\t\t\t\tthis.emailContent.recipients = 'Master'\n\t\t\t\t\t\tlet iframe = document.getElementById('message-content')\n\t\t\t\t\t\tiframe.src = 'data:text/html;charset=utf-8,' + encodeURI('The kittens found no messages :(')\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\" rel=\"stylesheet/scss\">\n\n\t.message-details {\n\t\theight: auto;\n\t\toverflow:auto;\n\t\tdisplay:flex;\n\t\tflex-direction: column;\n\n\t\t.subject {\n\t\t\tfont-weight: bold;\n\t\t\tfont-size:1.5rem;\n\t\t\tpadding:1rem;\n\t\t\tpadding-bottom:0;\n\t\t\ttext-align: left;\n\t\t}\n\n\t\t.meta-info{\n\t\t\tdisplay:flex;\n\t\t\tflex-direction: row;\n\t\t\tjustify-content: space-between;\n\t\t\ttext-align: left;\n\t\t\tpadding: 1rem;\n\t\t\tborder-bottom: 1px solid #20a0ff;\n\t\t}\n\t}\n\n\t@media (max-width: 760px) {\n\t\t.message-details{\n\t\t\ttop: 8rem;\n\t\t\t.subject{\n\t\t\t\tfont-size:1rem;\n\t\t\t}\n\t\t\t.meta-info{\n\t\t\t\tfont-size:0.6rem;\n\t\t\t}\n\t\t}\n\t\t#message-content{\n\t\t\tflex:1;\n\t\t}\n\t}\n\n\t#message-content{\n\t\twidth: 100%;\n\t\theight: 100%;\n\t\toverflow: auto;\n\t\tborder: none;\n\t}\n</style>\n"
  },
  {
    "path": "ui/src/components/mail/message_list.vue",
    "content": "<template>\n    <vue-scroll :ops=\"vueScrollBarOps\">\n      <div class=\"table-box advisory\" style=\"\n          background: #fbc02d4f;\n          padding: 0.5em;\n      \">\n        <i class=\"fas fa-exclamation-triangle\" style=\"margin-right:0.5em\"></i><a href=\"https://uilicious.com/blog/psa-inboxkitten-will-be-blocking-no-reply-google/\" target=\"_blank\"><b>PSA</b>: Please use inboxkitten, for only testing, or non critical emails. See here for more details.</a>\n      </div>\n      <pulse-loader v-if=\"refreshing\" class=\"loading\"></pulse-loader>\n      <div class=\"email-list table-box\" v-if=\"listOfMessages.length > 0\">\n        <div class=\"email-list-item\" :class=\"rowCls(index)\" v-for=\"(msg, index) in listOfMessages\" :key=\"msg.url\"\n             @click=\"getMessage(msg)\">\n\n          <div class=\"row-info\">\n            <div class=\"row-name\">{{extractEmail(msg.message.headers.from)}}</div>\n            <div class=\"row-subject\">{{(msg.message.headers.subject)}}</div>\n          </div>\n\n          <div class=\"row-time\">{{calculateTime(msg)}}</div>\n        </div>\n      </div>\n      <div class=\"no-mails\" v-if=\"listOfMessages.length == 0\">\n        <p>\n          There for no messages for this kitten :(<br/><br/>\n          Press on the 'Refresh' button if you want to overwork the kittens...\n        </p>\n        <button class=\"refresh-button\" @click=\"refreshList\" v-if=\"!refreshing\">Refresh</button>\n      </div>\n    </vue-scroll>\n</template>\n\n<script>\nimport NavBar from '../NavBar.vue'\nimport 'normalize.css'\nimport config from '@/../config/apiconfig.js'\nimport axios from 'axios'\nimport moment from 'moment'\nimport PulseLoader from 'vue-spinner/src/PulseLoader.vue'\n\nexport default {\n  name: 'MessageList',\n  data: () => {\n    return {\n      listOfMessages: [],\n      vueScrollBarOps: {\n        bar: {\n          background: 'darkgrey'\n        }\n      },\n      refreshing: false\n    }\n  },\n  mounted () {\n    let currentEmail = this.$route.params.email\n    if (currentEmail === '') {\n      this.$router.push({name: 'Kitten Land'})\n    }\n\n    this.getMessageList()\n\n    this.retrieveMessage = window.setInterval(this.getMessageList, 10000)\n\n    this.$eventHub.$on('refreshInbox', this.getMessageList)\n    this.$eventHub.$on('refresh', this.getMessageList)\n  },\n  beforeDestroy () {\n    window.clearInterval(this.retrieveMessage)\n\n    this.$eventHub.$off('refreshInbox', this.getMessageList)\n    this.$eventHub.$off('refresh', this.getMessageList)\n  },\n  methods: {\n    refreshList () {\n      this.refreshing = true\n      this.getMessageList()\n    },\n    getMessageList () {\n      this.refreshing = true\n      let email = this.$route.params.email\n      axios.get(config.apiUrl + '/list?recipient=' + email)\n        .then(res => {\n          this.listOfMessages = res.data\n          this.refreshing = false\n        }).catch((e) => {\n        this.refreshing = false\n      })\n    },\n    changeInbox () {\n      this.$router.push({\n        params: {\n          email: this.email\n        }\n      })\n      this.emailContent = {}\n      this.$eventHub.$emit('iframe_content', '')\n      this.refreshList()\n    },\n\n    getMessage (msg) {\n      \n      this.$router.push({\n        name: 'Message',\n        params: {\n          region: msg.storage.region,\n          key: msg.storage.key\n        }\n      })\n\n      this.$eventHub.$emit('getMessage', '')\n    },\n\n    //\n    // Utility Functions\n    //\n\n    calculateTime (msg) {\n      let now = moment()\n      let theDate = moment(msg.timestamp * 1000)\n      let diff = now.diff(theDate, 'day')\n      if (diff === 0) {\n        return theDate.format('hh:mm a')\n      } else if (diff > 0) {\n        return theDate.format('DD MMM')\n      }\n    },\n\n    extractEmail (sender) {\n      let emails = sender.match(/[^@<\\s]+@[^@\\s>]+/g)\n\n      // If there are any email in the matching, take the first and return\n      if (emails) {\n        return emails[0]\n      }\n\n      // Sender does not contain any formatted name, do not format them\n      return sender\n    },\n\n    rowCls (index) {\n      if (index % 2 === 0) {\n        return 'table-row even'\n      }\n      return 'table-row odd'\n    }\n  },\n  components: {\n    NavBar: NavBar,\n    PulseLoader: PulseLoader\n  }\n}\n</script>\n\n<style lang=\"scss\" rel=\"stylesheet/scss\">\n  @import '@/scss/_color.scss';\n\n  .table-box {\n    width: 100%;\n    height: auto;\n    .table-row {\n      display: flex;\n      flex-direction: row;\n      /*justify-content: space-evenly;*/\n      justify-content: flex-start;\n      border: 3px solid white;\n      border-bottom: 3px solid #20a0ff;\n\n      .row-info {\n        width: 85%;\n\n        .row-name {\n          font-weight: bold;\n          text-align: left;\n        }\n      }\n    }\n  }\n\n  .table-row:hover {\n    border: 3px solid black;\n    background-color: $cta-hover;\n  }\n\n  .no-mails {\n    text-align: center;\n    vertical-align: center;\n    overflow: auto;\n    margin-top: 2rem;\n    z-index: 10;\n  }\n\n  .refresh-button {\n    border: 3px solid black;\n    background-color: $cta-base;\n    color: $cta-base-text;\n  }\n\n  .refresh-button:hover {\n    background-color: $cta-hover;\n    color: $cta-hover-text;\n  }\n\n  .loading {\n    z-index:9;\n    position:absolute;\n    padding-top: 5rem;\n    left:50%;\n  }\n\n  @media (min-width: 760px) {\n    .table-row {\n      padding: 1rem;\n      .row-info {\n        display: flex;\n        flex-direction: row;\n        justify-content: flex-start;\n        .row-name {\n          margin-right: 1em;\n          width: 35%;\n          min-width: 35%;\n          max-width: 35%;\n        }\n      }\n      border-bottom: 1px solid #20a0ff;\n    }\n  }\n\n  @media (max-width: 760px) {\n    .table-row {\n      display: flex;\n      flex-direction: row;\n      width: 98vw;\n      margin: auto;\n      background-color: white;\n      border-bottom: 1px solid #20a0ff;\n\n      .row-info {\n        text-align: left;\n        padding-left: 0.5rem;\n        .row-name {\n          font-size: 1rem;\n          font-weight: bold;\n          padding: 0.5rem;\n          /*background: #06FFAB;*/\n          width: 100%;\n          text-overflow: ellipsis;\n          white-space: nowrap;\n          overflow: hidden;\n        }\n        .row-subject {\n          /*background-color: coral;*/\n          width: 100%;\n          padding-left: 0.5rem;\n          padding-bottom: 0.5rem;\n          font-size: 0.75rem;\n          font-weight: bold;\n          text-overflow: ellipsis;\n          white-space: nowrap;\n          overflow: hidden;\n        }\n      }\n\n      .row-time {\n        width: 20%;\n        font-size: 12px;\n        text-align: center;\n        vertical-align: middle;\n        padding: 0.5rem;\n        padding-left: 0;\n      }\n    }\n\n    .loading{\n      left:40%;\n    }\n  }\n</style>\n"
  },
  {
    "path": "ui/src/kittenrouter.vue",
    "content": "<template>\n\t<div>\n\t\t<!-- <carbon-ads placement=\"InboxKittenLanding\"></carbon-ads> -->\n\t\t<div class=\"kittenrouter-navigation\">\n\t\t\t<img class=\"kittenrouter-nav-main-logo\" src=\"@/assets/inbox_kitten.png\" @click=\"goToMainPage\"/>\n\t\t</div>\n\t\t<div class=\"header-gradient-background\">\n\t\t\t<div class=\"header\">\n\t\t\t\t<div class=\"logo-box\">\n\t\t\t\t\t<img class=\"logo\" src=\"@/assets/kitten_router.png\"/>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t\t<div class=\"info-guide\">\n\t\t\t<div class=\"features\" style=\"max-width:1100px\">\n\t\t\t\t<div class=\"feature-card\">\n                    <img class=\"logo\" src=\"@/assets/elasticsearch.png\" style=\"width:3.7rem\"/>\n\t\t\t\t\t<h3>ElasticSearch</h3>\n\t\t\t\t\t<p>\n\t\t\t\t\t\tLog down your network traffic to your own elasticsearch server for analytics!\n\t\t\t\t\t</p>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"feature-card\">\n                    <i class=\"fas fa-server fa-4x\"></i>\n\t\t\t\t\t<h3>Find next available server</h3>\n\t\t\t\t\t<p>With the list of configured servers, KittenRouter can help you redirect the request to the next available server.</p>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"feature-card\">\n                    <img class=\"logo\" src=\"@/assets/cloudflare.png\" style=\"width:10rem\"/>\n\t\t\t\t\t<h3>Deploy it on Cloudflare</h3>\n\t\t\t\t\t<p>Kitten Router is built for Cloudflare Workers script!</p>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t\t<div class=\"info-guide\">\n\t\t\t<div class=\"deploy-segmet\">\n\t\t\t\t<h2>Configuring Kitten Router</h2>\n\t\t\t\t<p>Before you use Kitten Router, make sure that your configuration settings are correct. Below is a sample configuration.</p>\n\t\t\t</div>\n\t\t</div>\n\t\t<div class=\"code config-code\">\n<pre style=\"color:#000020;background:#f6f8ff;\"><span style=\"color:#200080; font-weight:bold; \">let</span> config <span style=\"color:#308080; \">=</span> <span style=\"color:#406080; \">{</span>\n\n\t<span style=\"color:#595979; \">// logging endpoint to use</span>\n\t<span style=\"color:#200080; font-weight:bold; \">log</span> <span style=\"color:#406080; \">:</span> <span style=\"color:#308080; \">[</span>\n\t\t<span style=\"color:#406080; \">{</span>\n\t\t\ttype <span style=\"color:#406080; \">:</span> <span style=\"color:#800000; \">\"</span><span style=\"color:#1060b6; \">elasticsearch</span><span style=\"color:#800000; \">\"</span><span style=\"color:#308080; \">,</span>\n\t\t\turl <span style=\"color:#406080; \">:</span> <span style=\"color:#800000; \">\"</span><span style=\"color:#1060b6; \">https://&lt;Your elasticsearch server url&gt;</span><span style=\"color:#800000; \">\"</span><span style=\"color:#308080; \">,</span>\n\n\t\t\t<span style=\"color:#595979; \">//</span>\n\t\t\t<span style=\"color:#595979; \">// Authorization header (if needed)</span>\n\t\t\t<span style=\"color:#595979; \">//</span>\n\t\t\tbasicAuthToken <span style=\"color:#406080; \">:</span> <span style=\"color:#800000; \">\"</span><span style=\"color:#1060b6; \">username:password</span><span style=\"color:#800000; \">\"</span><span style=\"color:#308080; \">,</span>\n\n\t\t\t<span style=\"color:#595979; \">//</span>\n\t\t\t<span style=\"color:#595979; \">// Index prefix for storing data, this is before the \"YYYY.MM\" is attached</span>\n\t\t\t<span style=\"color:#595979; \">//</span>\n\t\t\tindexPrefix <span style=\"color:#406080; \">:</span> <span style=\"color:#800000; \">\"</span><span style=\"color:#1060b6; \">test-data-</span><span style=\"color:#800000; \">\"</span><span style=\"color:#308080; \">,</span>\n\n\t\t\t<span style=\"color:#595979; \">// Enable logging of the full ipv4/6</span>\n\t\t\t<span style=\"color:#595979; \">//</span>\n\t\t\t<span style=\"color:#595979; \">// Else it mask (by default) the last digit of IPv4 address</span>\n\t\t\t<span style=\"color:#595979; \">// or the \"network\" routing for IPv6</span>\n\t\t\t<span style=\"color:#595979; \">// see : </span><span style=\"color:#5555dd; \">https://www.haproxy.com/blog/ip-masking-in-haproxy/</span>\n\t\t\tlogTrueIP <span style=\"color:#406080; \">:</span> <span style=\"color:#0f4d75; \">false</span>\n\t\t<span style=\"color:#406080; \">}</span>\n\t<span style=\"color:#308080; \">]</span><span style=\"color:#308080; \">,</span>\n\n\t<span style=\"color:#595979; \">// Routing rules to evaluate, starting from 0 index</span>\n\t<span style=\"color:#595979; \">// these routes will always be processed in sequence</span>\n\troute <span style=\"color:#406080; \">:</span> <span style=\"color:#308080; \">[</span>\n\t\t<span style=\"color:#595979; \">// Lets load all requests to commonshost first</span>\n\t\t<span style=\"color:#800000; \">\"</span><span style=\"color:#1060b6; \">commonshost.inboxkitten.com</span><span style=\"color:#800000; \">\"</span>\n\n\t\t<span style=\"color:#595979; \">// If it fails, we fallback to firebase</span>\n\t\t<span style=\"color:#595979; \">//\"firebase.inboxkitten.com\"</span>\n\t<span style=\"color:#308080; \">]</span><span style=\"color:#308080; \">,</span>\n\n\t<span style=\"color:#595979; \">// Set to true to disable fallback to origin host </span>\n\t<span style=\"color:#595979; \">// when all routes fails</span>\n\tdisableOriginFallback <span style=\"color:#406080; \">:</span> <span style=\"color:#0f4d75; \">false</span><span style=\"color:#308080; \">,</span>\n<span style=\"color:#406080; \">}</span>\n</pre>\n\t\t</div>\n\t\t<div class=\"info-guide\">\n\t\t\t<div class=\"features steps\">\n\t\t\t<div class=\"feature-card\">\n\t\t\t\t<h2>Option 1: Using Kitten Router manually</h2>\n\t\t\t\t<div class=\"code deploy-code\">\n\t\t\t\t\t<div style=\"text-align:justify\">\n\t\t\t\t\t\t1. Copy the configuration<br/>\n\t\t\t\t\t\t2. Copy the index.js file in Github <br/>\n\t\t\t\t\t\t&nbsp;&nbsp;&nbsp;into your Cloudflare Worker script<br/>\n\t\t\t\t\t\t3. Initialize KittenRouter variable in your script<br/>\n\t\t\t\t\t\t&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;let router = new KittenRouter(config)<br/>\n\t\t\t\t\t\t4. Use it to route all your desires\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div class=\"feature-card\">\n\t\t\t\t<h2>Option 2: Using Kitten Router via NPM</h2>\n\t\t\t\t<div class=\"code deploy-code\">\n\t\t\t\t\t<div style=\"text-align:justify\">\n\t\t\t\t\t\t1. Copy the configuration<br/>\n\t\t\t\t\t\t2. Install KittenRouter via NPM<br/>\n\t\t\t\t\t\t&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;npm install --save kittenrouter<br/>\n\t\t\t\t\t\t3. Initialize KittenRouter class in your script<br/>\n\t\t\t\t\t\t&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;const KittenRouter = require(\"kittenrouter\")<br/>\n\t\t\t\t\t\t&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;let router = new KittenRouter(config)<br/>\n\t\t\t\t\t\t4. Use it to route all your desires\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<div class=\"info-guide\">\n\t\t\t<div class=\"features\">\n                <div class=\"feature-card\"></div>\n                <div class=\"feature-card feature-card-hover\">\n                    <a href=\"https://github.com/uilicious/kittenrouter\" target=\"_blank\" style=\"text-decoration: none; color: inherit; cursor: pointer;\">\n                        <h2> Check out Kitten Router at Github to find out more! </h2>\n<pre class=\"hljs\" style=\"display: block; overflow-x: auto; padding: 0.5em; background: rgb(240, 240, 240); color: rgb(68, 68, 68);\"><span>https://github.com/uilicious/kittenrouter</span></pre>\n                    </a>\n                </div>\n                <div class=\"feature-card\"></div>\n\t\t\t</div>\n\t\t</div>\n        <div class=\"line-break\"></div>\n\t\t<div class=\"love-notes\">\n\t\t\t<p>\n\t\t\t\tmade with <span style=\"color: #e25555;\">&hearts;</span> by <a href=\"https://uilicious.com\">uilicious</a> in <a href=\"https://www.google.com.sg/search?q=singapore\">Singapore</a>\n\t\t\t</p>\n\t\t</div>\n    </div>\n</template>\n\n<script>\nimport shareConfig from '@/../config/shareConfig.js'\n\n// import CarbonAds from './components/CarbonAds.vue'\n\nexport default {\n\tname: 'App',\n\tcomponents: {\n\t\t// CarbonAds: CarbonAds\n\t},\n\tdata: () => {\n\t\treturn {\n\t\t\tmainURL: shareConfig.mainURL\n\t\t}\n    },\n    mounted () {\n        document.getElementsByClassName('github-corner')[0].href = 'https://github.com/uilicious/kittenrouter'\n\t},\n\tmethods: {\n\t\tgoToMainPage () {\n\t\t\tlocation.href = this.mainURL\n\t\t}\n\t}\n}\n</script>\n\n<style lang=\"scss\" rel=\"stylesheet/scss\">\n\t@import url(\"https://use.fontawesome.com/releases/v5.3.1/css/all.css\");\n    @import \"scss/landingpage.scss\";\n\n\t.kittenrouter-navigation {\n\t\tdisplay:flex;\n\t\tjustify-content: flex-end;\n\t\talign-items: center;\n\t\tfloat:right;\n\t\twidth: 100vw;\n\t\theight: 1rem;\n\t\ttext-align: left;\n\n\t\t.kittenrouter-nav-main-logo {\n\t\t\twidth:8rem;\n\t\t\tpadding-top:7rem;\n\t\t\tpadding-right: 4rem;\n\t\t\tz-index: 2;\n\t\t\tcursor: pointer;\n\t\t}\n\n\t\t.kittenrouter-nav-header {\n\t\t\tpadding-top:6rem;\n\t\t\tpadding-right: 3rem;\n\t\t\tcursor: pointer;\n\t\t}\n\t}\n\n\t@media only screen and (max-width:470px) {\n\t\t.kittenrouter-navigation .kittenrouter-nav-main-logo {\n\t\t\tpadding-right: 4rem;\n\t\t\twidth:5rem;\n\t\t\tz-index: 3;\n\t\t}\n\t\t.kittenrouter-nav-header {\n\t\t\tdisplay: none;\n\t\t}\n\t}\n\n\t@media only screen and (max-width: 800px) {\n\t\t.kittenrouter-navigation {\n\t\t\tpadding-top: 140px;\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "ui/src/landingpage.vue",
    "content": "<template>\n\t<div class=\"landing-page\">\n\t\t<!-- <carbon-ads placement=\"InboxKittenLanding\"></carbon-ads> -->\n\t\t<!-- <div><a href=\"/kittenrouter\" class=\"product-hunt\">Introducing Kitten Router 🐱 </a></div> -->\n\t\t<div class=\"landing-navigation\">\n\t\t\t<router-link to=\"/kittenrouter\">\n\t\t\t\t<img class=\"landing-nav-main-logo\" src=\"@/assets/kitten_router.png\" />\n\t\t\t</router-link>\n\t\t</div>\n\t\t<div class=\"header-gradient-background\">\n\t\t\t<div class=\"header\">\n\t\t\t\t<img class=\"logo\" src=\"@/assets/inbox_kitten.png\" />\n\t\t\t\t<h1>\n\t\t\t\t\tOpen-Source\n\t\t\t\t\t<a></a> Disposable Email\n\t\t\t\t</h1>\n\t\t\t\t<h2>(Served by Serverless Kittens)</h2>\n\t\t\t</div>\n\t\t\t<div class=\"email-selection\">\n\t\t\t\t<form v-on:submit.prevent=\"goToInbox\">\n\t\t\t\t\t<div class=\"input-box\">\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\tclass=\"input-email\"\n\t\t\t\t\t\t\tname=\"email\"\n\t\t\t\t\t\t\taria-label=\"email\"\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tv-model=\"randomName\"\n\t\t\t\t\t\t\tid=\"email-input\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<div class=\"input-suffix\" id=\"div-domain\" data-clipboard-target=\"#email-input\">@{{domain}}</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"submit-box\">\n\t\t\t\t\t\t<input type=\"submit\" class=\"submit\" value=\"Get Mail Nyow!\" />\n\t\t\t\t\t</div>\n\t\t\t\t</form>\n\t\t\t</div>\n\t\t</div>\n\t\t<div class=\"info-guide mx-a my-0 p-2\" style=\"max-width: 1240px; \">\n\t\t\t<div class=\"pure-g mx-a\" style=\"max-width: 1000px; background: #fbc02d4f;\">\n\t\t\t\t<div class=\"pure-u-1-1\">\n\t\t\t\t\t\t<section class=\"p-1\">\n\t\t\t\t\t\t<h3>\n\t\t\t\t\t\t\t<i class=\"fas fa-exclamation-triangle\"></i> PSA: Please use inboxkitten, for only testing, or non critical emails.\n\t\t\t\t\t\t</h3>\n\t\t\t\t\t\t<p><a href=\"https://uilicious.com/blog/psa-inboxkitten-will-be-blocking-no-reply-google/\" target=\"_blank\">We block \"no-reply@google\" to prevent abuse on our platform, see more details on our blog post (here)</a></p>\n\t\t\t\t\t</section>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div class=\"pure-g mx-a my-1\" style=\"max-width: 1000px;\">\n\t\t\t\t<div class=\"pure-u-1-2\">\n\t\t\t\t\t<section class=\"p-1\">\n\t\t\t\t\t\t<h3>\n\t\t\t\t\t\t\t<i class=\"fas fa-mail-bulk\"></i> Use any inbox to avoid spam\n\t\t\t\t\t\t</h3>\n\t\t\t\t\t\t<p>Use inboxkitten when you don't want to get spammed by revealing your real email address.</p>\n\t\t\t\t\t</section>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"pure-u-1-2\">\n\t\t\t\t\t<section class=\"p-1\">\n\t\t\t\t\t\t<h3>\n\t\t\t\t\t\t\t<i class=\"fas fa-trash-alt\"></i> Email Auto-Deletes\n\t\t\t\t\t\t</h3>\n\t\t\t\t\t\t<p>Inboxkitten.com emails are in the public domain, and auto deletes after several hours. (officially 3 days according to mailgun)</p>\n\t\t\t\t\t</section>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t<section class=\"my-1 p-1\" style=\"background-color: #eee;\">\n\t\t\t\t<div class=\"mb-1\">\n\t\t\t\t\t<h3>\n\t\t\t\t\t\t<i class=\"fas fa-clipboard-check\"></i> Ideal for UI / QA Testing\n\t\t\t\t\t</h3>\n\t\t\t\t\t<p>\n\t\t\t\t\t\tTest your web application user signup flows, and email notification.\n\t\t\t\t\t\t<br />\n\t\t\t\t\t\t<a\n\t\t\t\t\t\t\thref=\"https://test.uilicious.com/test/public/7t74nVS828weKMtzGgJppF\"\n\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t>Or even better, automate testing with uilicious!</a>\n\t\t\t\t\t</p>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"flex-row\">\n\t\t\t\t\t<div class=\"code mb-0\" style=\"flex: 1 0 auto;\">\n\t\t\t\t\t\t<pre style=\"margin: 0; line-height: 125%\"><span style=\"color: #66d9ef\">let</span> <span style=\"color: #a6e22e\">email</span> <span style=\"color: #f92672\">=</span> <span style=\"color: #e6db74\">\"death-flew-61\"</span>\n\n<span style=\"color: #d5cccc\">// Go to inbox</span>\n<span style=\"color: #a6e22e\">I</span><span style=\"color: #f8f8f2\">.</span><span style=\"color: #a6e22e\">goTo</span><span style=\"color: #f8f8f2\">(</span><span style=\"color: #e6db74\">\"https://inboxkitten.com/inbox/\"</span> <span style=\"color: #f92672\">+</span> <span style=\"color: #a6e22e\">email</span> <span style=\"color: #f92672\">+</span> <span style=\"color: #e6db74\">\"/list\"</span><span style=\"color: #f8f8f2\">)</span>\n\n<span style=\"color: #d5cccc\">// Wait for some time for the email to arrive</span>\n<span style=\"color: #a6e22e\">I</span><span style=\"color: #f8f8f2\">.</span><span style=\"color: #a6e22e\">see</span><span style=\"color: #f8f8f2\">(</span><span style=\"color: #e6db74\">\"@inboxkitten\"</span><span style=\"color: #f8f8f2\">)</span>\n<span style=\"color: #a6e22e\">I</span><span style=\"color: #f8f8f2\">.</span><span style=\"color: #a6e22e\">wait</span><span style=\"color: #f8f8f2\">(</span><span style=\"color: #ae81ff\">15</span><span style=\"color: #f8f8f2\">)</span>\n\n<span style=\"color: #d5cccc\">// Open the mail</span>\n<span style=\"color: #a6e22e\">I</span><span style=\"color: #f8f8f2\">.</span><span style=\"color: #a6e22e\">see</span><span style=\"color: #f8f8f2\">(</span><span style=\"color: #e6db74\">\"Reset your password\"</span><span style=\"color: #f8f8f2\">)</span>\n<span style=\"color: #a6e22e\">I</span><span style=\"color: #f8f8f2\">.</span><span style=\"color: #a6e22e\">click</span><span style=\"color: #f8f8f2\">(</span><span style=\"color: #e6db74\">\"Reset your password\"</span><span style=\"color: #f8f8f2\">)</span>\n\n<span style=\"color: #d5cccc\">// click on the \"reset password\" button </span>\n<span style=\"color: #a6e22e\">UI</span><span style=\"color: #f8f8f2\">.</span><span style=\"color: #a6e22e\">context</span><span style=\"color: #f8f8f2\">(</span><span style=\"color: #e6db74\">\"#message-content\"</span><span style=\"color: #f8f8f2\">,</span> <span style=\"color: #f8f8f2\">()</span><span style=\"color: #f92672\">=&gt;</span><span style=\"color: #f8f8f2\">{</span>\n    <span style=\"color: #a6e22e\">I</span><span style=\"color: #f8f8f2\">.</span><span style=\"color: #a6e22e\">see</span><span style=\"color: #f8f8f2\">(</span><span style=\"color: #e6db74\">\"Here's your magic link\"</span><span style=\"color: #f8f8f2\">)</span>\n    <span style=\"color: #a6e22e\">I</span><span style=\"color: #f8f8f2\">.</span><span style=\"color: #a6e22e\">click</span><span style=\"color: #f8f8f2\">(</span><span style=\"color: #e6db74\">\"Reset password\"</span><span style=\"color: #f8f8f2\">)</span>\n<span style=\"color: #f8f8f2\">})</span>\n\n<span style=\"color: #d5cccc\">// Check that I'm at the \"Reset password\" page</span>\n<span style=\"color: #a6e22e\">I</span><span style=\"color: #f8f8f2\">.</span><span style=\"color: #a6e22e\">amAt</span><span style=\"color: #f8f8f2\">(</span><span\n\tstyle=\"color: #e6db74\"\n>\"https://user.uilicious.com/resetPassword\"</span><span\n\tstyle=\"color: #f8f8f2\"\n>)</span>\n</pre>\n\t\t\t\t\t</div>\n\n\t\t\t\t\t<iframe\n\t\t\t\t\t\tsrc=\"https://snippet.uilicious.com/embed/test/public/FPNPw6BRyc4agxP5SV1nry?step=2&autoplay=1\"\n\t\t\t\t\t\tframeborder=\"0\"\n\t\t\t\t\t\twidth=\"600px\"\n\t\t\t\t\t\theight=\"400px;\"\n\t\t\t\t\t\tclass=\"ml-1\"\n\t\t\t\t\t\tstyle=\"flex: 1 0 auto; border-radius: 5px; overflow: hidden;\"\n\t\t\t\t\t></iframe>\n\n\t\t\t\t</div>\n\t\t\t</section>\n\n\t\t\t<div class=\"deploy-segmet my-1 p-2\">\n\t\t\t\t<h3>\n\t\t\t\t\t<i class=\"fas fa-user-secret\"></i> Need a private / secure / selfhosted version?\n\t\t\t\t</h3>\n\t\t\t\t<p>Clone and adopt your own inboxkitten using our self-hosting package</p>\n\n\t\t\t\t<a href=\"https://github.com/uilicious/inboxkitten\" class=\"self-host-tier-link\">\n\t\t\t\t\t<div class=\"self-host-tier\">\n\t\t\t\t\t\t<div class=\"tier-title\">\n\t\t\t\t\t\t\t<h3>Self-Host</h3>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"price\">\n\t\t\t\t\t\t\t<h3>$0</h3>(*per month)\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</a>\n\n\t\t\t\t<p>All you need to do is the following steps</p>\n\t\t\t\t<div class=\"code mx-a mb-1\" style=\"max-width: 600px\"><pre>git clone \"https://github.com/uilicious/inboxkitten.git\"\ncd inboxkitten\n./config.sh\n./build.sh\nfirebase login\n./deploy/firebase/deploy.sh\n</pre></div>\n\t\t\t\t<p class=\"disclaimer\">\n\t\t\t\t\t*You will need to have signed up to firebase and mailgun, where you can deploy on their \"free\" tier.\n\t\t\t\t\t<br />We take zero responsiblity over your email, laptop, or life (or lack of)\n\t\t\t\t\t<br />\n\t\t\t\t\t<br />Optionally you should have some basic programming knowledge\n\t\t\t\t\t<br />For more details (or other deplyment options) see our\n\t\t\t\t\t<a\n\t\t\t\t\t\thref=\"https://github.com/uilicious/inboxkitten\"\n\t\t\t\t\t>github repository</a>\n\t\t\t\t</p>\n\t\t\t</div>\n\t\t\t<div class=\"line-break\"></div>\n\t\t</div>\n\t\t<div class=\"love-notes\">\n\t\t\t<p>\n\t\t\t\tmade with\n\t\t\t\t<span style=\"color: #e25555;\">&hearts;</span> by\n\t\t\t\t<a href=\"https://uilicious.com\">uilicious</a> in\n\t\t\t\t<a href=\"https://www.google.com.sg/search?q=singapore\">Singapore</a>\n\t\t\t</p>\n\t\t</div>\n\t\t<!--\n\t\t<div class=\"intermission-header\">\n\t\t\t<p>Host your own InboxKitten!</p>\n\t\t\t<i class=\"fa fa-chevron-down\" @click=\"scrollDown\"></i>\n\t\t</div>\n\t\tSet up guide\n\t\t<section id=\"express-js\">\n\t\t\t<h2>Coming Soon!</h2>\n\t\t</section>\n\t\t-->\n\t</div>\n</template>\n<script>\nimport $ from 'jquery'\nimport config from '@/../config/apiconfig.js'\nimport 'normalize.css'\nimport ClipboardJS from 'clipboard'\nimport { generate, count } from \"random-words\";\n\n// import CarbonAds from './components/CarbonAds.vue'\n\nexport default {\n\tname: 'LandingPage',\n\tcomponents: {\n\t\t// CarbonAds: CarbonAds\n\t},\n\tdata () {\n\t\treturn {\n\t\t\trandomName: ''\n\t\t}\n\t},\n\tmounted () {\n\t\tthis.randomName = this.generateRandomName().toString()\n\n\t\tthis.$clipboard = []\n\n\t\tlet self = this\n\n\t\tthis.$clipboard[0] = new ClipboardJS('#div-domain', {\n\t\t\ttext: function (trigger) {\n\t\t\t\tif (self.randomName.includes('@' + config.domain)) {\n\t\t\t\t\treturn self.randomName\n\t\t\t\t}\n\t\t\t\treturn self.randomName + '@' + config.domain\n\t\t\t}\n\t\t})\n\n\t\tthis.$clipboard[0].on('success', function (e) {\n\t\t\t$('#email-input').select()\n\t\t\t$('#div-domain').addClass('tooltipped tooltipped-s')\n\t\t\t$('#div-domain').attr('aria-label', 'Copied!')\n\t\t\t$('#div-domain').on('mouseleave', function () {\n\t\t\t\t$('#div-domain').removeClass('tooltipped tooltipped-s')\n\t\t\t\t$('#div-domain').removeAttr('aria-label')\n\t\t\t})\n\t\t})\n\t},\n\tbeforeDestroy () {\n\t\tif (this.$clipboard !== null) {\n\t\t\tthis.$clipboard.forEach(cb => {\n\t\t\t\tcb.destroy()\n\t\t\t})\n\t\t}\n\t},\n\tcomputed: {\n\t\tdomain () {\n\t\t\treturn config.domain\n\t\t}\n\t},\n\tmethods: {\n\t\tgenerateRandomName () {\n\t\t\t\n\t\t\treturn (\n\t\t\t\tgenerate({\n\t\t\t\t\texactly: 1,\n\t\t\t\t\twordsPerString: 2,\n\t\t\t\t\tseparator: '-'\n\t\t\t\t}) +\n\t\t\t\t'-' +\n\t\t\t\tMath.floor(Math.random() * 90 + 10)\n\t\t\t)\n\t\t},\n\t\tgoToInbox () {\n\t\t\tthis.$router.push({\n\t\t\t\tname: 'Inbox',\n\t\t\t\tparams: {\n\t\t\t\t\temail: this.randomName\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\t\tgoToKittenRouter () {\n\t\t\tlocation.href = '/kittenrouter'\n\t\t}\n\t}\n}\n</script>\n\n<style lang=\"scss\" rel=\"stylesheet/scss\">\n\t@import url(\"https://use.fontawesome.com/releases/v5.3.1/css/all.css\");\n\t@import \"scss/landingpage.scss\";\n\t@import \"primer-tooltips/index.scss\";\n\n\t.landing-navigation {\n\t\tdisplay: flex;\n\t\tjustify-content: flex-end;\n\t\talign-items: center;\n\t\tfloat: right;\n\t\twidth: 100vw;\n\t\theight: 1rem;\n\t\ttext-align: left;\n\n\t\t.landing-nav-main-logo {\n\t\t\twidth: 8rem;\n\t\t\tpadding-top: 7rem;\n\t\t\tpadding-right: 4rem;\n\t\t\tz-index: 2;\n\t\t}\n\n\t\t.landing-nav-header {\n\t\t\tpadding-top: 6rem;\n\t\t\tpadding-right: 3rem;\n\t\t\tcursor: pointer;\n\t\t}\n\t}\n\n\t@media only screen and (max-width: 470px) {\n\t\t.landing-navigation .landing-nav-main-logo {\n\t\t\tpadding-right: 4rem;\n\t\t\twidth: 5rem;\n\t\t\tz-index: 3;\n\t\t}\n\t\t.landing-nav-header {\n\t\t\tdisplay: none;\n\t\t}\n\t}\n\t@media only screen and (max-width: 800px) {\n\t\t.landing-navigation {\n\t\t\tpadding-top: 140px;\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "ui/src/main.js",
    "content": "// The Vue build version to load with the `import` command\n// (runtime-only or standalone) has been set in webpack.base.conf with an alias.\nimport Vue from 'vue'\nimport App from \"./App.vue\"\nimport router from './router'\n\nVue.config.productionTip = false\n\n/* eslint-disable no-new */\nnew Vue({\n  el: '#app',\n  router,\n  components: {App},\n  template: '<App/>',\n  created () {\n    this.$eventHub = new Vue({\n      name: 'EventHub',\n      parent: this,\n      functional: true\n    })\n  }\n})\n\n"
  },
  {
    "path": "ui/src/router/index.js",
    "content": "import Vue from 'vue'\nimport Vuex from 'vuex'\nimport Router from 'vue-router'\nimport LandingPage from '@/landingpage.vue'\nimport KittenRouter from '@/kittenrouter.vue'\nimport Inbox from '@/components/mail/inbox.vue'\nimport MessageDetail from '@/components/mail/message_detail.vue'\nimport MessageList from '@/components/mail/message_list.vue'\nimport vuescroll from 'vuescroll'\nimport 'vuescroll/dist/vuescroll.css'\n\nVue.mixin({\n  created () {\n    // pass the event hub down to descendents\n    if (!this.$eventHub && this.$root.$eventHub) {\n      this.$eventHub = this.$root.$eventHub\n    }\n  }\n})\n\nVue.use(Vuex)\nVue.use(Router)\nVue.use(vuescroll)\n\nexport default new Router({\n  mode: 'history',\n  routes: [\n    {\n      path: '/',\n      name: 'Kitten Land',\n      component: LandingPage\n    },\n    {\n      path: '/inbox/:email',\n      name: 'Inbox',\n      redirect: {name: 'List'},\n      component: Inbox,\n      children: [\n        {\n          path: '',\n          redirect: {name: 'List'}\n        },\n        {\n          path: 'list',\n          name: 'List',\n          component: MessageList\n        },\n        {\n          path: 'message/:region/:key',\n          name: 'Message',\n          component: MessageDetail\n        },\n        {\n          path: '*',\n          redirect: {name: 'List'}\n        }\n      ]\n    },\n    {\n      path: '/kittenrouter',\n      name: 'KittenRouter',\n      component: KittenRouter\n    },\n    {\n      path: '*',\n      redirect: {name: 'Kitten Land'}\n    }\n  ]\n})\n"
  },
  {
    "path": "ui/src/scss/_color.scss",
    "content": "//-----------------------------------------------\n// Base Color\n//-----------------------------------------------\n$color1-base: #119DA4;\n$color2-base: #0C7489;\n$color3-base: #13505B;\n$color4-base: #040404;\n$color5-base: #D7D9CE;\n$domain-base: #d7d9ce; //#06FFAB;\n\n//-----------------------------------------------\n// Dark Color varient\n//-----------------------------------------------\n$color1-dark: darken($color1-base, 10%);\n$color2-dark: darken($color2-base, 10%);\n$color3-dark: darken($color3-base, 10%);\n$color4-dark: darken($color4-base, 10%);\n$color5-dark: darken($color5-base, 10%);\n\n//-----------------------------------------------\n// Light Color varient\n//-----------------------------------------------\n$color1-light: lighten($color1-base, 10%);\n$color2-light: lighten($color2-base, 10%);\n$color3-light: lighten($color3-base, 10%);\n$color4-light: lighten($color4-base, 10%);\n$color5-light: lighten($color5-base, 10%);\n\n//-----------------------------------------------\n// Text colors\n//-----------------------------------------------\n$bright-text: #FFFFFF;\n$dark-text: #040404;\n\n//-----------------------------------------------\n// CALL TO ACTION - colors\n//-----------------------------------------------\n$cta-base: #FF4377;\n$cta-base-text: #FFFFFF;\n$cta-hover: #ff0;\n$cta-hover-text: #040404;\n\n//-----------------------------------------------\n// Header gradient background\n//-----------------------------------------------\n.header-gradient-background {\n\tbackground: #36D1DC;  /* fallback for old browsers */\n\tbackground: -webkit-linear-gradient(to right, #5B86E5, #36D1DC);  /* Chrome 10-25, Safari 5.1-6 */\n\tbackground: linear-gradient(to right, #5B86E5, #36D1DC); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */\n}\n"
  },
  {
    "path": "ui/src/scss/_common.scss",
    "content": "// Fix scroll alignment issues\nhtml,\nbody,\n#app {\n  min-height: 100%;\n  height: 100%;\n  position: relative;\n}\n\nhtml {\n  min-height: 600px;\n  margin: 0;\n  padding: 0;\n  overflow-x: hidden;\n}\n\n// Flex\n\n.flex-row {\n  display: flex;\n  flex-direction: row;\n}\n\n// Text alignment\n\n.text-left {\n  text-align: left;\n}\n\n.text-center {\n  text-align: center;\n}\n\n.text-right {\n  text-align: right;\n}\n\n// Code\n\n.code,\n.code pre {\n  font-family: monospace;\n\n  text-align: left;\n\n  background: #272822;\n  color: white;\n\n  overflow: auto;\n\n  width: auto;\n\n  border-radius: 5px;\n\n  padding: 1em;\n  margin-bottom: 1em;\n}\n\n// Margins\n\n.m-0 {\n  margin: 0;\n}\n\n.m-1 {\n  margin: 1rem;\n}\n\n.ml-1 {\n  margin-left: 1rem;\n}\n\n.mr-1 {\n  margin-right: 1rem;\n}\n\n.mb-0 {\n  margin-bottom: 0;\n}\n\n.mb-1 {\n  margin-bottom: 1rem;\n}\n\n.mb-2 {\n  margin-bottom: 1rem;\n}\n\n.mx-a {\n  margin-left: auto;\n  margin-right: auto;\n}\n\n.mx-1 {\n  margin-left: 1rem;\n  margin-right: 1rem;\n}\n\n.my-0 {\n  margin-top: 0;\n  margin-bottom: 0;\n}\n\n.my-1 {\n  margin-top: 1rem;\n  margin-bottom: 1rem;\n}\n\n.my-2 {\n  margin-top: 2rem;\n  margin-bottom: 2rem;\n}\n\n// Padding\n.p-0 {\n  padding: 0;\n}\n\n.p-1 {\n  padding: 1rem;\n}\n\n.p-2 {\n  padding: 2rem;\n}\n\n.pl-1 {\n  padding-left: 1rem;\n}\n\n.pr-1 {\n  padding-right: 1rem;\n}\n\n.pb-1 {\n  padding-bottom: 1rem;\n}\n\n.px-a {\n  padding-left: auto;\n  padding-right: auto;\n}\n\n.px-1 {\n  padding-left: 1rem;\n  padding-right: 1rem;\n}\n\n.py-0 {\n  padding-top: 0;\n  padding-bottom: 0;\n}\n\n.py-1 {\n  padding-top: 1rem;\n  padding-bottom: 1rem;\n}\n\n.py-2 {\n  padding-top: 2rem;\n  padding-bottom: 2rem;\n}\n"
  },
  {
    "path": "ui/src/scss/_producthunt.scss",
    "content": "//\n// Product Hunt banner\n//\n.product-hunt {\n\tbackground-color: white;\n\tpadding: 1rem;\n\tvertical-align: middle;\n\tcolor: black;\n\twidth:100%;\n\n\tmargin:auto;\n\tdisplay:flex;\n\tflex-direction:row;\n\tjustify-content:center;\n\n\tbackground: #fafad2;\n\tborder-top: 1px solid black;\n\tborder-bottom: 1px solid black;\n\n\t.ph-icon {\n\t\theight: 2rem;\n\t\tdisplay:inline-block;\n\t}\n\n\tp {\n\t\tdisplay:inline-block;\n\t\tpadding:0;\n\t\tmargin:0;\n\n\t\tmargin-left:1rem;\n\t\tmargin-top:0.5rem;\n\t}\n\n\tcursor: pointer;\n\t&:hover {\n\t\tbackground: darken(#fafad2, 25%);\n\t}\n\n\t@media (max-width:400px){\n\t\tfont-size:14px;\n\t\tpadding-left:0;\n\t}\n}\n"
  },
  {
    "path": "ui/src/scss/landingpage.scss",
    "content": "\n// Loading of font awesome\n// @import url(\"https://use.fontawesome.com/releases/v5.3.1/css/all.css\");\n\n// Loading of color scheme\n@import \"_color.scss\";\n\n// Min-width for body (for some sanity)\nbody {\n\tmin-width: 280px;\n\tbackground: white;\n}\n\n//\n// Landing page header and logo\n//\n.header {\n\tpadding-top: 3rem;\n\tpadding-bottom: 2rem;\n\twidth: 100vw;\n\n\t.logo {\n\t\twidth:40vw;\n\t\tmax-width: 800px;\n\t\tmargin-bottom:1rem;\n\t}\n\n\th1 {\n\t\tcolor:$bright-text;\n\t\tpadding-left:2rem;\n\t\tpadding-right:2rem;\n\t}\n\th2 {\n\t\tcolor:$dark-text;\n\t\tpadding-left:2rem;\n\t\tpadding-right:2rem;\n\t}\n\n\th1,h2 {\n\t\tmargin:0;\n\t}\n\n\t@media only screen and (max-width:760px) {\n\t\t.logo {\n\t\t\twidth: 80vw;\n\t\t}\n\n\t\t// Line display hack to force a new block\n\t\th1 a {\n\t\t\tdisplay:block;\n\t\t}\n\t}\n}\n\n//\n// Email selection box\n//\n.email-selection {\n\twidth: 100vw;\n\n\tpadding-top: 1rem;\n\tpadding-bottom: 3rem;\n\n\t// Adust the input box elements borders\n\t// $input-box-el-border-radius: 0.4rem;\n\t$input-box-el-border-radius: 0rem;\n\n\t// The main input box\n\t.input-box {\n\n\t\t// Remove space between inline-blocks\n\t\tfont-size:0;\n\n\t\t// Input-box radius : note you will need\n\t\t// to update input left top/bottom radius as well\n\t\tbackground-color:$color5-base;\n\t\tdisplay: inline-block;\n\t\t\n\t\tborder-radius: $input-box-el-border-radius;\n\t\tborder: 3px solid black;\n\n\t\t// Common settings for input, and suffix label\n\t\t.input-email, .input-suffix {\n\t\t\tpadding:0;\n\t\t\tmargin:0;\n\t\t\tfont-size:1rem;\n\t\t\tpadding-top:0.25rem;\n\t\t\tpadding-bottom:0.25rem;\n\t\t}\n\n\t\t// Input email styling\n\t\t.input-email {\n\t\t\tborder:0px;\n\t\t\twidth: 12rem;\n\t\t\tdisplay: inline-block;\n\t\t\ttext-align:center;\n\n\t\t\t// Border radius overwrite\n\t\t\tborder-top-left-radius: $input-box-el-border-radius;\n\t\t\tborder-bottom-left-radius: $input-box-el-border-radius;\n\t\t\tborder-top-right-radius: 0rem;\n\t\t\tborder-bottom-right-radius: 0rem;\n\n\t\t\t// input text color\n\t\t\tcolor: $dark-text;\n\n\t\t\t// Right border\n\t\t\tborder-right: 3px solid black;\n\t\t}\n\n\t\t// Input email suffix\n\t\t.input-suffix {\n\t\t\twidth: 10rem;\n\t\t\tdisplay: inline-block;\n\t\t\tpadding-left:1rem;\n\t\t\tpadding-right:1rem;\n\n\t\t\t// Text with subtle fade out\n\t\t\tcolor: fade-out($dark-text, 0.3);\n\n\t\t}\n\t}\n\n\t// Submit box\n\t.submit-box {\n\t\tdisplay:inline-block;\n\t\tmargin-left:1rem;\n\t}\n\t// Submission button styling\n\t.submit  {\n\t\tpadding: 0.25rem 1rem 0.25rem 1rem;\n\t\tmargin:0;\n\t\tborder: 0;\n\n\t\tfont-size:1rem;\n\t\tfont-weight: bold;\n\n\t\tbackground: $cta-base;\n\t\tcolor: $cta-base-text;\n\n\t\tborder-radius: $input-box-el-border-radius;\n\n\t\tfont-size: 100%;\n\t\tfont-family: inherit;\n\t\ttext-transform: none;\n\t\t-webkit-appearance: button;\n\t\tborder: 3px solid black;\n\t}\n\n\t// Hover submit button styling\n\t.submit:hover {\n\t\tbackground: $cta-hover;\n\t\tcolor: $cta-hover-text;\n\t}\n\n\t/*\n\t@media only screen and (max-width:760px) {\n\t\t// Collapes the inbox input into 2 lines\n\t\t.input-box {\n\t\t\tdisplay: inline-block;\n\t\t\tvertical-align: middle;\n\t\t\twidth: 12rem;\n\t\t\t.input-email {\n\t\t\t\tborder-top-right-radius: $input-box-el-border-radius;\n\t\t\t\tborder-bottom-left-radius: 0;\n\t\t\t}\n\t\t}\n\n\t\t// Increase submit button height\n\t\t.submit-box {\n\t\t\tdisplay: inline-block;\n\t\t\tvertical-align: middle;\n\t\t\t.submit {\n\t\t\t\tdisplay: inline;\n\t\t\t\theight: 3.5rem;\n\t\t\t}\n\t\t}\n\t}\n\t*/\n\n\t@media only screen and (max-width:760px) {\n\t\t// Collapes the inbox input into 2 lines\n\t\t.input-box {\n\t\t\tdisplay: inline-block;\n\t\t\tvertical-align: middle;\n\t\t\twidth: 75%;\n\t\t\t.input-email {\n\t\t\t\tborder-top-right-radius: $input-box-el-border-radius;\n\t\t\t\tborder-bottom-left-radius: 0;\n\t\t\t\twidth: 100%;\n\t\t\t\theight:2rem;\n\t\t\t}\n\t\t}\n\n\t\t// Increase submit button height\n\t\t.submit-box {\n\t\t\tmargin-top:1.25rem;\n\t\t\tmargin-left:0;\n\n\t\t\tdisplay: block;\n\t\t\tvertical-align: middle;\n\n\t\t\t.submit {\n\t\t\t\tdisplay: inline;\n\t\t\t\twidth: 75%;\n\t\t\t\theight:2.5rem;\n\t\t\t}\n\t\t}\n\t}\n}\n\n//\n// Love notes\n//\n.love-notes {\n\tpadding: 1rem;\n\tbackground: white;\n\n\tp {\n\t\tfont-size:1.2rem;\n\t}\n}\n\n//\n// Line break\n//\n.line-break {\n\twidth: 80vw;\n\tbackground: black;\n\tmargin: auto;\n\theight: 1px;\n}\n\n//\n// Deployment guide content\n//\n.info-guide {\n\tpadding: 1rem;\n\n\t.features {\n\t\tpadding-top:2rem;\n\t\tmargin:auto;\n\t\tdisplay:flex;\n\t\tflex-direction:row;\n\t\tjustify-content:center;\n\t}\n\n\t.feature-card {\n\t\tflex-grow: 1;\n\t\tflex-shrink: 1;\n\t\tflex-basis: 0;\n\t\tpadding-left:1rem;\n\t\tpadding-right:1rem;\n\t}\n\t\n\t.feature-card-hover:hover {\n\t\tbackground-color:rgba(30,30,30,0.2);\n\t\tborder-radius: 8px;\n\t}\n\n\t.fas {\n\t\tmargin-right:0.5rem;\n\t}\n\n\t@media only screen and (max-width:800px) {\n\t\t.features {\n\t\t\tflex-direction:column;\n\t\t}\n\t}\n\n\t@media only screen and (max-width:1080px) {\n\t\t.steps {\n\t\t\tflex-direction: column;\n\t\t}\n\t}\n}\n\n.snippet {\n\tpadding: 1rem;\n\tdisplay: flex;\n\tflex-direction: row;\n\tmargin:auto;\n\tjustify-content: center;\n\talign-items: center;\n\n\t.features {\n\t\tpadding: 0;\n\t\tbackground-color:rgba(30,30,30,0.1); \n\t\tborder-radius:8px;\n\t}\n\t.features:hover {\n\t\tbackground-color:rgba(30,30,30,0.2);\n\t}\n\n\t.feature-card {\n\t\tflex-grow: 1;\n\t\tflex-shrink: 1;\n\t\tflex-basis: 0;\n\t\tpadding-left:1rem;\n\t\tpadding-right:0.5rem;\n\t\tpadding-top: 1rem;\n\t\talign-self: center;\n\t}\n\n\t.feature-gif {\n\t\tflex-grow: 1;\n\t\tflex-shrink: 1;\n\t\tflex-basis: 0;\n\t\talign-self: flex-end;\n\t\tpadding-left:0.5rem;\n\t\tpadding-right:1rem;\n\t\tpadding-bottom: 1rem;\n\t\tpadding-top: 1rem;\n\t\t\n\t\timg {\n\t\t\twidth: 100%;\n\t\t\theight: 100%;\n\t\t}\n\t}\n\t\n\t.hljs {\n\t\ttext-align: left;\n\t\tborder-radius: 8px;\n\t\tdisplay: block; \n\t\toverflow-x: auto; \n\t\tmargin-top: 2rem;\n\t\tpadding: 0.5em;\n\t\tbackground: rgb(35, 36, 31); \n\t\tcolor: rgb(248, 248, 242);\n\t}\n\n\t@media only screen and (max-width:1170px) {\n\t\t.features {\n\t\t\tflex-direction:column;\n\t\t\tpre {\n\t\t\t\tfont-size:0.75rem;\n\t\t\t}\n\t\t}\n\n\t\t.feature-gif {\n\t\t\tpadding-top: 0;\n\t\t\tpadding-left: 1rem;\n\t\t}\n\t}\n\n\t@media only screen and (max-width:630px){\n\t\t.feature-card {\n\t\t\tpadding-right:1rem;\n\t\t\timg {\n\t\t\t\twidth:100%;\n\t\t\t\theight: auto;\n\t\t\t}\n\t\t\tpre {\n\t\t\t\tfont-size:0.5rem;\n\t\t\t}\n\t\t}\n\n\t\t.hljs {\n\t\t\tmargin-top: 1rem;\n\t\t}\n\t}\n}\n\n.line-break {\n\theight:2px;\n\n\t// Fallback\n\tbackground:black;\n\t// Gradient\n\tbackground:linear-gradient(to right, rgba(0,0,0,0), rgba(0,0,0,0.2), rgba(0,0,0,0));\n\n\twidth:50%;\n\tmargin:auto;\n\tmargin-top:2rem;\n\t// margin-bottom:4rem;\n}\n\n.deploy-code {\n\twidth: 30rem;\n\tmax-width:90%;\n\tmargin:auto;\n\tborder-radius: 8px;\n\tscroll-behavior: auto;\n}\n\n.config-code {\n\tfont-size:0.8rem;\n\twidth:50rem;\n\tmax-width: 90%;\n\tbackground: #F6F8FF;\n\tmargin:auto;\n\tborder-radius: 8px;\n\tscroll-behavior: auto;\n\ttext-align:justify;\n}\n\n@media only screen and (max-width:800px) {\n\t.deploy-code {\n\t\tfont-size:0.75rem;\n\t}\n\n\t.config-code {\n\t\tfont-size: 0.75rem;\n\t}\n}\n\n.deploy-segmet {\n\n\tp {\n\t\tpadding:1rem;\n\t}\n\th3 {\n\t\tpadding-left:1rem;\n\t\tpadding-right:1rem;\n\t}\n\n\t.self-host-tier-link {\n\t\ttext-decoration: none;\n\t\tcursor: pointer;\n\t\tdisplay:inline-block;\n\t\twidth:12rem;\n\t}\n\n\t.self-host-tier {\n\t\tborder:3px solid black;\n\t\tborder-radius:5px;\n\t\tdisplay:flex;\n\t\tflex-direction: column;\n\t\tpadding:0;\n\t\tmax-width:12rem;\n\t\tmargin:auto;\n\t\tbackground:white;\n\n\t\ttext-decoration: none;\n\n\t\t.tier-title {\n\t\t\t\n\t\t\tmargin:0;\n\t\t\tbackground: #5a86e4;\n\t\t\tpadding:1rem;\n\t\t\tborder-bottom:2px solid black;\n\n\t\t\th3 {\n\t\t\t\tdisplay: inline-block;\n\t\t\t\tcolor:white;\n\t\t\t\tmargin:0;\n\t\t\t\tpadding:0;\n\t\t\t}\n\t\t}\n\n\t\t.price {\n\t\t\tmargin:0;\n\t\t\tpadding:1rem;\n\t\t\tcolor:black;\n\t\t\t\n\t\t\th3 {\n\t\t\t\tcolor:black;\n\t\t\t\tfont-size: 4rem;\n\t\t\t\tmargin:0;\n\t\t\t\tpadding:0;\n\t\t\t}\n\t\t}\n\t}\n\n\t.disclaimer {\n\t\tfont-size:0.75rem;\n\t}\n}"
  },
  {
    "path": "ui/src/store/emailStore.js",
    "content": "import config from '@/../config/apiconfig.js'\n\n// State\nconst state = {\n  domain: config.domain,\n  apiUrl: config.apiUrl\n}\n\nconst mutations = {\n}\n\nconst getters = {}\n\nconst actions = {}\n\nexport default {\n  mutations,\n  state,\n  getters,\n  actions\n}\n"
  },
  {
    "path": "ui/uilicious-test/inboxkitten.test.js",
    "content": "//\n// This is a https://uilicious.com/ test case\n//\n// See : https://test.uilicious.com/test/public/DRupiHvXGspN9wVUYNv3Re\n// for an example of using this test script\n//\n// Alternatively signup to schedule a background task, or use their CLI =)\n// #selfpromotion\n//\n\n//\n// Testing for empty inbox\n//\n\n// Lets goto inbox kitten\nI.goTo(\"https://inboxkitten.com\");\nI.see(\"Open-Source Disposable Email\");\n\n// Go to a random inbox inbox\nI.fill(\"email\", SAMPLE.id(22));\nI.click(\"Get Mail Nyow!\");\n\n// Check that its empty\nI.see(\"There for no messages for this kitten :(\");\n\n//\n// Testing for regular email\n// (sent using a jenkins perodic build)\n//\n\n// Lets go back inbox kitten mailbox\nI.goTo(\"https://inboxkitten.com\");\nI.see(\"Open-Source Disposable Email\");\nI.fill(\"email\", \"ik-reciever-f7s1g28\");\nI.click(\"Get Mail Nyow!\");\n\n// See an email we expect, and click it\nI.click(\"Testing inboxkitten subject\");\n\n// And validate the content\nI.see(\"Testing inboxkitten text content\");\n"
  },
  {
    "path": "ui/vite.config.js",
    "content": "import path from \"path\"\nimport { defineConfig } from 'vite'\nimport vue from '@vitejs/plugin-vue2'\n\n// https://vitejs.dev/config/\nexport default defineConfig({\n  plugins: [vue()],\n  resolve: {\n    alias: {\n      // resolve \"@\" - required for css imports\n      \"@\": path.resolve(__dirname, \"src\"),\n      // we need to use the vue build with the runtime template compiler included, otherwise vue will complain about the needing the runtime template compiler or to pre-compile the templates\n      vue: 'vue/dist/vue.esm.js',\n    }\n  },\n})\n"
  }
]