diff --git a/.gitignore b/.gitignore index 4a903f99..dbdafba7 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,8 @@ Cargo.lock # intellij files .idea/** + +.history/ + +# For multipart example +upload.png diff --git a/.travis.yml b/.travis.yml index 24111b9d..f3e3603a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,8 +3,13 @@ sudo: false dist: trusty cache: - cargo: true apt: true + directories: + - $HOME/.cargo + - $HOME/.rustup + +before_cache: + - rm -rf $HOME/.cargo/registry matrix: include: @@ -28,44 +33,13 @@ before_install: before_script: - | if [[ "$TRAVIS_RUST_VERSION" == "nightly" ]]; then - ( ( cargo install clippy && export CLIPPY=true ) || export CLIPPY=false ); + rustup component add clippy --toolchain=nightly fi - export PATH=$PATH:~/.cargo/bin script: + - cargo check --all - | - cd async_db && cargo check && cd .. - cd async_ex1 && cargo check && cd .. - cd actix_redis && cargo check && cd .. - cd actix_todo && cargo check && cd .. - cd basics && cargo check && cd .. - cd cookie-auth && cargo check && cd .. - cd cookie-auth-full && cargo check && cd .. - cd cookie-session && cargo check && cd .. - cd diesel && cargo check && cd .. - cd error_handling && cargo check && cd .. - cd form && cargo check && cd .. - cd hello-world && cargo check && cd .. - cd http-proxy && cargo check && cd .. - cd http-full-proxy && cargo check && cd .. - cd json && cargo check && cd .. - cd juniper && cargo check && cd .. - cd middleware && cargo check && cd .. - cd multipart && cargo check && cd .. - cd protobuf && cargo check && cd .. - cd r2d2 && cargo check && cd .. - cd redis-session && cargo check && cd .. - cd simple-auth-sarver && cargo check && cd .. - cd state && cargo check && cd .. - cd static_index && cargo check && cd .. - cd template_askama && cargo check && cd .. - cd template_tera && cargo check && cd .. - cd tls && cargo check && cd .. - cd rustls && cargo check && cd .. - cd udp-echo && cargo check && cd .. - cd unix-socket && cargo check && cd .. - cd web-cors/backend && cargo check && cd ../.. - cd websocket && cargo check && cd .. - cd websocket-chat && cargo check && cd .. - cd websocket-chat-broker && cargo check && cd .. - cd websocket-tcp-chat && cargo check && cd .. + if [[ "$TRAVIS_RUST_VERSION" == "nightly" ]]; then + cargo clippy + fi diff --git a/Cargo.toml b/Cargo.toml index 6a9b3e27..ed798f7c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,30 +5,33 @@ members = [ "actix_todo", "async_db", "async_ex1", + "async_ex2", "basics", "cookie-auth", - "cookie-auth-full", "cookie-session", "diesel", "error_handling", "form", "hello-world", "http-proxy", - "http-full-proxy", "json", + "json_error", + "jsonrpc", "juniper", "middleware", "multipart", "protobuf", "r2d2", "redis-session", + "rustls", "simple-auth-server", "state", "static_index", "template_askama", "template_tera", + "template_yarte", + "template_handlebars", "tls", - "rustls", "udp-echo", "unix-socket", "web-cors/backend", diff --git a/README.md b/README.md index a79557f5..b42550bc 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,7 @@ A curated list of examples related to actix. ## from community -* [Rust-webapp-starter -](https://github.com/rustlang-cn/Rust-webapp-starter) : Rust single page webapp written in actix-web with vuejs. -* [Rust中文社区](http://47.104.146.58/) : A CN online community forum written in Actix-web with vuejs [source](https://github.com/rustlang-cn/ruster). +* [OUISRC](http://ouisrc.xyz/) : Welcome to OUISRC, let us Gain more in exploration and interaction. * [Roseline](https://github.com/DoumanAsh/roseline.rs) : A personal web site and discord & IRC bot to access simple SQLite database. Demonstrates usage of various actix and actix-web concepts. * [Actix Auth Server](https://hgill.io/posts/auth-microservice-rust-actix-web-diesel-complete-tutorial-part-1/) : Auth web micro-service with rust using actix-web - complete tutorial. See code in [examples/simple-auth-server](https://github.com/actix/examples/tree/master/simple-auth-server) ## Contribute diff --git a/actix_redis/Cargo.toml b/actix_redis/Cargo.toml index a716e624..f1534fe3 100644 --- a/actix_redis/Cargo.toml +++ b/actix_redis/Cargo.toml @@ -2,13 +2,15 @@ name = "actix_redis" version = "0.1.0" authors = ["dowwie "] +edition = "2018" +workspace = ".." [dependencies] -actix = "0.7.3" -actix-web = "0.7.3" -actix-redis = "0.5.1" +actix = "0.8.2" +actix-web = "1.0.3" +actix-redis = "0.6" futures = "0.1.23" redis-async = "0.4.0" serde = "1.0.71" serde_derive = "1.0.71" -env_logger = "0.5.12" +env_logger = "0.6" diff --git a/actix_redis/src/main.rs b/actix_redis/src/main.rs index d9ccfa8e..a9ed3c4c 100644 --- a/actix_redis/src/main.rs +++ b/actix_redis/src/main.rs @@ -1,34 +1,26 @@ -extern crate actix; -extern crate actix_redis; -extern crate actix_web; -extern crate env_logger; -extern crate futures; -#[macro_use] extern crate redis_async; -extern crate serde; -#[macro_use] extern crate serde_derive; +#[macro_use] +extern crate redis_async; +#[macro_use] +extern crate serde_derive; - -use std::sync::Arc; use actix::prelude::*; -use actix_redis::{Command, RedisActor, Error as ARError}; -use actix_web::{middleware, server, App, HttpRequest, HttpResponse, Json, - AsyncResponder, http::Method, Error as AWError}; -use futures::future::{Future, join_all}; +use actix_redis::{Command, Error as ARError, RedisActor}; +use actix_web::{middleware, web, App, Error as AWError, HttpResponse, HttpServer}; +use futures::future::{join_all, Future}; use redis_async::resp::RespValue; - #[derive(Deserialize)] pub struct CacheInfo { one: String, two: String, - three: String + three: String, } - -fn cache_stuff((info, req): (Json, HttpRequest)) - -> impl Future { +fn cache_stuff( + info: web::Json, + redis: web::Data>, +) -> impl Future { let info = info.into_inner(); - let redis = req.state().redis_addr.clone(); let one = redis.send(Command(resp_array!["SET", "mydomain:one", info.one])); let two = redis.send(Command(resp_array!["SET", "mydomain:two", info.two])); @@ -44,63 +36,58 @@ fn cache_stuff((info, req): (Json, HttpRequest)) let info_set = join_all(vec![one, two, three].into_iter()); info_set - .map_err(AWError::from) - .and_then(|res: Vec>| - // successful operations return "OK", so confirm that all returned as so - if !res.iter().all(|res| match res { - Ok(RespValue::SimpleString(x)) if x=="OK" => true, - _ => false - }) { - Ok(HttpResponse::InternalServerError().finish()) - } else { - Ok(HttpResponse::Ok().body("successfully cached values")) - } - ) - .responder() + .map_err(AWError::from) + .and_then(|res: Vec>| + // successful operations return "OK", so confirm that all returned as so + if !res.iter().all(|res| match res { + Ok(RespValue::SimpleString(x)) if x=="OK" => true, + _ => false + }) { + Ok(HttpResponse::InternalServerError().finish()) + } else { + Ok(HttpResponse::Ok().body("successfully cached values")) + } + ) } -fn del_stuff(req: HttpRequest) - -> impl Future { - let redis = req.state().redis_addr.clone(); - - redis.send(Command(resp_array!["DEL", "mydomain:one", "mydomain:two", "mydomain:three"])) - .map_err(AWError::from) - .and_then(|res: Result| - match &res { - Ok(RespValue::Integer(x)) if x==&3 => - Ok(HttpResponse::Ok().body("successfully deleted values")), - _ =>{println!("---->{:?}", res); - Ok(HttpResponse::InternalServerError().finish())} - }) - .responder() - +fn del_stuff( + redis: web::Data>, +) -> impl Future { + redis + .send(Command(resp_array![ + "DEL", + "mydomain:one", + "mydomain:two", + "mydomain:three" + ])) + .map_err(AWError::from) + .and_then(|res: Result| match &res { + Ok(RespValue::Integer(x)) if x == &3 => { + Ok(HttpResponse::Ok().body("successfully deleted values")) + } + _ => { + println!("---->{:?}", res); + Ok(HttpResponse::InternalServerError().finish()) + } + }) } -pub struct AppState { - pub redis_addr: Arc> -} - -fn main() { - ::std::env::set_var("RUST_LOG", "actix_web=info,actix_redis=info"); +fn main() -> std::io::Result<()> { + std::env::set_var("RUST_LOG", "actix_web=info,actix_redis=info"); env_logger::init(); - let sys = actix::System::new("actix_redis_ex"); - server::new(|| { - let redis_addr = Arc::new(RedisActor::start("127.0.0.1:6379")); - let app_state = AppState{redis_addr}; + HttpServer::new(|| { + let redis_addr = RedisActor::start("127.0.0.1:6379"); - App::with_state(app_state) - .middleware(middleware::Logger::default()) - .resource("/stuff", |r| { - r.method(Method::POST) - .with_async(cache_stuff); - r.method(Method::DELETE) - .with_async(del_stuff)}) - - }).bind("0.0.0.0:8080") - .unwrap() - .workers(1) - .start(); - - let _ = sys.run(); + App::new() + .data(redis_addr) + .wrap(middleware::Logger::default()) + .service( + web::resource("/stuff") + .route(web::post().to_async(cache_stuff)) + .route(web::delete().to_async(del_stuff)), + ) + }) + .bind("0.0.0.0:8080")? + .run() } diff --git a/actix_todo/Cargo.toml b/actix_todo/Cargo.toml index eb17097e..4812c25c 100644 --- a/actix_todo/Cargo.toml +++ b/actix_todo/Cargo.toml @@ -2,10 +2,13 @@ authors = ["Dan Munckton "] name = "actix-todo" version = "0.1.0" +workspace = ".." +edition = "2018" [dependencies] -actix = "0.7.3" -actix-web = "0.7.4" +actix-web = "1.0.0" +actix-files = "0.1.1" +actix-session = "0.2.0" dotenv = "0.13.0" env_logger = "0.5.10" futures = "0.1.22" diff --git a/actix_todo/src/api.rs b/actix_todo/src/api.rs index fcb67a0c..cb3e15ae 100644 --- a/actix_todo/src/api.rs +++ b/actix_todo/src/api.rs @@ -1,41 +1,34 @@ -use actix::prelude::Addr; -use actix_web::middleware::Response; -use actix_web::{ - error, fs::NamedFile, http, AsyncResponder, Form, FutureResponse, HttpRequest, - HttpResponse, Path, Responder, Result, -}; -use futures::{future, Future}; +use actix_files::NamedFile; +use actix_session::Session; +use actix_web::middleware::errhandlers::ErrorHandlerResponse; +use actix_web::{dev, error, http, web, Error, HttpResponse, Responder, Result}; +use futures::future::{err, Either, Future, IntoFuture}; use tera::{Context, Tera}; -use db::{AllTasks, CreateTask, DbExecutor, DeleteTask, ToggleTask}; -use session::{self, FlashMessage}; +use crate::db; +use crate::session::{self, FlashMessage}; -pub struct AppState { - pub template: Tera, - pub db: Addr, -} - -pub fn index(req: HttpRequest) -> FutureResponse { - req.state() - .db - .send(AllTasks) +pub fn index( + pool: web::Data, + tmpl: web::Data, + session: Session, +) -> impl Future { + web::block(move || db::get_all_tasks(&pool)) .from_err() - .and_then(move |res| match res { + .then(move |res| match res { Ok(tasks) => { let mut context = Context::new(); - context.add("tasks", &tasks); + context.insert("tasks", &tasks); //Session is set during operations on other endpoints //that can redirect to index - if let Some(flash) = session::get_flash(&req)? { - context.add("msg", &(flash.kind, flash.message)); - session::clear_flash(&req); + if let Some(flash) = session::get_flash(&session)? { + context.insert("msg", &(flash.kind, flash.message)); + session::clear_flash(&session); } - let rendered = req.state() - .template - .render("index.html.tera", &context) - .map_err(|e| { + let rendered = + tmpl.render("index.html.tera", &context).map_err(|e| { error::ErrorInternalServerError(e.description().to_owned()) })?; @@ -43,7 +36,6 @@ pub fn index(req: HttpRequest) -> FutureResponse { } Err(e) => Err(e), }) - .responder() } #[derive(Deserialize)] @@ -52,34 +44,34 @@ pub struct CreateForm { } pub fn create( - (req, params): (HttpRequest, Form), -) -> FutureResponse { + params: web::Form, + pool: web::Data, + session: Session, +) -> impl Future { if params.description.is_empty() { - future::lazy(move || { + Either::A( session::set_flash( - &req, + &session, FlashMessage::error("Description cannot be empty"), - )?; - Ok(redirect_to("/")) - }).responder() + ) + .map(|_| redirect_to("/")) + .into_future(), + ) } else { - req.state() - .db - .send(CreateTask { - description: params.description.clone(), - }) - .from_err() - .and_then(move |res| match res { - Ok(_) => { - session::set_flash( - &req, - FlashMessage::success("Task successfully added"), - )?; - Ok(redirect_to("/")) - } - Err(e) => Err(e), - }) - .responder() + Either::B( + web::block(move || db::create_task(params.into_inner().description, &pool)) + .from_err() + .then(move |res| match res { + Ok(_) => { + session::set_flash( + &session, + FlashMessage::success("Task successfully added"), + )?; + Ok(redirect_to("/")) + } + Err(e) => Err(e), + }), + ) } } @@ -94,49 +86,50 @@ pub struct UpdateForm { } pub fn update( - (req, params, form): (HttpRequest, Path, Form), -) -> FutureResponse { + db: web::Data, + params: web::Path, + form: web::Form, + session: Session, +) -> impl Future { match form._method.as_ref() { - "put" => toggle(req, params), - "delete" => delete(req, params), + "put" => Either::A(Either::A(toggle(db, params))), + "delete" => Either::A(Either::B(delete(db, params, session))), unsupported_method => { let msg = format!("Unsupported HTTP method: {}", unsupported_method); - future::err(error::ErrorBadRequest(msg)).responder() + Either::B(err(error::ErrorBadRequest(msg))) } } } fn toggle( - req: HttpRequest, - params: Path, -) -> FutureResponse { - req.state() - .db - .send(ToggleTask { id: params.id }) + pool: web::Data, + params: web::Path, +) -> impl Future { + web::block(move || db::toggle_task(params.id, &pool)) .from_err() - .and_then(move |res| match res { + .then(move |res| match res { Ok(_) => Ok(redirect_to("/")), Err(e) => Err(e), }) - .responder() } fn delete( - req: HttpRequest, - params: Path, -) -> FutureResponse { - req.state() - .db - .send(DeleteTask { id: params.id }) + pool: web::Data, + params: web::Path, + session: Session, +) -> impl Future { + web::block(move || db::delete_task(params.id, &pool)) .from_err() - .and_then(move |res| match res { + .then(move |res| match res { Ok(_) => { - session::set_flash(&req, FlashMessage::success("Task was deleted."))?; + session::set_flash( + &session, + FlashMessage::success("Task was deleted."), + )?; Ok(redirect_to("/")) } Err(e) => Err(e), }) - .responder() } fn redirect_to(location: &str) -> HttpResponse { @@ -145,32 +138,31 @@ fn redirect_to(location: &str) -> HttpResponse { .finish() } -pub fn bad_request( - req: &HttpRequest, - resp: HttpResponse, -) -> Result { +pub fn bad_request(res: dev::ServiceResponse) -> Result> { let new_resp = NamedFile::open("static/errors/400.html")? - .set_status_code(resp.status()) - .respond_to(req)?; - Ok(Response::Done(new_resp)) + .set_status_code(res.status()) + .respond_to(res.request())?; + Ok(ErrorHandlerResponse::Response( + res.into_response(new_resp.into_body()), + )) } -pub fn not_found( - req: &HttpRequest, - resp: HttpResponse, -) -> Result { +pub fn not_found(res: dev::ServiceResponse) -> Result> { let new_resp = NamedFile::open("static/errors/404.html")? - .set_status_code(resp.status()) - .respond_to(req)?; - Ok(Response::Done(new_resp)) + .set_status_code(res.status()) + .respond_to(res.request())?; + Ok(ErrorHandlerResponse::Response( + res.into_response(new_resp.into_body()), + )) } -pub fn internal_server_error( - req: &HttpRequest, - resp: HttpResponse, -) -> Result { +pub fn internal_server_error( + res: dev::ServiceResponse, +) -> Result> { let new_resp = NamedFile::open("static/errors/500.html")? - .set_status_code(resp.status()) - .respond_to(req)?; - Ok(Response::Done(new_resp)) + .set_status_code(res.status()) + .respond_to(res.request())?; + Ok(ErrorHandlerResponse::Response( + res.into_response(new_resp.into_body()), + )) } diff --git a/actix_todo/src/db.rs b/actix_todo/src/db.rs index 534ae648..2bba1446 100644 --- a/actix_todo/src/db.rs +++ b/actix_todo/src/db.rs @@ -1,13 +1,11 @@ use std::ops::Deref; -use actix::prelude::{Actor, Handler, Message, SyncContext}; -use actix_web::{error, Error}; use diesel::pg::PgConnection; use diesel::r2d2::{ConnectionManager, Pool, PoolError, PooledConnection}; -use model::{NewTask, Task}; +use crate::model::{NewTask, Task}; -type PgPool = Pool>; +pub type PgPool = Pool>; type PgPooledConnection = PooledConnection>; pub fn init_pool(database_url: &str) -> Result { @@ -15,86 +13,29 @@ pub fn init_pool(database_url: &str) -> Result { Pool::builder().build(manager) } -pub struct DbExecutor(pub PgPool); - -impl DbExecutor { - pub fn get_conn(&self) -> Result { - self.0.get().map_err(|e| error::ErrorInternalServerError(e)) - } +fn get_conn(pool: &PgPool) -> Result { + pool.get().map_err(|_| "Can't get connection") } -impl Actor for DbExecutor { - type Context = SyncContext; +pub fn get_all_tasks(pool: &PgPool) -> Result, &'static str> { + Task::all(get_conn(pool)?.deref()).map_err(|_| "Error inserting task") } -pub struct AllTasks; - -impl Message for AllTasks { - type Result = Result, Error>; +pub fn create_task(todo: String, pool: &PgPool) -> Result<(), &'static str> { + let new_task = NewTask { description: todo }; + Task::insert(new_task, get_conn(pool)?.deref()) + .map(|_| ()) + .map_err(|_| "Error inserting task") } -impl Handler for DbExecutor { - type Result = Result, Error>; - - fn handle(&mut self, _: AllTasks, _: &mut Self::Context) -> Self::Result { - Task::all(self.get_conn()?.deref()) - .map_err(|_| error::ErrorInternalServerError("Error inserting task")) - } +pub fn toggle_task(id: i32, pool: &PgPool) -> Result<(), &'static str> { + Task::toggle_with_id(id, get_conn(pool)?.deref()) + .map(|_| ()) + .map_err(|_| "Error inserting task") } -pub struct CreateTask { - pub description: String, -} - -impl Message for CreateTask { - type Result = Result<(), Error>; -} - -impl Handler for DbExecutor { - type Result = Result<(), Error>; - - fn handle(&mut self, todo: CreateTask, _: &mut Self::Context) -> Self::Result { - let new_task = NewTask { - description: todo.description, - }; - Task::insert(new_task, self.get_conn()?.deref()) - .map(|_| ()) - .map_err(|_| error::ErrorInternalServerError("Error inserting task")) - } -} - -pub struct ToggleTask { - pub id: i32, -} - -impl Message for ToggleTask { - type Result = Result<(), Error>; -} - -impl Handler for DbExecutor { - type Result = Result<(), Error>; - - fn handle(&mut self, task: ToggleTask, _: &mut Self::Context) -> Self::Result { - Task::toggle_with_id(task.id, self.get_conn()?.deref()) - .map(|_| ()) - .map_err(|_| error::ErrorInternalServerError("Error inserting task")) - } -} - -pub struct DeleteTask { - pub id: i32, -} - -impl Message for DeleteTask { - type Result = Result<(), Error>; -} - -impl Handler for DbExecutor { - type Result = Result<(), Error>; - - fn handle(&mut self, task: DeleteTask, _: &mut Self::Context) -> Self::Result { - Task::delete_with_id(task.id, self.get_conn()?.deref()) - .map(|_| ()) - .map_err(|_| error::ErrorInternalServerError("Error inserting task")) - } +pub fn delete_task(id: i32, pool: &PgPool) -> Result<(), &'static str> { + Task::delete_with_id(id, get_conn(pool)?.deref()) + .map(|_| ()) + .map_err(|_| "Error inserting task") } diff --git a/actix_todo/src/main.rs b/actix_todo/src/main.rs index f2de26af..aca105ec 100644 --- a/actix_todo/src/main.rs +++ b/actix_todo/src/main.rs @@ -1,8 +1,3 @@ -extern crate actix; -extern crate actix_web; -extern crate dotenv; -extern crate env_logger; -extern crate futures; #[macro_use] extern crate diesel; #[macro_use] @@ -12,12 +7,13 @@ extern crate serde_derive; #[macro_use] extern crate tera; -use actix::prelude::SyncArbiter; -use actix_web::middleware::session::{CookieSessionBackend, SessionStorage}; -use actix_web::middleware::{ErrorHandlers, Logger}; -use actix_web::{dev::Resource, fs, http, server, App}; +use std::{env, io}; + +use actix_files as fs; +use actix_session::CookieSession; +use actix_web::middleware::{errhandlers::ErrorHandlers, Logger}; +use actix_web::{http, web, App, HttpServer}; use dotenv::dotenv; -use std::env; use tera::Tera; mod api; @@ -27,29 +23,22 @@ mod schema; mod session; static SESSION_SIGNING_KEY: &[u8] = &[0; 32]; -const NUM_DB_THREADS: usize = 3; -fn main() { +fn main() -> io::Result<()> { dotenv().ok(); - std::env::set_var("RUST_LOG", "actix_todo=debug,actix_web=info"); + env::set_var("RUST_LOG", "actix_todo=debug,actix_web=info"); env_logger::init(); - // Start the Actix system - let system = actix::System::new("todo-app"); - let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set"); let pool = db::init_pool(&database_url).expect("Failed to create pool"); - let addr = SyncArbiter::start(NUM_DB_THREADS, move || db::DbExecutor(pool.clone())); let app = move || { debug!("Constructing the App"); let templates: Tera = compile_templates!("templates/**/*"); - let session_store = SessionStorage::new( - CookieSessionBackend::signed(SESSION_SIGNING_KEY).secure(false), - ); + let session_store = CookieSession::signed(SESSION_SIGNING_KEY).secure(false); let error_handlers = ErrorHandlers::new() .handler( @@ -59,29 +48,20 @@ fn main() { .handler(http::StatusCode::BAD_REQUEST, api::bad_request) .handler(http::StatusCode::NOT_FOUND, api::not_found); - let static_files = fs::StaticFiles::new("static/") - .expect("failed constructing static files handler"); - - let state = api::AppState { - template: templates, - db: addr.clone(), - }; - - App::with_state(state) - .middleware(Logger::default()) - .middleware(session_store) - .middleware(error_handlers) - .route("/", http::Method::GET, api::index) - .route("/todo", http::Method::POST, api::create) - .resource("/todo/{id}", |r: &mut Resource<_>| { - r.post().with(api::update) - }) - .handler("/static", static_files) + App::new() + .data(templates) + .data(pool.clone()) + .wrap(Logger::default()) + .wrap(session_store) + .wrap(error_handlers) + .service(web::resource("/").route(web::get().to_async(api::index))) + .service(web::resource("/todo").route(web::post().to_async(api::create))) + .service( + web::resource("/todo/{id}").route(web::post().to_async(api::update)), + ) + .service(fs::Files::new("/static", "static/")) }; debug!("Starting server"); - server::new(app).bind("localhost:8088").unwrap().start(); - - // Run actix system, this method actually starts all async processes - let _ = system.run(); + HttpServer::new(app).bind("localhost:8088")?.run() } diff --git a/actix_todo/src/model.rs b/actix_todo/src/model.rs index 3f1afb13..4b80f4a8 100644 --- a/actix_todo/src/model.rs +++ b/actix_todo/src/model.rs @@ -2,8 +2,9 @@ use diesel; use diesel::pg::PgConnection; use diesel::prelude::*; -use schema::{ - tasks, tasks::dsl::{completed as task_completed, tasks as all_tasks}, +use crate::schema::{ + tasks, + tasks::dsl::{completed as task_completed, tasks as all_tasks}, }; #[derive(Debug, Insertable)] diff --git a/actix_todo/src/session.rs b/actix_todo/src/session.rs index c4c6aadb..d1cfc7a4 100644 --- a/actix_todo/src/session.rs +++ b/actix_todo/src/session.rs @@ -1,19 +1,18 @@ +use actix_session::Session; use actix_web::error::Result; -use actix_web::middleware::session::RequestSession; -use actix_web::HttpRequest; const FLASH_KEY: &str = "flash"; -pub fn set_flash(request: &HttpRequest, flash: FlashMessage) -> Result<()> { - request.session().set(FLASH_KEY, flash) +pub fn set_flash(session: &Session, flash: FlashMessage) -> Result<()> { + session.set(FLASH_KEY, flash) } -pub fn get_flash(req: &HttpRequest) -> Result> { - req.session().get::(FLASH_KEY) +pub fn get_flash(session: &Session) -> Result> { + session.get::(FLASH_KEY) } -pub fn clear_flash(req: &HttpRequest) { - req.session().remove(FLASH_KEY); +pub fn clear_flash(session: &Session) { + session.remove(FLASH_KEY); } #[derive(Deserialize, Serialize)] diff --git a/async_db/Cargo.toml b/async_db/Cargo.toml index 55105f57..b7ef39fb 100644 --- a/async_db/Cargo.toml +++ b/async_db/Cargo.toml @@ -2,18 +2,21 @@ name = "async_db" version = "0.1.0" authors = ["Darin Gordon "] +edition = "2018" +workspace = ".." [dependencies] -actix = "0.7" -actix-web = "0.7" +actix-rt = "0.2.2" +actix-web = "1.0.0" dotenv = "0.10" env_logger = "0.5" failure = "0.1.1" futures = "0.1" -num_cpus = "1.8.0" +num_cpus = "1.10.0" r2d2 = "0.8.2" -r2d2_sqlite = "0.5.0" +r2d2_sqlite = "0.8.0" +rusqlite = "0.16" serde = "1.0" serde_json = "1.0" serde_derive = "1.0" diff --git a/async_db/db/setup_db.sh b/async_db/db/setup_db.sh index 41b73f89..1d5abb02 100755 --- a/async_db/db/setup_db.sh +++ b/async_db/db/setup_db.sh @@ -1,3 +1,4 @@ #!/usr/bin/env bash -sqlite3 weather.db < db.sql -sqlite3 -csv weather.db ".import nyc_centralpark_weather.csv nyc_weather" +cd $(dirname "$0") +sqlite3 ../weather.db < db.sql +sqlite3 -csv ../weather.db ".import nyc_centralpark_weather.csv nyc_weather" diff --git a/async_db/src/db.rs b/async_db/src/db.rs index a7903d71..1a0f8658 100644 --- a/async_db/src/db.rs +++ b/async_db/src/db.rs @@ -1,17 +1,15 @@ -use actix::prelude::*; +use actix_web::{web, Error as AWError}; use failure::Error; +use futures::Future; use r2d2; use r2d2_sqlite; +use rusqlite::NO_PARAMS; +use serde_derive::{Deserialize, Serialize}; use std::{thread::sleep, time::Duration}; pub type Pool = r2d2::Pool; pub type Connection = r2d2::PooledConnection; -pub struct DbExecutor(pub Pool); -impl Actor for DbExecutor { - type Context = SyncContext; -} - #[derive(Debug, Serialize, Deserialize)] pub enum WeatherAgg { AnnualAgg { year: i32, total: f64 }, @@ -25,29 +23,24 @@ pub enum Queries { GetTopTenColdestMonths, } -//pub struct GetTopTenHottestYears; -impl Message for Queries { - type Result = Result, Error>; -} -impl Handler for DbExecutor { - type Result = Result, Error>; - - fn handle(&mut self, msg: Queries, _: &mut Self::Context) -> Self::Result { - let conn: Connection = self.0.get()?; - - match msg { - Queries::GetTopTenHottestYears => get_hottest_years(conn), - Queries::GetTopTenColdestYears => get_coldest_years(conn), - Queries::GetTopTenHottestMonths => get_hottest_months(conn), - Queries::GetTopTenColdestMonths => get_coldest_months(conn), - } - } +pub fn execute( + pool: &Pool, + query: Queries, +) -> impl Future, Error = AWError> { + let pool = pool.clone(); + web::block(move || match query { + Queries::GetTopTenHottestYears => get_hottest_years(pool.get()?), + Queries::GetTopTenColdestYears => get_coldest_years(pool.get()?), + Queries::GetTopTenHottestMonths => get_hottest_months(pool.get()?), + Queries::GetTopTenColdestMonths => get_coldest_months(pool.get()?), + }) + .from_err() } fn get_hottest_years(conn: Connection) -> Result, Error> { let stmt = " SELECT cast(strftime('%Y', date) as int) as theyear, - sum(tmax) as total + sum(tmax) as total FROM nyc_weather WHERE tmax <> 'TMAX' GROUP BY theyear @@ -55,7 +48,7 @@ fn get_hottest_years(conn: Connection) -> Result, Error> { let mut prep_stmt = conn.prepare(stmt)?; let annuals = prep_stmt - .query_map(&[], |row| WeatherAgg::AnnualAgg { + .query_map(NO_PARAMS, |row| WeatherAgg::AnnualAgg { year: row.get(0), total: row.get(1), }) @@ -65,7 +58,7 @@ fn get_hottest_years(conn: Connection) -> Result, Error> { .collect::>()) })?; - sleep(Duration::from_secs(2)); + sleep(Duration::from_secs(2)); //see comments at top of main.rs Ok(annuals) } @@ -73,7 +66,7 @@ fn get_hottest_years(conn: Connection) -> Result, Error> { fn get_coldest_years(conn: Connection) -> Result, Error> { let stmt = " SELECT cast(strftime('%Y', date) as int) as theyear, - sum(tmax) as total + sum(tmax) as total FROM nyc_weather WHERE tmax <> 'TMAX' GROUP BY theyear @@ -81,7 +74,7 @@ fn get_coldest_years(conn: Connection) -> Result, Error> { let mut prep_stmt = conn.prepare(stmt)?; let annuals = prep_stmt - .query_map(&[], |row| WeatherAgg::AnnualAgg { + .query_map(NO_PARAMS, |row| WeatherAgg::AnnualAgg { year: row.get(0), total: row.get(1), }) @@ -91,15 +84,15 @@ fn get_coldest_years(conn: Connection) -> Result, Error> { .collect::>()) })?; - sleep(Duration::from_secs(2)); + sleep(Duration::from_secs(2)); //see comments at top of main.rs Ok(annuals) } fn get_hottest_months(conn: Connection) -> Result, Error> { let stmt = "SELECT cast(strftime('%Y', date) as int) as theyear, - cast(strftime('%m', date) as int) as themonth, - sum(tmax) as total + cast(strftime('%m', date) as int) as themonth, + sum(tmax) as total FROM nyc_weather WHERE tmax <> 'TMAX' GROUP BY theyear, themonth @@ -107,7 +100,7 @@ fn get_hottest_months(conn: Connection) -> Result, Error> { let mut prep_stmt = conn.prepare(stmt)?; let annuals = prep_stmt - .query_map(&[], |row| WeatherAgg::MonthAgg { + .query_map(NO_PARAMS, |row| WeatherAgg::MonthAgg { year: row.get(0), month: row.get(1), total: row.get(2), @@ -118,14 +111,14 @@ fn get_hottest_months(conn: Connection) -> Result, Error> { .collect::>()) })?; - sleep(Duration::from_secs(2)); + sleep(Duration::from_secs(2)); //see comments at top of main.rs Ok(annuals) } fn get_coldest_months(conn: Connection) -> Result, Error> { let stmt = "SELECT cast(strftime('%Y', date) as int) as theyear, - cast(strftime('%m', date) as int) as themonth, - sum(tmax) as total + cast(strftime('%m', date) as int) as themonth, + sum(tmax) as total FROM nyc_weather WHERE tmax <> 'TMAX' GROUP BY theyear, themonth @@ -133,7 +126,7 @@ fn get_coldest_months(conn: Connection) -> Result, Error> { let mut prep_stmt = conn.prepare(stmt)?; let annuals = prep_stmt - .query_map(&[], |row| WeatherAgg::MonthAgg { + .query_map(NO_PARAMS, |row| WeatherAgg::MonthAgg { year: row.get(0), month: row.get(1), total: row.get(2), @@ -144,6 +137,6 @@ fn get_coldest_months(conn: Connection) -> Result, Error> { .collect::>()) })?; - sleep(Duration::from_secs(2)); + sleep(Duration::from_secs(2)); //see comments at top of main.rs Ok(annuals) } diff --git a/async_db/src/main.rs b/async_db/src/main.rs index 42dcb1bd..78ab4cc7 100644 --- a/async_db/src/main.rs +++ b/async_db/src/main.rs @@ -8,122 +8,92 @@ This project illustrates two examples: 2. An asynchronous handler that executes 4 queries in *parallel*, collecting the results and returning them as a single serialized json object -*/ + Note: The use of sleep(Duration::from_secs(2)); in db.rs is to make performance + improvement with parallelism more obvious. + */ +use std::io; -extern crate actix; -extern crate actix_web; -extern crate env_logger; -extern crate failure; -extern crate futures; -extern crate num_cpus; -extern crate r2d2; -extern crate r2d2_sqlite; -extern crate serde; -#[macro_use] -extern crate serde_derive; -extern crate serde_json; - -use actix::prelude::*; -use actix_web::{ - http, middleware, server, App, AsyncResponder, Error as AWError, FutureResponse, - HttpResponse, State, -}; +use actix_web::{middleware, web, App, Error as AWError, HttpResponse, HttpServer}; use futures::future::{join_all, ok as fut_ok, Future}; +use r2d2_sqlite; use r2d2_sqlite::SqliteConnectionManager; mod db; -use db::{DbExecutor, Pool, Queries, WeatherAgg}; - -/// State with DbExecutor address -struct AppState { - db: Addr, -} +use db::{Pool, Queries, WeatherAgg}; /// Version 1: Calls 4 queries in sequential order, as an asynchronous handler -fn asyncio_weather(state: State) -> FutureResponse { +fn asyncio_weather( + db: web::Data, +) -> impl Future { let mut result: Vec> = vec![]; - state - .db - .send(Queries::GetTopTenHottestYears) + db::execute(&db, Queries::GetTopTenHottestYears) .from_err() .and_then(move |res| { - result.push(res.unwrap()); - state - .db - .send(Queries::GetTopTenColdestYears) + result.push(res); + db::execute(&db, Queries::GetTopTenColdestYears) .from_err() .and_then(move |res| { - result.push(res.unwrap()); - state - .db - .send(Queries::GetTopTenHottestMonths) + result.push(res); + db::execute(&db, Queries::GetTopTenHottestMonths) .from_err() .and_then(move |res| { - result.push(res.unwrap()); - state - .db - .send(Queries::GetTopTenColdestMonths) + result.push(res); + db::execute(&db, Queries::GetTopTenColdestMonths) .from_err() .and_then(move |res| { - result.push(res.unwrap()); + result.push(res); fut_ok(result) }) }) }) }) .and_then(|res| Ok(HttpResponse::Ok().json(res))) - .responder() } /// Version 2: Calls 4 queries in parallel, as an asynchronous handler /// Returning Error types turn into None values in the response -fn parallel_weather(state: State) -> FutureResponse { +fn parallel_weather( + db: web::Data, +) -> impl Future { let fut_result = vec![ - Box::new(state.db.send(Queries::GetTopTenHottestYears)), - Box::new(state.db.send(Queries::GetTopTenColdestYears)), - Box::new(state.db.send(Queries::GetTopTenHottestMonths)), - Box::new(state.db.send(Queries::GetTopTenColdestMonths)), + Box::new(db::execute(&db, Queries::GetTopTenHottestYears)), + Box::new(db::execute(&db, Queries::GetTopTenColdestYears)), + Box::new(db::execute(&db, Queries::GetTopTenHottestMonths)), + Box::new(db::execute(&db, Queries::GetTopTenColdestMonths)), ]; join_all(fut_result) .map_err(AWError::from) - .and_then(|result| { - let res: Vec>> = - result.into_iter().map(|x| x.ok()).collect(); - - Ok(HttpResponse::Ok().json(res)) - }) - .responder() + .map(|result| HttpResponse::Ok().json(result)) } -fn main() { - ::std::env::set_var("RUST_LOG", "actix_web=info"); +fn main() -> io::Result<()> { + std::env::set_var("RUST_LOG", "actix_web=info"); env_logger::init(); - let sys = actix::System::new("parallel_db_example"); + let sys = actix_rt::System::new("parallel_db_example"); // Start N db executor actors (N = number of cores avail) - let manager = SqliteConnectionManager::file("weather.db"); let pool = Pool::new(manager).unwrap(); - let addr = SyncArbiter::start(num_cpus::get(), move || DbExecutor(pool.clone())); - // Start http server - server::new(move || { - App::with_state(AppState{db: addr.clone()}) - // enable logger - .middleware(middleware::Logger::default()) - .resource("/asyncio_weather", |r| - r.method(http::Method::GET) - .with(asyncio_weather)) - .resource("/parallel_weather", |r| - r.method(http::Method::GET) - .with(parallel_weather)) - }).bind("127.0.0.1:8080") - .unwrap() - .start(); + HttpServer::new(move || { + App::new() + .data(pool.clone()) + .wrap(middleware::Logger::default()) + .service( + web::resource("/asyncio_weather") + .route(web::get().to_async(asyncio_weather)), + ) + .service( + web::resource("/parallel_weather") + .route(web::get().to_async(parallel_weather)), + ) + }) + .bind("127.0.0.1:8080")? + .start(); println!("Started http server: 127.0.0.1:8080"); - let _ = sys.run(); + sys.run() } diff --git a/async_ex1/Cargo.toml b/async_ex1/Cargo.toml index 1c30e0f9..e5b97163 100644 --- a/async_ex1/Cargo.toml +++ b/async_ex1/Cargo.toml @@ -2,10 +2,12 @@ name = "awc_examples" version = "0.1.0" authors = ["dowwie "] +edition = "2018" +workspace = ".." [dependencies] -actix = "0.7" -actix-web = { version="0.7.3", features=["rust-tls"] } +actix-rt = "0.2.2" +actix-web = { version="1.0.0", features=["ssl"] } futures = "0.1" serde = "1.0.43" diff --git a/async_ex1/README.md b/async_ex1/README.md index 11bbbeed..1b446fc8 100644 --- a/async_ex1/README.md +++ b/async_ex1/README.md @@ -16,5 +16,4 @@ Actix-web features illustrated here include: Example query from the command line using httpie: - ```http post 127.0.0.1:8088/something id=1 name=JohnDoe``` - + ```echo '{"id":"1", "name": "JohnDoe"}' | http 127.0.0.1:8080/something``` diff --git a/async_ex1/src/main.rs b/async_ex1/src/main.rs index a3d5a028..25fa3bc5 100644 --- a/async_ex1/src/main.rs +++ b/async_ex1/src/main.rs @@ -10,30 +10,23 @@ // 2. validating user-submitted parameters using the 'validator' crate // 2. actix-web client features: // - POSTing json body -// 3. chaining futures into a single response used by an asynch endpoint -// -// There are 2 versions in this example, one that uses Boxed Futures and the -// other that uses Impl Future, available since rustc v1.26. +// 3. chaining futures into a single response used by an async endpoint -extern crate actix; -extern crate actix_web; -extern crate serde; -#[macro_use] -extern crate serde_derive; -extern crate serde_json; #[macro_use] extern crate validator_derive; -extern crate env_logger; -extern crate futures; -extern crate validator; +#[macro_use] +extern crate serde_derive; + +use std::collections::HashMap; +use std::io; use actix_web::{ - client, http::Method, server, App, AsyncResponder, Error, HttpMessage, HttpResponse, - Json, + client::Client, + error::ErrorBadRequest, + web::{self, BytesMut}, + App, Error, HttpResponse, HttpServer, }; -use futures::{future::ok as fut_ok, Future}; -use std::collections::HashMap; -use std::time::Duration; +use futures::{Future, Stream}; use validator::Validate; #[derive(Debug, Validate, Deserialize, Serialize)] @@ -56,99 +49,57 @@ struct HttpBinResponse { url: String, } -// ----------------------------------------------------------------------- -// v1 uses Boxed Futures, which were the only option prior to rustc v1.26 -// ----------------------------------------------------------------------- - -/// post json to httpbin, get it back in the response body, return deserialized -fn step_x_v1(data: SomeData) -> Box> { - Box::new( - client::ClientRequest::post("https://httpbin.org/post") - .json(data).unwrap() - .send() - .conn_timeout(Duration::from_secs(10)) - .map_err(Error::from) // <- convert SendRequestError to an Error - .and_then( - |resp| resp.body() // <- this is MessageBody type, resolves to complete body - .from_err() // <- convert PayloadError to an Error - .and_then(|body| { - let resp: HttpBinResponse = serde_json::from_slice(&body).unwrap(); - fut_ok(resp.json) - }) - ), - ) -} - -fn create_something_v1( - some_data: Json, -) -> Box> { - step_x_v1(some_data.into_inner()) - .and_then(|some_data_2| { - step_x_v1(some_data_2).and_then(|some_data_3| { - step_x_v1(some_data_3).and_then(|d| { - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(serde_json::to_string(&d).unwrap()) - .into()) +/// validate data, post json to httpbin, get it back in the response body, return deserialized +fn step_x( + data: SomeData, + client: &Client, +) -> impl Future { + let validation = futures::future::result(data.validate()).map_err(ErrorBadRequest); + let post_response = client + .post("https://httpbin.org/post") + .send_json(&data) + .map_err(Error::from) // <- convert SendRequestError to an Error + .and_then(|resp| { + resp.from_err() + .fold(BytesMut::new(), |mut acc, chunk| { + acc.extend_from_slice(&chunk); + Ok::<_, Error>(acc) }) - }) - }) - .responder() -} - -// --------------------------------------------------------------- -// v2 uses impl Future, available as of rustc v1.26 -// --------------------------------------------------------------- - -/// post json to httpbin, get it back in the response body, return deserialized -fn step_x_v2(data: SomeData) -> impl Future { - client::ClientRequest::post("https://httpbin.org/post") - .json(data).unwrap() - .send() - .conn_timeout(Duration::from_secs(10)) - .map_err(Error::from) // <- convert SendRequestError to an Error - .and_then( - |resp| resp.body() // <- this is MessageBody type, resolves to complete body - .from_err() // <- convert PayloadError to an Error - .and_then(|body| { - let resp: HttpBinResponse = serde_json::from_slice(&body).unwrap(); - fut_ok(resp.json) + .map(|body| { + let body: HttpBinResponse = serde_json::from_slice(&body).unwrap(); + body.json }) - ) + }); + + validation.and_then(|_| post_response) } -fn create_something_v2( - some_data: Json, +fn create_something( + some_data: web::Json, + client: web::Data, ) -> impl Future { - step_x_v2(some_data.into_inner()).and_then(|some_data_2| { - step_x_v2(some_data_2).and_then(|some_data_3| { - step_x_v2(some_data_3).and_then(|d| { + step_x(some_data.into_inner(), &client).and_then(move |some_data_2| { + step_x(some_data_2, &client).and_then(move |some_data_3| { + step_x(some_data_3, &client).and_then(|d| { Ok(HttpResponse::Ok() .content_type("application/json") - .body(serde_json::to_string(&d).unwrap()) - .into()) + .body(serde_json::to_string(&d).unwrap())) }) }) }) } -fn main() { - ::std::env::set_var("RUST_LOG", "actix_web=info"); +fn main() -> io::Result<()> { + std::env::set_var("RUST_LOG", "actix_web=info"); env_logger::init(); - let sys = actix::System::new("asyncio_example"); + let endpoint = "127.0.0.1:8080"; - server::new(move || { - App::new() - .resource("/something_v1", |r| { - r.method(Method::POST).with(create_something_v1) - }) - .resource("/something_v2", |r| { - r.method(Method::POST).with_async(create_something_v2) - }) - }).bind("127.0.0.1:8088") - .unwrap() - .start(); - - println!("Started http server: 127.0.0.1:8088"); - let _ = sys.run(); + println!("Starting server at: {:?}", endpoint); + HttpServer::new(|| { + App::new().data(Client::default()).service( + web::resource("/something").route(web::post().to_async(create_something)), + ) + }) + .bind(endpoint)? + .run() } diff --git a/async_ex2/Cargo.toml b/async_ex2/Cargo.toml new file mode 100644 index 00000000..c1492b9f --- /dev/null +++ b/async_ex2/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "async_ex2" +version = "0.1.0" +authors = ["dowwie "] +edition = "2018" +workspace = ".." + +[dependencies] +actix-rt = "0.2.2" +actix-web = { version="1.0.0", features=["ssl"] } +actix-multipart = "0.1.1" +actix-service = "0.4.1" +bytes = "0.4.12" +env_logger = "0.6.1" +futures = "0.1" +serde = { version = "^1.0", features = ["derive"] } +serde_derive = "1.0.90" +serde_json = "1.0.39" +time = "0.1.42" +validator = "0.8.0" +validator_derive = "0.8.0" diff --git a/async_ex2/README.md b/async_ex2/README.md new file mode 100644 index 00000000..68374f9a --- /dev/null +++ b/async_ex2/README.md @@ -0,0 +1,3 @@ +This example illustrates how to use nested resource registration through application-level configuration. +The endpoints do nothing. + diff --git a/async_ex2/src/appconfig.rs b/async_ex2/src/appconfig.rs new file mode 100644 index 00000000..f007003a --- /dev/null +++ b/async_ex2/src/appconfig.rs @@ -0,0 +1,36 @@ +use actix_web::web; + +use crate::handlers::{parts, products}; + +pub fn config_app(cfg: &mut web::ServiceConfig) { + // domain includes: /products/{product_id}/parts/{part_id} + cfg.service( + web::scope("/products") + .service( + web::resource("") + .route(web::get().to_async(products::get_products)) + .route(web::post().to_async(products::add_product)), + ) + .service( + web::scope("/{product_id}") + .service( + web::resource("") + .route(web::get().to_async(products::get_product_detail)) + .route(web::delete().to_async(products::remove_product)), + ) + .service( + web::scope("/parts") + .service( + web::resource("") + .route(web::get().to_async(parts::get_parts)) + .route(web::post().to_async(parts::add_part)), + ) + .service( + web::resource("/{part_id}") + .route(web::get().to_async(parts::get_part_detail)) + .route(web::delete().to_async(parts::remove_part)), + ), + ), + ), + ); +} diff --git a/async_ex2/src/bin/main.rs b/async_ex2/src/bin/main.rs new file mode 100644 index 00000000..f5ee4fb9 --- /dev/null +++ b/async_ex2/src/bin/main.rs @@ -0,0 +1,16 @@ +use actix_web::{middleware, App, HttpServer}; + +use async_ex2::appconfig::config_app; + +fn main() -> std::io::Result<()> { + std::env::set_var("RUST_LOG", "actix_server=info,actix_web=info"); + env_logger::init(); + + HttpServer::new(|| { + App::new() + .configure(config_app) + .wrap(middleware::Logger::default()) + }) + .bind("127.0.0.1:8080")? + .run() +} diff --git a/async_ex2/src/common.rs b/async_ex2/src/common.rs new file mode 100644 index 00000000..4c100617 --- /dev/null +++ b/async_ex2/src/common.rs @@ -0,0 +1,15 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Deserialize, Serialize)] +pub struct Product { + id: Option, + product_type: Option, + name: Option, +} + +#[derive(Deserialize, Serialize)] +pub struct Part { + id: Option, + part_type: Option, + name: Option, +} diff --git a/async_ex2/src/handlers/mod.rs b/async_ex2/src/handlers/mod.rs new file mode 100644 index 00000000..299c561b --- /dev/null +++ b/async_ex2/src/handlers/mod.rs @@ -0,0 +1,2 @@ +pub mod parts; +pub mod products; diff --git a/async_ex2/src/handlers/parts.rs b/async_ex2/src/handlers/parts.rs new file mode 100644 index 00000000..3e3874f4 --- /dev/null +++ b/async_ex2/src/handlers/parts.rs @@ -0,0 +1,28 @@ +use actix_web::{web, Error, HttpResponse}; +use futures::{future::ok as fut_ok, Future}; + +use crate::common::{Part, Product}; + +pub fn get_parts( + query: web::Query>, +) -> impl Future { + fut_ok(HttpResponse::Ok().finish()) +} + +pub fn add_part( + new_part: web::Json, +) -> impl Future { + fut_ok(HttpResponse::Ok().finish()) +} + +pub fn get_part_detail( + id: web::Path, +) -> impl Future { + fut_ok(HttpResponse::Ok().finish()) +} + +pub fn remove_part( + id: web::Path, +) -> impl Future { + fut_ok(HttpResponse::Ok().finish()) +} diff --git a/async_ex2/src/handlers/products.rs b/async_ex2/src/handlers/products.rs new file mode 100644 index 00000000..36b774a4 --- /dev/null +++ b/async_ex2/src/handlers/products.rs @@ -0,0 +1,56 @@ +use actix_web::{web, Error, HttpResponse}; +use futures::{future::ok as fut_ok, Future}; + +use crate::common::{Part, Product}; + +pub fn get_products( + query: web::Query>, +) -> impl Future { + fut_ok(HttpResponse::Ok().finish()) +} + +pub fn add_product( + new_product: web::Json, +) -> impl Future { + fut_ok(HttpResponse::Ok().finish()) +} + +pub fn get_product_detail( + id: web::Path, +) -> impl Future { + fut_ok(HttpResponse::Ok().finish()) +} + +pub fn remove_product( + id: web::Path, +) -> impl Future { + fut_ok(HttpResponse::Ok().finish()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::appconfig::config_app; + use actix_service::Service; + use actix_web::{ + http::{header, StatusCode}, + test, web, App, HttpRequest, HttpResponse, + }; + + #[test] + fn test_add_product() { + let mut app = test::init_service(App::new().configure(config_app)); + + let payload = r#"{"id":12345,"product_type":"fancy","name":"test"}"#.as_bytes(); + + let req = test::TestRequest::post() + .uri("/products") + .header(header::CONTENT_TYPE, "application/json") + .set_payload(payload) + .to_request(); + + let resp = test::block_on(app.call(req)).unwrap(); + + assert_eq!(resp.status(), StatusCode::OK); + } +} diff --git a/async_ex2/src/lib.rs b/async_ex2/src/lib.rs new file mode 100644 index 00000000..1e9c00b3 --- /dev/null +++ b/async_ex2/src/lib.rs @@ -0,0 +1,3 @@ +pub mod appconfig; +pub mod common; +pub mod handlers; diff --git a/basics/Cargo.toml b/basics/Cargo.toml index 2db8e75a..dd46235f 100644 --- a/basics/Cargo.toml +++ b/basics/Cargo.toml @@ -1,13 +1,16 @@ [package] name = "basics" -version = "0.1.0" +version = "1.0.0" authors = ["Nikolay Kim "] -workspace = "../" +workspace = ".." +edition = "2018" [dependencies] -actix = "0.7" -actix-web = "0.7" +actix-rt = "0.2.2" +actix-web = "1.0.0" +actix-files = "0.1.1" +actix-session = "0.2.0" -futures = "0.1" +futures = "0.1.25" env_logger = "0.5" bytes = "0.4" diff --git a/basics/src/main.rs b/basics/src/main.rs index b4fbe9e9..ab98ecba 100644 --- a/basics/src/main.rs +++ b/basics/src/main.rs @@ -1,43 +1,39 @@ -#![allow(unused_variables)] -#![cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))] - -extern crate actix; +#[macro_use] extern crate actix_web; -extern crate bytes; -extern crate env_logger; -extern crate futures; -use bytes::Bytes; -use futures::sync::mpsc; -use futures::Stream; - -use actix_web::http::{header, Method, StatusCode}; -use actix_web::middleware::session::{self, RequestSession}; -use actix_web::{ - error, fs, middleware, pred, server, App, Error, HttpRequest, HttpResponse, Path, - Result, -}; -use futures::future::{result, FutureResult}; use std::{env, io}; +use actix_files as fs; +use actix_session::{CookieSession, Session}; +use actix_web::http::{header, Method, StatusCode}; +use actix_web::{ + error, guard, middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer, + Result, +}; +use bytes::Bytes; +use futures::unsync::mpsc; +use futures::{future::ok, Future, Stream}; + /// favicon handler -fn favicon(req: &HttpRequest) -> Result { +#[get("/favicon")] +fn favicon() -> Result { Ok(fs::NamedFile::open("static/favicon.ico")?) } /// simple index handler -fn welcome(req: &HttpRequest) -> Result { +#[get("/welcome")] +fn welcome(session: Session, req: HttpRequest) -> Result { println!("{:?}", req); // session let mut counter = 1; - if let Some(count) = req.session().get::("counter")? { + if let Some(count) = session.get::("counter")? { println!("SESSION value: {}", count); counter = count + 1; } // set counter to session - req.session().set("counter", counter)?; + session.set("counter", counter)?; // response Ok(HttpResponse::build(StatusCode::OK) @@ -46,97 +42,103 @@ fn welcome(req: &HttpRequest) -> Result { } /// 404 handler -fn p404(req: &HttpRequest) -> Result { +fn p404() -> Result { Ok(fs::NamedFile::open("static/404.html")?.set_status_code(StatusCode::NOT_FOUND)) } /// async handler -fn index_async(req: &HttpRequest) -> FutureResult { +fn index_async(req: HttpRequest) -> impl Future { println!("{:?}", req); - result(Ok(HttpResponse::Ok().content_type("text/html").body( - format!("Hello {}!", req.match_info().get("name").unwrap()), - ))) + ok(HttpResponse::Ok() + .content_type("text/html") + .body(format!("Hello {}!", req.match_info().get("name").unwrap()))) } /// async body -fn index_async_body(path: Path) -> HttpResponse { +fn index_async_body(path: web::Path) -> HttpResponse { let text = format!("Hello {}!", *path); let (tx, rx_body) = mpsc::unbounded(); let _ = tx.unbounded_send(Bytes::from(text.as_bytes())); HttpResponse::Ok() - .streaming(rx_body.map_err(|e| error::ErrorBadRequest("bad request"))) + .streaming(rx_body.map_err(|_| error::ErrorBadRequest("bad request"))) } /// handler with path parameters like `/user/{name}/` -fn with_param(req: &HttpRequest) -> HttpResponse { +fn with_param(req: HttpRequest, path: web::Path<(String,)>) -> HttpResponse { println!("{:?}", req); HttpResponse::Ok() .content_type("text/plain") - .body(format!("Hello {}!", req.match_info().get("name").unwrap())) + .body(format!("Hello {}!", path.0)) } -fn main() { +fn main() -> io::Result<()> { env::set_var("RUST_LOG", "actix_web=debug"); - env::set_var("RUST_BACKTRACE", "1"); env_logger::init(); - let sys = actix::System::new("basic-example"); + let sys = actix_rt::System::new("basic-example"); - let addr = server::new( - || App::new() - // enable logger - .middleware(middleware::Logger::default()) + HttpServer::new(|| { + App::new() // cookie session middleware - .middleware(session::SessionStorage::new( - session::CookieSessionBackend::signed(&[0; 32]).secure(false) - )) + .wrap(CookieSession::signed(&[0; 32]).secure(false)) + // enable logger - always register actix-web Logger middleware last + .wrap(middleware::Logger::default()) // register favicon - .resource("/favicon", |r| r.f(favicon)) + .service(favicon) // register simple route, handle all methods - .resource("/welcome", |r| r.f(welcome)) + .service(welcome) // with path parameters - .resource("/user/{name}", |r| r.method(Method::GET).f(with_param)) + .service(web::resource("/user/{name}").route(web::get().to(with_param))) // async handler - .resource("/async/{name}", |r| r.method(Method::GET).a(index_async)) + .service( + web::resource("/async/{name}").route(web::get().to_async(index_async)), + ) // async handler - .resource("/async-body/{name}", |r| r.method(Method::GET).with(index_async_body)) - .resource("/test", |r| r.f(|req| { - match *req.method() { + .service( + web::resource("/async-body/{name}") + .route(web::get().to(index_async_body)), + ) + .service( + web::resource("/test").to(|req: HttpRequest| match *req.method() { Method::GET => HttpResponse::Ok(), Method::POST => HttpResponse::MethodNotAllowed(), _ => HttpResponse::NotFound(), - } - })) - .resource("/error", |r| r.f(|req| { + }), + ) + .service(web::resource("/error").to(|| { error::InternalError::new( - io::Error::new(io::ErrorKind::Other, "test"), StatusCode::INTERNAL_SERVER_ERROR) + io::Error::new(io::ErrorKind::Other, "test"), + StatusCode::INTERNAL_SERVER_ERROR, + ) })) // static files - .handler("/static", fs::StaticFiles::new("static").unwrap()) + .service(fs::Files::new("/static", "static").show_files_listing()) // redirect - .resource("/", |r| r.method(Method::GET).f(|req| { + .service(web::resource("/").route(web::get().to(|req: HttpRequest| { println!("{:?}", req); HttpResponse::Found() .header(header::LOCATION, "static/welcome.html") .finish() - })) + }))) // default - .default_resource(|r| { + .default_service( // 404 for GET request - r.method(Method::GET).f(p404); - - // all requests that are not `GET` - r.route().filter(pred::Not(pred::Get())).f( - |req| HttpResponse::MethodNotAllowed()); - })) - - .bind("127.0.0.1:8080").expect("Can not bind to 127.0.0.1:8080") - .shutdown_timeout(0) // <- Set shutdown timeout to 0 seconds (default 60s) - .start(); + web::resource("") + .route(web::get().to(p404)) + // all requests that are not `GET` + .route( + web::route() + .guard(guard::Not(guard::Get())) + .to(HttpResponse::MethodNotAllowed), + ), + ) + }) + .bind("127.0.0.1:8080")? + .start(); println!("Starting http server: 127.0.0.1:8080"); - let _ = sys.run(); + sys.run() } diff --git a/basics/static/404.html b/basics/static/404.html index c87e1d9f..e140a1d9 100644 --- a/basics/static/404.html +++ b/basics/static/404.html @@ -1,7 +1,14 @@ -actix - basics - - - back to home -

404

- - + + + + + actix - basics + + + + + back to home +

404

+ + + \ No newline at end of file diff --git a/basics/static/welcome.html b/basics/static/welcome.html index 48bf3cfe..a5c76c81 100644 --- a/basics/static/welcome.html +++ b/basics/static/welcome.html @@ -1,6 +1,13 @@ -actix - basics - - -

Welcome

- - + + + + + actix - basics + + + + +

Welcome

+ + + \ No newline at end of file diff --git a/cookie-auth-full/Cargo.toml b/cookie-auth-full/Cargo.toml deleted file mode 100644 index 87e6df95..00000000 --- a/cookie-auth-full/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "cookie-auth-full" -version = "0.1.0" -authors = ["Nikolay Kim "] -workspace = "../" - -[dependencies] -actix = "0.7" -actix-web = "0.7" - -cookie = { version="0.11", features=["percent-encode", "secure"] } -futures = "0.1" -time = "0.1" -env_logger = "0.5" diff --git a/cookie-auth-full/src/auth.rs b/cookie-auth-full/src/auth.rs deleted file mode 100644 index 56ec54e4..00000000 --- a/cookie-auth-full/src/auth.rs +++ /dev/null @@ -1,274 +0,0 @@ -#![allow(dead_code)] -use std::rc::Rc; - -use cookie::{Cookie, CookieJar, Key}; -use futures::future::{err as FutErr, ok as FutOk, FutureResult}; -use futures::Future; -use time::Duration; - -use actix_web::http::header::{self, HeaderValue}; -use actix_web::middleware::{Middleware, Response, Started}; -use actix_web::{Error, HttpRequest, HttpResponse, Result}; - -/// Trait provides identity service for the request. -pub trait RequestIdentity { - /// Return the claimed identity of the user associated request or - /// ``None`` if no identity can be found associated with the request. - fn identity(&self) -> Option; - - /// Remember identity. - fn remember(&self, identity: String); - - /// This method is used to 'forget' the current identity on subsequent - /// requests. - fn forget(&self); -} - -impl RequestIdentity for HttpRequest { - fn identity(&self) -> Option { - if let Some(id) = self.extensions().get::() { - return id.0.identity().map(|s| s.to_owned()); - } - None - } - - fn remember(&self, identity: String) { - if let Some(id) = self.extensions_mut().get_mut::() { - return id.0.as_mut().remember(identity); - } - } - - fn forget(&self) { - if let Some(id) = self.extensions_mut().get_mut::() { - return id.0.forget(); - } - } -} - -/// An identity -pub trait Identity: 'static { - fn identity(&self) -> Option<&str>; - - fn remember(&mut self, key: String); - - fn forget(&mut self); - - /// Write session to storage backend. - fn write(&mut self, resp: HttpResponse) -> Result; -} - -/// Identity policy definition. -pub trait IdentityPolicy: Sized + 'static { - type Identity: Identity; - type Future: Future; - - /// Parse the session from request and load data from a service identity. - fn from_request(&self, request: &mut HttpRequest) -> Self::Future; -} - -/// Middleware that implements identity service -pub struct IdentityService { - backend: T, -} - -impl IdentityService { - /// Create new identity service with specified backend. - pub fn new(backend: T) -> Self { - IdentityService { backend } - } -} - -struct IdentityBox(Box); - -#[doc(hidden)] -unsafe impl Send for IdentityBox {} -#[doc(hidden)] -unsafe impl Sync for IdentityBox {} - -impl> Middleware for IdentityService { - fn start(&self, req: &HttpRequest) -> Result { - let mut req = req.clone(); - - let fut = self - .backend - .from_request(&mut req) - .then(move |res| match res { - Ok(id) => { - req.extensions_mut().insert(IdentityBox(Box::new(id))); - FutOk(None) - } - Err(err) => FutErr(err), - }); - Ok(Started::Future(Box::new(fut))) - } - - fn response(&self, req: &HttpRequest, resp: HttpResponse) -> Result { - if let Some(mut id) = req.extensions_mut().remove::() { - id.0.write(resp) - } else { - Ok(Response::Done(resp)) - } - } -} - -/// Identity that uses private cookies as identity storage -pub struct CookieIdentity { - changed: bool, - identity: Option, - inner: Rc, -} - -impl Identity for CookieIdentity { - fn identity(&self) -> Option<&str> { - self.identity.as_ref().map(|s| s.as_ref()) - } - - fn remember(&mut self, value: String) { - self.changed = true; - self.identity = Some(value); - } - - fn forget(&mut self) { - self.changed = true; - self.identity = None; - } - - fn write(&mut self, mut resp: HttpResponse) -> Result { - if self.changed { - let _ = self.inner.set_cookie(&mut resp, self.identity.take()); - } - Ok(Response::Done(resp)) - } -} - -struct CookieIdentityInner { - key: Key, - name: String, - path: String, - domain: Option, - secure: bool, - max_age: Option, -} - -impl CookieIdentityInner { - fn new(key: &[u8]) -> CookieIdentityInner { - CookieIdentityInner { - key: Key::from_master(key), - name: "actix-identity".to_owned(), - path: "/".to_owned(), - domain: None, - secure: true, - max_age: None, - } - } - - fn set_cookie(&self, resp: &mut HttpResponse, id: Option) -> Result<()> { - let some = id.is_some(); - { - let id = id.unwrap_or_else(|| String::new()); - let mut cookie = Cookie::new(self.name.clone(), id); - cookie.set_path(self.path.clone()); - cookie.set_secure(self.secure); - cookie.set_http_only(true); - - if let Some(ref domain) = self.domain { - cookie.set_domain(domain.clone()); - } - - if let Some(max_age) = self.max_age { - cookie.set_max_age(max_age); - } - - let mut jar = CookieJar::new(); - if some { - jar.private(&self.key).add(cookie); - } else { - jar.add_original(cookie.clone()); - jar.private(&self.key).remove(cookie); - } - - for cookie in jar.delta() { - let val = HeaderValue::from_str(&cookie.to_string())?; - resp.headers_mut().append(header::SET_COOKIE, val); - } - } - - Ok(()) - } - - fn load(&self, req: &mut HttpRequest) -> Option { - if let Ok(cookies) = req.cookies() { - for cookie in cookies.iter() { - if cookie.name() == self.name { - let mut jar = CookieJar::new(); - jar.add_original(cookie.clone()); - - let cookie_opt = jar.private(&self.key).get(&self.name); - if let Some(cookie) = cookie_opt { - return Some(cookie.value().into()); - } - } - } - } - None - } -} - -/// Use cookies for request identity. -pub struct CookieIdentityPolicy(Rc); - -impl CookieIdentityPolicy { - /// Construct new `CookieIdentityPolicy` instance. - /// - /// Panics if key length is less than 32 bytes. - pub fn new(key: &[u8]) -> CookieIdentityPolicy { - CookieIdentityPolicy(Rc::new(CookieIdentityInner::new(key))) - } - - /// Sets the `path` field in the session cookie being built. - pub fn path>(mut self, value: S) -> CookieIdentityPolicy { - Rc::get_mut(&mut self.0).unwrap().path = value.into(); - self - } - - /// Sets the `name` field in the session cookie being built. - pub fn name>(mut self, value: S) -> CookieIdentityPolicy { - Rc::get_mut(&mut self.0).unwrap().name = value.into(); - self - } - - /// Sets the `domain` field in the session cookie being built. - pub fn domain>(mut self, value: S) -> CookieIdentityPolicy { - Rc::get_mut(&mut self.0).unwrap().domain = Some(value.into()); - self - } - - /// Sets the `secure` field in the session cookie being built. - /// - /// If the `secure` field is set, a cookie will only be transmitted when the - /// connection is secure - i.e. `https` - pub fn secure(mut self, value: bool) -> CookieIdentityPolicy { - Rc::get_mut(&mut self.0).unwrap().secure = value; - self - } - - /// Sets the `max-age` field in the session cookie being built. - pub fn max_age(mut self, value: Duration) -> CookieIdentityPolicy { - Rc::get_mut(&mut self.0).unwrap().max_age = Some(value); - self - } -} - -impl IdentityPolicy for CookieIdentityPolicy { - type Identity = CookieIdentity; - type Future = FutureResult; - - fn from_request(&self, req: &mut HttpRequest) -> Self::Future { - let identity = self.0.load(req); - FutOk(CookieIdentity { - identity, - changed: false, - inner: Rc::clone(&self.0), - }) - } -} diff --git a/cookie-auth-full/src/main.rs b/cookie-auth-full/src/main.rs deleted file mode 100644 index f7a0c09c..00000000 --- a/cookie-auth-full/src/main.rs +++ /dev/null @@ -1,49 +0,0 @@ -extern crate actix; -extern crate actix_web; -extern crate cookie; -extern crate env_logger; -extern crate futures; -extern crate time; - -use actix_web::{middleware, server, App, HttpRequest, HttpResponse}; - -mod auth; -use auth::{CookieIdentityPolicy, IdentityService, RequestIdentity}; - -fn index(req: &HttpRequest) -> String { - format!("Hello {}", req.identity().unwrap_or("Anonymous".to_owned())) -} - -fn login(req: &HttpRequest) -> HttpResponse { - req.remember("user1".to_owned()); - HttpResponse::Found().header("location", "/").finish() -} - -fn logout(req: &HttpRequest) -> HttpResponse { - req.forget(); - HttpResponse::Found().header("location", "/").finish() -} - -fn main() { - ::std::env::set_var("RUST_LOG", "actix_web=info"); - env_logger::init(); - let sys = actix::System::new("cookie-auth"); - - server::new(|| { - App::new() - .middleware(middleware::Logger::default()) - .middleware(IdentityService::new( - CookieIdentityPolicy::new(&[0; 32]) - .name("auth-example") - .secure(false), - )) - .resource("/login", |r| r.f(login)) - .resource("/logout", |r| r.f(logout)) - .resource("/", |r| r.f(index)) - }).bind("127.0.0.1:8080") - .unwrap() - .start(); - - println!("Started http server: 127.0.0.1:8080"); - let _ = sys.run(); -} diff --git a/cookie-auth/Cargo.toml b/cookie-auth/Cargo.toml index ceda654d..12f8cf51 100644 --- a/cookie-auth/Cargo.toml +++ b/cookie-auth/Cargo.toml @@ -2,9 +2,10 @@ name = "cookie-auth" version = "0.1.0" authors = ["Nikolay Kim "] -workspace = "../" +edition = "2018" +workspace = ".." [dependencies] -actix = "0.7" -actix-web = "0.7" -env_logger = "0.5" +actix-web = "1.0.0" +actix-identity = "0.1.0" +env_logger = "0.6" diff --git a/cookie-auth/src/main.rs b/cookie-auth/src/main.rs index a7444793..9da2da57 100644 --- a/cookie-auth/src/main.rs +++ b/cookie-auth/src/main.rs @@ -1,45 +1,41 @@ -extern crate actix; -extern crate actix_web; -extern crate env_logger; +use actix_identity::Identity; +use actix_identity::{CookieIdentityPolicy, IdentityService}; +use actix_web::{middleware, web, App, HttpResponse, HttpServer}; -use actix_web::{middleware, server, App, HttpRequest, HttpResponse}; -use actix_web::middleware::identity::RequestIdentity; -use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService}; - -fn index(req: &HttpRequest) -> String { - format!("Hello {}", req.identity().unwrap_or("Anonymous".to_owned())) +fn index(id: Identity) -> String { + format!( + "Hello {}", + id.identity().unwrap_or_else(|| "Anonymous".to_owned()) + ) } -fn login(req: &HttpRequest) -> HttpResponse { - req.remember("user1".to_owned()); +fn login(id: Identity) -> HttpResponse { + id.remember("user1".to_owned()); HttpResponse::Found().header("location", "/").finish() } -fn logout(req: &HttpRequest) -> HttpResponse { - req.forget(); +fn logout(id: Identity) -> HttpResponse { + id.forget(); HttpResponse::Found().header("location", "/").finish() } -fn main() { - ::std::env::set_var("RUST_LOG", "actix_web=info"); +fn main() -> std::io::Result<()> { + std::env::set_var("RUST_LOG", "actix_web=info"); env_logger::init(); - let sys = actix::System::new("cookie-auth"); - server::new(|| { + HttpServer::new(|| { App::new() - .middleware(middleware::Logger::default()) - .middleware(IdentityService::new( + .wrap(IdentityService::new( CookieIdentityPolicy::new(&[0; 32]) .name("auth-example") .secure(false), )) - .resource("/login", |r| r.f(login)) - .resource("/logout", |r| r.f(logout)) - .resource("/", |r| r.f(index)) - }).bind("127.0.0.1:8080") - .unwrap() - .start(); - - println!("Started http server: 127.0.0.1:8080"); - let _ = sys.run(); + // enable logger - always register actix-web Logger middleware last + .wrap(middleware::Logger::default()) + .service(web::resource("/login").route(web::post().to(login))) + .service(web::resource("/logout").to(logout)) + .service(web::resource("/").route(web::get().to(index))) + }) + .bind("127.0.0.1:8080")? + .run() } diff --git a/cookie-session/Cargo.toml b/cookie-session/Cargo.toml index 399a6172..a927124e 100644 --- a/cookie-session/Cargo.toml +++ b/cookie-session/Cargo.toml @@ -2,12 +2,14 @@ name = "cookie-session" version = "0.1.0" authors = ["Nikolay Kim "] -workspace = "../" +workspace = ".." +edition = "2018" [dependencies] -actix = "0.7" -actix-web = "^0.7" +actix-web = "1.0.0" +actix-session = "0.2.0" +actix-rt = "0.2.5" futures = "0.1" time = "0.1" -env_logger = "0.5" +env_logger = "0.6" diff --git a/cookie-session/README.md b/cookie-session/README.md new file mode 100644 index 00000000..05e12515 --- /dev/null +++ b/cookie-session/README.md @@ -0,0 +1,7 @@ +## Cookie session example + +```sh +cd cookie-session +cargo run +# Starting http server: 127.0.0.1:8080 +``` diff --git a/cookie-session/src/main.rs b/cookie-session/src/main.rs index 94f736f9..f0eac8c5 100644 --- a/cookie-session/src/main.rs +++ b/cookie-session/src/main.rs @@ -3,52 +3,44 @@ //! //! [Redis session example](https://github.com/actix/examples/tree/master/redis-session) //! -//! [User guide](https://actix.rs/book/actix-web/sec-9-middlewares.html#user-sessions) +//! [User guide](https://actix.rs/docs/middleware/#user-sessions) -extern crate actix; -extern crate actix_web; -extern crate env_logger; -extern crate futures; - -use actix_web::middleware::session::{self, RequestSession}; -use actix_web::{middleware, server, App, HttpRequest, Result}; -use std::env; +use actix_session::{CookieSession, Session}; +use actix_web::{middleware::Logger, web, App, HttpRequest, HttpServer, Result}; /// simple index handler with session -fn index(req: &HttpRequest) -> Result<&'static str> { +fn index(session: Session, req: HttpRequest) -> Result<&'static str> { println!("{:?}", req); // RequestSession trait is used for session access let mut counter = 1; - if let Some(count) = req.session().get::("counter")? { + if let Some(count) = session.get::("counter")? { println!("SESSION value: {}", count); counter = count + 1; - req.session().set("counter", counter)?; + session.set("counter", counter)?; } else { - req.session().set("counter", counter)?; + session.set("counter", counter)?; } Ok("welcome!") } -fn main() { - env::set_var("RUST_LOG", "actix_web=info"); +fn main() -> std::io::Result<()> { + std::env::set_var("RUST_LOG", "actix_web=info"); env_logger::init(); - let sys = actix::System::new("session-example"); + let sys = actix_rt::System::new("cookie-session"); - server::new(|| { + HttpServer::new(|| { App::new() // enable logger - .middleware(middleware::Logger::default()) + .wrap(Logger::default()) // cookie session middleware - .middleware(session::SessionStorage::new( - session::CookieSessionBackend::signed(&[0; 32]).secure(false) - )) - .resource("/", |r| r.f(index)) - }).bind("127.0.0.1:8080") - .expect("Can not bind to 127.0.0.1:8080") - .start(); + .wrap(CookieSession::signed(&[0; 32]).secure(false)) + .service(web::resource("/").to(index)) + }) + .bind("127.0.0.1:8080")? + .start(); println!("Starting http server: 127.0.0.1:8080"); - let _ = sys.run(); + sys.run() } diff --git a/diesel/.env b/diesel/.env index 1fbc5af7..186be7ce 100644 --- a/diesel/.env +++ b/diesel/.env @@ -1 +1 @@ -DATABASE_URL=file:test.db +DATABASE_URL=test.db diff --git a/diesel/Cargo.toml b/diesel/Cargo.toml index a2f309b8..5065ff33 100644 --- a/diesel/Cargo.toml +++ b/diesel/Cargo.toml @@ -2,15 +2,14 @@ name = "diesel-example" version = "0.1.0" authors = ["Nikolay Kim "] -workspace = "../" +workspace = ".." +edition = "2018" [dependencies] +actix-web = "1.0.0" + bytes = "0.4" -env_logger = "0.5" - -actix = "0.7" -actix-web = "0.7" - +env_logger = "0.6" futures = "0.1" uuid = { version = "0.5", features = ["serde", "v4"] } serde = "1.0" diff --git a/diesel/README.md b/diesel/README.md index a989377e..13d45f08 100644 --- a/diesel/README.md +++ b/diesel/README.md @@ -7,9 +7,10 @@ Diesel's `Getting Started` guide using SQLite for Actix web ### init database sqlite ```bash +# if opensuse: sudo zypper install sqlite3-devel cargo install diesel_cli --no-default-features --features sqlite cd examples/diesel -echo "DATABASE_URL=file:test.db" > .env +echo "DATABASE_URL=test.db" > .env diesel migration run ``` @@ -18,6 +19,7 @@ diesel migration run ```bash # if ubuntu : sudo apt-get install libsqlite3-dev # if fedora : sudo dnf install libsqlite3x-devel +# if opensuse: sudo zypper install libsqlite3-0 cd examples/diesel cargo run (or ``cargo watch -x run``) # Started http server: 127.0.0.1:8080 @@ -32,6 +34,7 @@ cargo run (or ``cargo watch -x run``) ```bash # if ubuntu : sudo apt-get install sqlite3 # if fedora : sudo dnf install sqlite3x +# if opensuse: sudo zypper install sqlite3 sqlite3 test.db sqlite> .tables sqlite> select * from users; diff --git a/diesel/src/db.rs b/diesel/src/db.rs deleted file mode 100644 index de3dd179..00000000 --- a/diesel/src/db.rs +++ /dev/null @@ -1,55 +0,0 @@ -//! Db executor actor -use actix::prelude::*; -use actix_web::*; -use diesel; -use diesel::prelude::*; -use diesel::r2d2::{ConnectionManager, Pool}; -use uuid; - -use models; -use schema; - -/// This is db executor actor. We are going to run 3 of them in parallel. -pub struct DbExecutor(pub Pool>); - -/// This is only message that this actor can handle, but it is easy to extend -/// number of messages. -pub struct CreateUser { - pub name: String, -} - -impl Message for CreateUser { - type Result = Result; -} - -impl Actor for DbExecutor { - type Context = SyncContext; -} - -impl Handler for DbExecutor { - type Result = Result; - - fn handle(&mut self, msg: CreateUser, _: &mut Self::Context) -> Self::Result { - use self::schema::users::dsl::*; - - let uuid = format!("{}", uuid::Uuid::new_v4()); - let new_user = models::NewUser { - id: &uuid, - name: &msg.name, - }; - - let conn: &SqliteConnection = &self.0.get().unwrap(); - - diesel::insert_into(users) - .values(&new_user) - .execute(conn) - .map_err(|_| error::ErrorInternalServerError("Error inserting person"))?; - - let mut items = users - .filter(id.eq(&uuid)) - .load::(conn) - .map_err(|_| error::ErrorInternalServerError("Error loading person"))?; - - Ok(items.pop().unwrap()) - } -} diff --git a/diesel/src/main.rs b/diesel/src/main.rs index d30519d6..36453495 100644 --- a/diesel/src/main.rs +++ b/diesel/src/main.rs @@ -4,77 +4,72 @@ //! Actix supports sync actors by default, so we going to create sync actor //! that use diesel. Technically sync actors are worker style actors, multiple //! of them can run in parallel and process messages from same queue. -extern crate serde; -extern crate serde_json; -#[macro_use] -extern crate serde_derive; #[macro_use] extern crate diesel; -extern crate actix; -extern crate actix_web; -extern crate env_logger; -extern crate futures; -extern crate r2d2; -extern crate uuid; -extern crate bytes; -// extern crate json; - +#[macro_use] +extern crate serde_derive; +use actix_web::{error, middleware, web, App, Error, HttpResponse, HttpServer}; use bytes::BytesMut; -use actix::prelude::*; -use actix_web::{ - http, middleware, server, App, AsyncResponder, FutureResponse, HttpResponse, Path, Error, HttpRequest, - State, HttpMessage, error, Json -}; - use diesel::prelude::*; -use diesel::r2d2::ConnectionManager; -use futures::{future, Future, Stream}; +use diesel::r2d2::{self, ConnectionManager}; +use dotenv; +use futures::future::{err, Either}; +use futures::{Future, Stream}; -mod db; mod models; mod schema; -use db::{CreateUser, DbExecutor}; +type Pool = r2d2::Pool>; -/// State with DbExecutor address -struct AppState { - db: Addr, +/// Diesel query +fn query( + nm: String, + pool: web::Data, +) -> Result { + use self::schema::users::dsl::*; + + let uuid = format!("{}", uuid::Uuid::new_v4()); + let new_user = models::NewUser { + id: &uuid, + name: nm.as_str(), + }; + let conn: &SqliteConnection = &pool.get().unwrap(); + + diesel::insert_into(users).values(&new_user).execute(conn)?; + + let mut items = users.filter(id.eq(&uuid)).load::(conn)?; + Ok(items.pop().unwrap()) } /// Async request handler fn add( - (name, state): (Path, State), -) -> FutureResponse { - // send async `CreateUser` message to a `DbExecutor` - state - .db - .send(CreateUser { - name: name.into_inner(), - }) - .from_err() - .and_then(|res| match res { - Ok(user) => Ok(HttpResponse::Ok().json(user)), - Err(_) => Ok(HttpResponse::InternalServerError().into()), - }) - .responder() + name: web::Path, + pool: web::Data, +) -> impl Future { + // run diesel blocking code + web::block(move || query(name.into_inner(), pool)).then(|res| match res { + Ok(user) => Ok(HttpResponse::Ok().json(user)), + Err(_) => Ok(HttpResponse::InternalServerError().into()), + }) } #[derive(Debug, Serialize, Deserialize)] struct MyUser { - name: String + name: String, } const MAX_SIZE: usize = 262_144; // max payload size is 256k /// This handler manually load request payload and parse json object -fn index_add((req, state): (HttpRequest, State)) -> impl Future { - // HttpRequest::payload() is stream of Bytes objects - req.payload() +fn index_add( + pl: web::Payload, + pool: web::Data, +) -> impl Future { + pl // `Future::from_err` acts like `?` in that it coerces the error type from // the future into the final error type .from_err() - // `fold` will asynchronously read each chunk of the request body and // call supplied closure, then it resolves to result of closure .fold(BytesMut::new(), move |mut body, chunk| { @@ -92,78 +87,80 @@ fn index_add((req, state): (HttpRequest, State)) -> impl Fut // Douman NOTE: // The return value in this closure helps, to clarify result for compiler // as otheriwse it cannot understand it - .and_then(move |body| -> Box> { + .and_then(move |body| { // body is loaded, now we can deserialize serde-json let r_obj = serde_json::from_slice::(&body); // Send to the db for create match r_obj { Ok(obj) => { - let res = state.db.send(CreateUser { name: obj.name, }) - .from_err() - .and_then(|res| match res { - Ok(user) => Ok(HttpResponse::Ok().json(user)), - Err(_) => Ok(HttpResponse::InternalServerError().into()), - }); - - Box::new(res) + Either::A(web::block(move || query(obj.name, pool)).then(|res| { + match res { + Ok(user) => Ok(HttpResponse::Ok().json(user)), + Err(_) => Ok(HttpResponse::InternalServerError().into()), + } + })) } - Err(_) => Box::new(future::err(error::ErrorBadRequest("Json Decode Failed"))) + Err(_) => Either::B(err(error::ErrorBadRequest("Json Decode Failed"))), } }) } -fn add2((item, state): (Json, State)) -> impl Future { - state.db - .send(CreateUser { - // into_inner to move into the reference, then accessing name to - // move the name out. - name: item.into_inner().name, - }) - .from_err() - .and_then(|res| match res { - Ok(user) => Ok(HttpResponse::Ok().json(user)), - Err(_) => Ok(HttpResponse::InternalServerError().into()), - }) +fn add2( + item: web::Json, + pool: web::Data, +) -> impl Future { + // run diesel blocking code + web::block(move || query(item.into_inner().name, pool)).then(|res| match res { + Ok(user) => Ok(HttpResponse::Ok().json(user)), + Err(_) => Ok(HttpResponse::InternalServerError().into()), + }) } -fn main() { - ::std::env::set_var("RUST_LOG", "actix_web=info"); +fn main() -> std::io::Result<()> { + std::env::set_var("RUST_LOG", "actix_web=info"); env_logger::init(); - let sys = actix::System::new("diesel-example"); - // Start 3 db executor actors - let manager = ConnectionManager::::new("test.db"); + dotenv::dotenv().ok(); + + let connspec = std::env::var("DATABASE_URL").expect("DATABASE_URL"); + let manager = ConnectionManager::::new(connspec); let pool = r2d2::Pool::builder() .build(manager) .expect("Failed to create pool."); - let addr = SyncArbiter::start(3, move || DbExecutor(pool.clone())); - // Start http server - server::new(move || { - App::with_state(AppState{db: addr.clone()}) + HttpServer::new(move || { + App::new() + .data(pool.clone()) // enable logger - .middleware(middleware::Logger::default()) + .wrap(middleware::Logger::default()) // This can be called with: // curl -S --header "Content-Type: application/json" --request POST --data '{"name":"xyz"}' http://127.0.0.1:8080/add // Use of the extractors makes some post conditions simpler such // as size limit protections and built in json validation. - .resource("/add2", |r| { - r.method(http::Method::POST) - .with_async_config(add2, |(json_cfg, )| { - json_cfg.0.limit(4096); // <- limit size of the payload - }) - }) + .service( + web::resource("/add2") + .data( + web::JsonConfig::default() + .limit(4096) // <- limit size of the payload + .error_handler(|err, _| { + // <- create custom error response + error::InternalError::from_response( + err, + HttpResponse::Conflict().finish(), + ) + .into() + }), + ) + .route(web::post().to_async(add2)), + ) // Manual parsing would allow custom error construction, use of // other parsers *beside* json (for example CBOR, protobuf, xml), and allows // an application to standardise on a single parser implementation. - .resource("/add", |r| r.method(http::Method::POST).with_async(index_add)) - .resource("/add/{name}", |r| r.method(http::Method::GET).with(add)) - }).bind("127.0.0.1:8080") - .unwrap() - .start(); - - println!("Started http server: 127.0.0.1:8080"); - let _ = sys.run(); + .service(web::resource("/add").route(web::post().to_async(index_add))) + .service(web::resource("/add/{name}").route(web::get().to_async(add))) + }) + .bind("127.0.0.1:8080")? + .run() } diff --git a/error_handling/Cargo.toml b/error_handling/Cargo.toml index fe27b1c3..808e7335 100644 --- a/error_handling/Cargo.toml +++ b/error_handling/Cargo.toml @@ -2,11 +2,13 @@ name = "error_handling" version = "0.1.0" authors = ["dowwie "] +edition = "2018" +workspace = ".." [dependencies] -actix = "0.7.3" -actix-web = "0.7.3" -failure = "0.1.2" +actix-web = "1.0.0" + +derive_more = "0.14.0" futures = "0.1.23" rand = "0.5.4" -env_logger = "0.5.12" +env_logger = "0.6" diff --git a/error_handling/src/main.rs b/error_handling/src/main.rs index 6a1788f0..676a8ce8 100644 --- a/error_handling/src/main.rs +++ b/error_handling/src/main.rs @@ -1,10 +1,10 @@ /* -The goal of this example is to show how to propagate a custom error type, derived -from the Fail trait, to a web handler that will evaluate the type of error that +The goal of this example is to show how to propagate a custom error type, +to a web handler that will evaluate the type of error that was raised and return an appropriate HTTPResponse. This example uses a 50/50 chance of returning 200 Ok, otherwise one of four possible -http errors will be chosen, each with an equal chance of being selected: +http errors will be chosen, each with an equal chance of being selected: 1. 403 Forbidden 2. 401 Unauthorized 3. 500 InternalServerError @@ -12,128 +12,91 @@ http errors will be chosen, each with an equal chance of being selected: */ - -extern crate actix; -extern crate actix_web; -extern crate env_logger; -#[macro_use] extern crate failure; -extern crate futures; -extern crate rand; - - -use actix_web::{ - http::Method, server, App, AsyncResponder, Error as ActixWebError, - HttpResponse, HttpRequest +use actix_web::{web, App, Error, HttpResponse, HttpServer, ResponseError}; +use derive_more::Display; // naming it clearly for illustration purposes +use futures::future::{err, ok, Future}; +use rand::{ + distributions::{Distribution, Standard}, + thread_rng, Rng, }; -use failure::Error as FailureError; // naming it clearly for illustration purposes -use futures::{ - future::{ - ok as fut_ok, - err as fut_err - }, - Future -}; -use rand::{thread_rng, Rng, distributions::{Distribution, Standard}}; - - -#[derive(Fail, Debug)] +#[derive(Debug, Display)] pub enum CustomError { - #[fail(display = "Custom Error 1")] + #[display(fmt = "Custom Error 1")] CustomOne, - #[fail(display = "Custom Error 2")] + #[display(fmt = "Custom Error 2")] CustomTwo, - #[fail(display = "Custom Error 3")] + #[display(fmt = "Custom Error 3")] CustomThree, - #[fail(display = "Custom Error 4")] - CustomFour + #[display(fmt = "Custom Error 4")] + CustomFour, } - impl Distribution for Standard { fn sample(&self, rng: &mut R) -> CustomError { match rng.gen_range(0, 4) { 0 => CustomError::CustomOne, 1 => CustomError::CustomTwo, 2 => CustomError::CustomThree, - _ => CustomError::CustomFour + _ => CustomError::CustomFour, } } } -/* +/// Actix web uses `ResponseError` for conversion of errors to a response impl ResponseError for CustomError { fn error_response(&self) -> HttpResponse { - HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR) + match self { + CustomError::CustomOne => { + println!("do some stuff related to CustomOne error"); + HttpResponse::Forbidden().finish() + } + + CustomError::CustomTwo => { + println!("do some stuff related to CustomTwo error"); + HttpResponse::Unauthorized().finish() + } + + CustomError::CustomThree => { + println!("do some stuff related to CustomThree error"); + HttpResponse::InternalServerError().finish() + } + + _ => { + println!("do some stuff related to CustomFour error"); + HttpResponse::BadRequest().finish() + } + } } } -*/ - /// randomly returns either () or one of the 4 CustomError variants -//fn do_something_random() -> impl Future, -// Error = ActixWebError> { fn do_something_random() -> impl Future { let mut rng = thread_rng(); // 20% chance that () will be returned by this function - if rng.gen_bool(2.0/10.0) { - return fut_ok(()) + if rng.gen_bool(2.0 / 10.0) { + ok(()) + } else { + err(rand::random::()) } - - let err: CustomError = rand::random(); - return fut_err(err) } - -fn do_something(_req: HttpRequest) - -> impl Future { - - do_something_random() - .then(|result| match result { - Ok(_) => Ok(HttpResponse::Ok() - .body("Nothing interesting happened. Try again.")), - - Err(err) => match err { - CustomError::CustomOne => { - println!("do some stuff related to CustomOne error"); - Ok(HttpResponse::Forbidden().finish()) - }, - - CustomError::CustomTwo => { - println!("do some stuff related to CustomTwo error"); - Ok(HttpResponse::Unauthorized().finish()) - }, - - CustomError::CustomThree => { - println!("do some stuff related to CustomThree error"); - Ok(HttpResponse::InternalServerError().finish()) - }, - - _ => { - println!("do some stuff related to CustomFour error"); - Ok(HttpResponse::BadRequest().finish()) - } - } +fn do_something() -> impl Future { + do_something_random().from_err().and_then(|_| { + HttpResponse::Ok().body("Nothing interesting happened. Try again.") }) - .responder() } - -fn main() { - ::std::env::set_var("RUST_LOG", "actix_web=info"); +fn main() -> std::io::Result<()> { + std::env::set_var("RUST_LOG", "actix_web=info"); env_logger::init(); - let sys = actix::System::new("error_handling_example"); - server::new(move || { - App::new() - .resource("/something", |r| - r.method(Method::GET) - .with_async(do_something)) - }).bind("127.0.0.1:8088") - .unwrap() - .start(); - - println!("Started http server: 127.0.0.1:8088"); - let _ = sys.run(); + HttpServer::new(move || { + App::new().service( + web::resource("/something").route(web::get().to_async(do_something)), + ) + }) + .bind("127.0.0.1:8088")? + .run() } diff --git a/form/Cargo.toml b/form/Cargo.toml index 42c314ff..7a09f74a 100644 --- a/form/Cargo.toml +++ b/form/Cargo.toml @@ -2,10 +2,11 @@ name = "form-example" version = "0.1.0" authors = ["Gorm Casper "] +edition = "2018" +workspace = ".." [dependencies] -actix = "0.7" -actix-web = "0.7" +actix-web = "1.0.0" serde = "1.0" serde_derive = "1.0" diff --git a/form/src/main.rs b/form/src/main.rs index 615fff2e..ebdddf0a 100644 --- a/form/src/main.rs +++ b/form/src/main.rs @@ -1,46 +1,32 @@ -extern crate actix; -extern crate actix_web; - #[macro_use] extern crate serde_derive; use actix_web::{ - http, middleware, server, App, Form, HttpRequest, HttpResponse, Result, State, + middleware, web, App, HttpRequest, HttpResponse, HttpServer, Responder, Result, }; struct AppState { foo: String, } -fn main() { - let sys = actix::System::new("form-example"); - - let _addr = server::new(|| { - App::with_state(AppState { - foo: "bar".to_string(), - }).middleware(middleware::Logger::default()) - .resource("/", |r| { - r.method(http::Method::GET).with(index); +fn main() -> std::io::Result<()> { + HttpServer::new(|| { + App::new() + .data(AppState { + foo: "bar".to_string(), }) - .resource("/post1", |r| { - r.method(http::Method::POST).with(handle_post_1) - }) - .resource("/post2", |r| { - r.method(http::Method::POST).with(handle_post_2) - }) - .resource("/post3", |r| { - r.method(http::Method::POST).with(handle_post_3) - }) - }).bind("127.0.0.1:8080") - .expect("Can not bind to 127.0.0.1:8080") - .start(); - - println!("Starting http server: 127.0.0.1:8080"); - let _ = sys.run(); + .wrap(middleware::Logger::default()) + .service(web::resource("/").route(web::get().to(index))) + .service(web::resource("/post1").route(web::post().to(handle_post_1))) + .service(web::resource("/post2").route(web::post().to(handle_post_2))) + .service(web::resource("/post3").route(web::post().to(handle_post_3))) + }) + .bind("127.0.0.1:8080")? + .run() } -fn index(_req: HttpRequest) -> Result { - Ok(HttpResponse::build(http::StatusCode::OK) +fn index() -> Result { + Ok(HttpResponse::Ok() .content_type("text/html; charset=utf-8") .body(include_str!("../static/form.html"))) } @@ -51,30 +37,28 @@ pub struct MyParams { } /// Simple handle POST request -fn handle_post_1(params: Form) -> Result { - Ok(HttpResponse::build(http::StatusCode::OK) +fn handle_post_1(params: web::Form) -> Result { + Ok(HttpResponse::Ok() .content_type("text/plain") .body(format!("Your name is {}", params.name))) } /// State and POST Params fn handle_post_2( - (state, params): (State, Form), -) -> Result { - Ok(HttpResponse::build(http::StatusCode::OK) - .content_type("text/plain") - .body(format!( - "Your name is {}, and in AppState I have foo: {}", - params.name, state.foo - ))) + state: web::Data, + params: web::Form, +) -> HttpResponse { + HttpResponse::Ok().content_type("text/plain").body(format!( + "Your name is {}, and in AppState I have foo: {}", + params.name, state.foo + )) } /// Request and POST Params -fn handle_post_3( - (req, params): (HttpRequest, Form), -) -> Result { +fn handle_post_3(req: HttpRequest, params: web::Form) -> impl Responder { println!("Handling POST request: {:?}", req); - Ok(HttpResponse::build(http::StatusCode::OK) + + HttpResponse::Ok() .content_type("text/plain") - .body(format!("Your name is {}", params.name))) + .body(format!("Your name is {}", params.name)) } diff --git a/hello-world/Cargo.toml b/hello-world/Cargo.toml index 7126813c..396062bd 100644 --- a/hello-world/Cargo.toml +++ b/hello-world/Cargo.toml @@ -2,10 +2,9 @@ name = "hello-world" version = "0.1.0" authors = ["Nikolay Kim "] -workspace = "../" +workspace = ".." +edition = "2018" [dependencies] -env_logger = "0.5" - -actix = "0.7" -actix-web = "^0.7" +actix-web = "1.0.0" +env_logger = "0.6" diff --git a/hello-world/src/main.rs b/hello-world/src/main.rs index 90d93b7d..ac294063 100644 --- a/hello-world/src/main.rs +++ b/hello-world/src/main.rs @@ -1,28 +1,21 @@ -extern crate actix; -extern crate actix_web; -extern crate env_logger; +use actix_web::{middleware, web, App, HttpRequest, HttpServer}; -use actix_web::{middleware, server, App, HttpRequest}; - -fn index(_req: &HttpRequest) -> &'static str { +fn index(req: HttpRequest) -> &'static str { + println!("REQ: {:?}", req); "Hello world!" } -fn main() { - ::std::env::set_var("RUST_LOG", "actix_web=info"); +fn main() -> std::io::Result<()> { + std::env::set_var("RUST_LOG", "actix_web=info"); env_logger::init(); - let sys = actix::System::new("hello-world"); - server::new(|| { + HttpServer::new(|| { App::new() // enable logger - .middleware(middleware::Logger::default()) - .resource("/index.html", |r| r.f(|_| "Hello world!")) - .resource("/", |r| r.f(index)) - }).bind("127.0.0.1:8080") - .unwrap() - .start(); - - println!("Started http server: 127.0.0.1:8080"); - let _ = sys.run(); + .wrap(middleware::Logger::default()) + .service(web::resource("/index.html").to(|| "Hello world!")) + .service(web::resource("/").to(index)) + }) + .bind("127.0.0.1:8080")? + .run() } diff --git a/http-full-proxy/Cargo.toml b/http-full-proxy/Cargo.toml deleted file mode 100644 index f9363344..00000000 --- a/http-full-proxy/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "http-full-proxy" -version = "0.1.0" -authors = ["Rotem Yaari"] - -[dependencies] -actix = "0.7.5" -actix-web = "0.7.13" -clap = "2.32.0" -futures = "0.1.25" -failure = "0.1.3" -url = "1.7.1" diff --git a/http-full-proxy/README.md b/http-full-proxy/README.md deleted file mode 100644 index 55a6a680..00000000 --- a/http-full-proxy/README.md +++ /dev/null @@ -1,10 +0,0 @@ -## HTTP Full proxy example - -This proxy forwards all types of requests, including ones with body, to another HTTP server, -returning the response to the client. - -To start: - -``` shell -cargo run -``` diff --git a/http-full-proxy/src/main.rs b/http-full-proxy/src/main.rs deleted file mode 100644 index 4d691870..00000000 --- a/http-full-proxy/src/main.rs +++ /dev/null @@ -1,135 +0,0 @@ -#![deny(warnings)] -extern crate actix; -extern crate actix_web; -extern crate clap; -extern crate failure; -extern crate futures; -extern crate url; - -use actix_web::{ - client, http, server, App, AsyncResponder, Error, HttpMessage, HttpRequest, - HttpResponse, -}; -use clap::{value_t, Arg}; -use futures::{future, Future}; -use std::net::ToSocketAddrs; -use url::Url; - -struct AppState { - forward_url: Url, -} - -impl AppState { - pub fn init(forward_url: Url) -> AppState { - AppState { forward_url } - } -} - -fn forward( - req: &HttpRequest, -) -> Box> { - let mut new_url = req.state().forward_url.clone(); - new_url.set_path(req.uri().path()); - new_url.set_query(req.uri().query()); - - let mut forwarded_req = client::ClientRequest::build_from(req) - .no_default_headers() - .uri(new_url) - .streaming(req.payload()) - .unwrap(); - - if let Some(addr) = req.peer_addr() { - match forwarded_req.headers_mut().entry("x-forwarded-for") { - Ok(http::header::Entry::Vacant(entry)) => { - let addr = format!("{}", addr.ip()); - entry.insert(addr.parse().unwrap()); - } - Ok(http::header::Entry::Occupied(mut entry)) => { - let addr = format!("{}, {}", entry.get().to_str().unwrap(), addr.ip()); - entry.insert(addr.parse().unwrap()); - } - _ => unreachable!(), - } - } - - forwarded_req - .send() - .map_err(Error::from) - .and_then(construct_response) - .responder() -} - -fn construct_response( - resp: client::ClientResponse, -) -> Box> { - let mut client_resp = HttpResponse::build(resp.status()); - for (header_name, header_value) in - resp.headers().iter().filter(|(h, _)| *h != "connection") - { - client_resp.header(header_name.clone(), header_value.clone()); - } - if resp.chunked().unwrap_or(false) { - Box::new(future::ok(client_resp.streaming(resp.payload()))) - } else { - Box::new( - resp.body() - .from_err() - .and_then(move |body| Ok(client_resp.body(body))), - ) - } -} - -fn main() { - let matches = clap::App::new("HTTP Proxy") - .arg( - Arg::with_name("listen_addr") - .takes_value(true) - .value_name("LISTEN ADDR") - .index(1) - .required(true), - ).arg( - Arg::with_name("listen_port") - .takes_value(true) - .value_name("LISTEN PORT") - .index(2) - .required(true), - ).arg( - Arg::with_name("forward_addr") - .takes_value(true) - .value_name("FWD ADDR") - .index(3) - .required(true), - ).arg( - Arg::with_name("forward_port") - .takes_value(true) - .value_name("FWD PORT") - .index(4) - .required(true), - ).get_matches(); - - let listen_addr = matches.value_of("listen_addr").unwrap(); - let listen_port = value_t!(matches, "listen_port", u16).unwrap_or_else(|e| e.exit()); - - let forwarded_addr = matches.value_of("forward_addr").unwrap(); - let forwarded_port = - value_t!(matches, "forward_port", u16).unwrap_or_else(|e| e.exit()); - - let forward_url = Url::parse(&format!( - "http://{}", - (forwarded_addr, forwarded_port) - .to_socket_addrs() - .unwrap() - .next() - .unwrap() - )).unwrap(); - - server::new(move || { - App::with_state(AppState::init(forward_url.clone())).default_resource(|r| { - r.f(forward); - }) - }).workers(32) - .bind((listen_addr, listen_port)) - .expect("Cannot bind listening port") - .system_exit() - .run(); -} diff --git a/http-proxy/Cargo.toml b/http-proxy/Cargo.toml index b06a5946..a2c8740c 100644 --- a/http-proxy/Cargo.toml +++ b/http-proxy/Cargo.toml @@ -1,20 +1,14 @@ [package] name = "http-proxy" version = "0.1.0" -authors = ["Nikolay Kim "] -workspace = "../" - -[[bin]] -name = "proxy" -path = "src/main.rs" - -[[bin]] -name = "proxy-example-server" -path = "src/server.rs" +authors = ["Nikolay Kim ", "Rotem Yaari "] +workspace = ".." +edition = "2018" [dependencies] -env_logger = "0.5" -futures = "0.1" - -actix = "0.7" -actix-web = "^0.7" +actix-rt = "0.2" +actix-web = { version = "1.0.0", features=["ssl"] } +clap = "2.32.0" +futures = "0.1.25" +failure = "0.1.3" +url = "1.7.1" diff --git a/http-proxy/README.md b/http-proxy/README.md index 2bc8272a..5badaca8 100644 --- a/http-proxy/README.md +++ b/http-proxy/README.md @@ -1,13 +1,10 @@ -## Http proxy example +## HTTP Full proxy example -To start proxy server: +This is a relatively simple HTTP proxy, forwarding HTTP requests to another HTTP server, including +request body, headers, and streaming uploads. -```sh -cargo run --bin proxy -``` - -To start local backend server: - -```sh -cargo run --bin proxy-example-server +To start: + +``` shell +cargo run ``` diff --git a/http-proxy/src/main.rs b/http-proxy/src/main.rs index 3785ae6c..a68b46f1 100644 --- a/http-proxy/src/main.rs +++ b/http-proxy/src/main.rs @@ -1,60 +1,102 @@ -extern crate actix; -extern crate actix_web; -extern crate env_logger; -extern crate futures; +use actix_web::client::Client; +use actix_web::{middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer}; +use clap::{value_t, Arg}; +use futures::Future; +use std::net::ToSocketAddrs; +use url::Url; -use actix_web::{ - client, middleware, server, App, AsyncResponder, Body, Error, HttpMessage, - HttpRequest, HttpResponse, -}; -use futures::{Future, Stream}; +fn forward( + req: HttpRequest, + payload: web::Payload, + url: web::Data, + client: web::Data, +) -> impl Future { + let mut new_url = url.get_ref().clone(); + new_url.set_path(req.uri().path()); + new_url.set_query(req.uri().query()); -/// Stream client request response and then send body to a server response -fn index(_req: &HttpRequest) -> Box> { - client::ClientRequest::get("http://127.0.0.1:8081/") - .finish().unwrap() - .send() - .map_err(Error::from) // <- convert SendRequestError to an Error - .and_then( - |resp| resp.body() // <- this is MessageBody type, resolves to complete body - .from_err() // <- convert PayloadError to an Error - .and_then(|body| { // <- we got complete body, now send as server response - Ok(HttpResponse::Ok().body(body)) - })) - .responder() -} + let forwarded_req = client + .request_from(new_url.as_str(), req.head()) + .no_decompress(); + let forwarded_req = if let Some(addr) = req.head().peer_addr { + forwarded_req.header("x-forwarded-for", format!("{}", addr.ip())) + } else { + forwarded_req + }; -/// streaming client request to a streaming server response -fn streaming(_req: &HttpRequest) -> Box> { - // send client request - client::ClientRequest::get("https://www.rust-lang.org/en-US/") - .finish().unwrap() - .send() // <- connect to host and send request - .map_err(Error::from) // <- convert SendRequestError to an Error - .and_then(|resp| { // <- we received client response - Ok(HttpResponse::Ok() - // read one chunk from client response and send this chunk to a server response - // .from_err() converts PayloadError to an Error - .body(Body::Streaming(Box::new(resp.payload().from_err())))) + forwarded_req + .send_stream(payload) + .map_err(Error::from) + .map(|res| { + let mut client_resp = HttpResponse::build(res.status()); + for (header_name, header_value) in res + .headers() + .iter() + .filter(|(h, _)| *h != "connection" && *h != "content-length") + { + client_resp.header(header_name.clone(), header_value.clone()); + } + client_resp.streaming(res) }) - .responder() } -fn main() { - ::std::env::set_var("RUST_LOG", "actix_web=info"); - env_logger::init(); - let sys = actix::System::new("http-proxy"); +fn main() -> std::io::Result<()> { + let matches = clap::App::new("HTTP Proxy") + .arg( + Arg::with_name("listen_addr") + .takes_value(true) + .value_name("LISTEN ADDR") + .index(1) + .required(true), + ) + .arg( + Arg::with_name("listen_port") + .takes_value(true) + .value_name("LISTEN PORT") + .index(2) + .required(true), + ) + .arg( + Arg::with_name("forward_addr") + .takes_value(true) + .value_name("FWD ADDR") + .index(3) + .required(true), + ) + .arg( + Arg::with_name("forward_port") + .takes_value(true) + .value_name("FWD PORT") + .index(4) + .required(true), + ) + .get_matches(); - server::new(|| { + let listen_addr = matches.value_of("listen_addr").unwrap(); + let listen_port = value_t!(matches, "listen_port", u16).unwrap_or_else(|e| e.exit()); + + let forwarded_addr = matches.value_of("forward_addr").unwrap(); + let forwarded_port = + value_t!(matches, "forward_port", u16).unwrap_or_else(|e| e.exit()); + + let forward_url = Url::parse(&format!( + "http://{}", + (forwarded_addr, forwarded_port) + .to_socket_addrs() + .unwrap() + .next() + .unwrap() + )) + .unwrap(); + + HttpServer::new(move || { App::new() - .middleware(middleware::Logger::default()) - .resource("/streaming", |r| r.f(streaming)) - .resource("/", |r| r.f(index)) - }).workers(1) - .bind("127.0.0.1:8080") - .unwrap() - .start(); - - println!("Started http server: 127.0.0.1:8080"); - let _ = sys.run(); + .data(Client::new()) + .data(forward_url.clone()) + .wrap(middleware::Logger::default()) + .default_service(web::route().to_async(forward)) + }) + .bind((listen_addr, listen_port))? + .system_exit() + .run() } diff --git a/http-proxy/src/server.rs b/http-proxy/src/server.rs deleted file mode 100644 index ca1dfb26..00000000 --- a/http-proxy/src/server.rs +++ /dev/null @@ -1,34 +0,0 @@ -extern crate actix; -extern crate actix_web; -extern crate env_logger; -extern crate futures; - -use actix_web::*; -use futures::Future; - -fn index(req: &HttpRequest) -> FutureResponse { - req.body() - .from_err() - .map(|bytes| HttpResponse::Ok().body(bytes)) - .responder() -} - -fn main() { - ::std::env::set_var("RUST_LOG", "actix_web=error"); - let _ = env_logger::init(); - let sys = actix::System::new("ws-example"); - - server::new(|| { - App::new() - // enable logger - .middleware(middleware::Logger::default()) - .resource("/index.html", |r| r.f(|_| "Hello world!")) - .resource("/", |r| r.f(index)) - }).workers(1) - .bind("127.0.0.1:8081") - .unwrap() - .start(); - - println!("Started http server: 127.0.0.1:8081"); - let _ = sys.run(); -} diff --git a/json/Cargo.toml b/json/Cargo.toml index c358f4ae..d7196f25 100644 --- a/json/Cargo.toml +++ b/json/Cargo.toml @@ -2,9 +2,12 @@ name = "json-example" version = "0.1.0" authors = ["Nikolay Kim "] -workspace = "../" +workspace = ".." +edition = "2018" [dependencies] +actix-web = "1.0.0" + bytes = "0.4" futures = "0.1" env_logger = "*" @@ -13,6 +16,3 @@ serde = "1.0" serde_json = "1.0" serde_derive = "1.0" json = "*" - -actix = "0.7" -actix-web = "^0.7" diff --git a/json/src/main.rs b/json/src/main.rs index a93f761e..86f2a242 100644 --- a/json/src/main.rs +++ b/json/src/main.rs @@ -1,22 +1,13 @@ -extern crate actix; -extern crate actix_web; -extern crate bytes; -extern crate env_logger; -extern crate futures; -extern crate serde_json; -#[macro_use] -extern crate serde_derive; #[macro_use] extern crate json; use actix_web::{ - error, http, middleware, server, App, AsyncResponder, Error, HttpMessage, - HttpRequest, HttpResponse, Json, + error, middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer, }; - use bytes::BytesMut; use futures::{Future, Stream}; use json::JsonValue; +use serde_derive::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize)] struct MyObj { @@ -24,39 +15,31 @@ struct MyObj { number: i32, } -/// This handler uses `HttpRequest::json()` for loading json object. -fn index(req: &HttpRequest) -> Box> { - req.json() - .from_err() // convert all errors into `Error` - .and_then(|val: MyObj| { - println!("model: {:?}", val); - Ok(HttpResponse::Ok().json(val)) // <- send response - }) - .responder() -} - /// This handler uses json extractor -fn extract_item(item: Json) -> HttpResponse { +fn index(item: web::Json) -> HttpResponse { println!("model: {:?}", &item); HttpResponse::Ok().json(item.0) // <- send response } /// This handler uses json extractor with limit -fn extract_item_limit((item, _req): (Json, HttpRequest)) -> HttpResponse { - println!("model: {:?}", &item); - HttpResponse::Ok().json(item.0) // <- send response +fn extract_item(item: web::Json, req: HttpRequest) -> HttpResponse { + println!("request: {:?}", req); + println!("model: {:?}", item); + + HttpResponse::Ok().json(item.0) // <- send json response } const MAX_SIZE: usize = 262_144; // max payload size is 256k /// This handler manually load request payload and parse json object -fn index_manual(req: &HttpRequest) -> Box> { - // HttpRequest::payload() is stream of Bytes objects - req.payload() +fn index_manual( + payload: web::Payload, +) -> impl Future { + // payload is a stream of Bytes objects + payload // `Future::from_err` acts like `?` in that it coerces the error type from // the future into the final error type .from_err() - // `fold` will asynchronously read each chunk of the request body and // call supplied closure, then it resolves to result of closure .fold(BytesMut::new(), move |mut body, chunk| { @@ -75,59 +58,44 @@ fn index_manual(req: &HttpRequest) -> Box(&body)?; Ok(HttpResponse::Ok().json(obj)) // <- send response }) - .responder() } /// This handler manually load request payload and parse json-rust -fn index_mjsonrust( - req: &HttpRequest, -) -> Box> { - req.payload() - .concat2() - .from_err() - .and_then(|body| { - // body is loaded, now we can deserialize json-rust - let result = json::parse(std::str::from_utf8(&body).unwrap()); // return Result - let injson: JsonValue = match result { - Ok(v) => v, - Err(e) => object!{"err" => e.to_string() }, - }; - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(injson.dump())) - }) - .responder() +fn index_mjsonrust(pl: web::Payload) -> impl Future { + pl.concat2().from_err().and_then(|body| { + // body is loaded, now we can deserialize json-rust + let result = json::parse(std::str::from_utf8(&body).unwrap()); // return Result + let injson: JsonValue = match result { + Ok(v) => v, + Err(e) => json::object! {"err" => e.to_string() }, + }; + Ok(HttpResponse::Ok() + .content_type("application/json") + .body(injson.dump())) + }) } -fn main() { - ::std::env::set_var("RUST_LOG", "actix_web=info"); +fn main() -> std::io::Result<()> { + std::env::set_var("RUST_LOG", "actix_web=info"); env_logger::init(); - let sys = actix::System::new("json-example"); - server::new(|| { + HttpServer::new(|| { App::new() // enable logger - .middleware(middleware::Logger::default()) - .resource("/extractor", |r| { - r.method(http::Method::POST) - .with_config(extract_item, |(cfg,)| { - cfg.limit(4096); // <- limit size of the payload - }) - }) - .resource("/extractor2", |r| { - r.method(http::Method::POST) - .with_config(extract_item_limit, |((cfg, _),)| { - cfg.limit(4096); // <- limit size of the payload - }) - }) - .resource("/manual", |r| r.method(http::Method::POST).f(index_manual)) - .resource("/mjsonrust", |r| r.method(http::Method::POST).f(index_mjsonrust)) - .resource("/", |r| r.method(http::Method::POST).f(index)) - }).bind("127.0.0.1:8080") - .unwrap() - .shutdown_timeout(1) - .start(); - - println!("Started http server: 127.0.0.1:8080"); - let _ = sys.run(); + .wrap(middleware::Logger::default()) + .data(web::JsonConfig::default().limit(4096)) // <- limit size of the payload (global configuration) + .service(web::resource("/extractor").route(web::post().to(index))) + .service( + web::resource("/extractor2") + .data(web::JsonConfig::default().limit(1024)) // <- limit size of the payload (resource level) + .route(web::post().to_async(extract_item)), + ) + .service(web::resource("/manual").route(web::post().to_async(index_manual))) + .service( + web::resource("/mjsonrust").route(web::post().to_async(index_mjsonrust)), + ) + .service(web::resource("/").route(web::post().to(index))) + }) + .bind("127.0.0.1:8080")? + .run() } diff --git a/json_error/Cargo.toml b/json_error/Cargo.toml new file mode 100644 index 00000000..59f6ff8e --- /dev/null +++ b/json_error/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "json_error" +version = "0.1.0" +authors = ["Kai Yao "] +edition = "2018" + +[dependencies] +actix = "0.8" +actix-web = "1.0" +failure = "0.1" +futures = "0.1" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" diff --git a/json_error/src/main.rs b/json_error/src/main.rs new file mode 100644 index 00000000..6af1c10f --- /dev/null +++ b/json_error/src/main.rs @@ -0,0 +1,53 @@ +// This example is meant to show how to automatically generate a json error response when something goes wrong. + +use actix::System; +use actix_web::http::StatusCode; +use actix_web::web::{get, resource, HttpRequest, HttpResponse}; +use actix_web::{App, HttpServer, ResponseError}; +use futures::future::err; +use futures::Future; +use serde::Serialize; +use serde_json::{json, to_string_pretty}; +use std::fmt::{Display, Formatter, Result as FmtResult}; +use std::io; + +#[derive(Debug, Serialize)] +struct Error { + msg: String, + status: u16, +} + +impl Display for Error { + fn fmt(&self, f: &mut Formatter) -> FmtResult { + write!(f, "{}", to_string_pretty(self).unwrap()) + } +} + +impl ResponseError for Error { + // builds the actual response to send back when an error occurs + fn render_response(&self) -> HttpResponse { + let err_json = json!({ "error": self.msg }); + HttpResponse::build(StatusCode::from_u16(self.status).unwrap()).json(err_json) + } +} + +fn index(_: HttpRequest) -> impl Future { + err(Error { + msg: "an example error message".to_string(), + status: 400, + }) +} + +fn main() -> io::Result<()> { + let sys = System::new("json_error_example"); + let ip_address = "127.0.0.1:8000"; + + HttpServer::new(|| App::new().service(resource("/").route(get().to_async(index)))) + .bind(ip_address) + .expect("Can not bind to port 8000") + .start(); + + println!("Running server on {}", ip_address); + + sys.run() +} diff --git a/jsonrpc/Cargo.toml b/jsonrpc/Cargo.toml new file mode 100644 index 00000000..8b1a3dcf --- /dev/null +++ b/jsonrpc/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "jsonrpc-example" +version = "0.1.0" +authors = ["mohanson "] +edition = "2018" +workspace = ".." + +[dependencies] +actix = "0.8.2" +actix-web = "1.0.0" +env_logger = "0.6" +futures = "0.1.23" +futures-timer = "0.1" +log = "0.4" +serde = "1.0" +serde_derive = "1.0" +serde_json = "1.0" diff --git a/jsonrpc/README.md b/jsonrpc/README.md new file mode 100644 index 00000000..b9a4ceac --- /dev/null +++ b/jsonrpc/README.md @@ -0,0 +1,34 @@ +A simple demo for building a `JSONRPC over HTTP` server in [actix-web](https://github.com/actix/actix-web). + +# Server + +```sh +$ cargo run +# Starting server on 127.0.0.1:8080 +``` + +# Client + +**curl** + +```sh +$ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0", "method": "ping", "params": [], "id": 1}' http://127.0.0.1:8080 +# {"jsonrpc":"2.0","result":"pong","error":null,"id":1} +``` + + +**python** + +```sh +$ python tests\test_client.py +# {'jsonrpc': '2.0', 'result': 'pong', 'error': None, 'id': 1} +``` + +# Methods + +- `ping`: Pong immeditely +- `wait`: Wait `n` seconds, and then pong +- `get`: Get global count +- `inc`: Increment global count + +See `tests\test_client.py` to get more information. diff --git a/jsonrpc/src/convention.rs b/jsonrpc/src/convention.rs new file mode 100644 index 00000000..300b826d --- /dev/null +++ b/jsonrpc/src/convention.rs @@ -0,0 +1,137 @@ +//! JSON-RPC 2.0 Specification +//! See: https://www.jsonrpc.org/specification +use std::error; +use std::fmt; + +use serde_derive::{Deserialize, Serialize}; +use serde_json::Value; + +pub static JSONRPC_VERSION: &str = "2.0"; + +/// When a rpc call encounters an error, the Response Object MUST contain the +/// error member with a value that is a Object with the following members: +#[derive(Debug, Serialize, Deserialize)] +pub struct ErrorData { + /// A Number that indicates the error type that occurred. This MUST be an integer. + pub code: i32, + + /// A String providing a short description of the error. The message SHOULD be + /// limited to a concise single sentence. + pub message: String, + + /// A Primitive or Structured value that contains additional information + /// about the error. This may be omitted. The value of this member is + /// defined by the Server (e.g. detailed error information, nested errors + /// etc.). + pub data: Value, +} + +impl ErrorData { + pub fn new(code: i32, message: &str) -> Self { + Self { + code, + message: String::from(message), + data: Value::Null, + } + } + + pub fn std(code: i32) -> Self { + match code { + // Invalid JSON was received by the server. An error occurred on the server while parsing the JSON text. + -32700 => ErrorData::new(-32700, "Parse error"), + // The JSON sent is not a valid Request object. + -32600 => ErrorData::new(-32600, "Invalid Request"), + // The method does not exist / is not available. + -32601 => ErrorData::new(-32601, "Method not found"), + // Invalid method parameter(s). + -32602 => ErrorData::new(-32602, "Invalid params"), + // Internal JSON-RPC error. + -32603 => ErrorData::new(-32603, "Internal error"), + // The error codes from and including -32768 to -32000 are reserved for pre-defined errors. Any code within + // this range, but not defined explicitly below is reserved for future use. + _ => panic!("Undefined pre-defined error codes"), + } + } + + /// Prints out the value as JSON string. + pub fn dump(&self) -> String { + serde_json::to_string(self).expect("Should never failed") + } +} + +impl error::Error for ErrorData {} +impl fmt::Display for ErrorData { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "({}, {}, {})", self.code, self.message, self.data) + } +} + +/// A rpc call is represented by sending a Request object to a Server. +#[derive(Debug, Serialize, Deserialize)] +pub struct Request { + /// A String specifying the version of the JSON-RPC protocol. MUST be exactly "2.0". + pub jsonrpc: String, + + /// A String containing the name of the method to be invoked. Method names that begin with the word rpc followed by + /// a period character (U+002E or ASCII 46) are reserved for rpc-internal methods and extensions and MUST NOT be + /// used for anything else. + pub method: String, + + /// A Structured value that holds the parameter values to be used during the invocation of the method. This member + /// MAY be omitted. + pub params: Vec, + + /// An identifier established by the Client that MUST contain a String, Number, or NULL value if included. If it is + /// not included it is assumed to be a notification. The value SHOULD normally not be Null [1] and Numbers SHOULD + /// NOT contain fractional parts. + pub id: Value, +} + +impl Request { + /// Prints out the value as JSON string. + pub fn dump(&self) -> String { + serde_json::to_string(self).expect("Should never failed") + } +} + +/// When a rpc call is made, the Server MUST reply with a Response, except for in the case of Notifications. The +/// Response is expressed as a single JSON Object, with the following members: +#[derive(Debug, Serialize, Deserialize)] +pub struct Response { + /// A String specifying the version of the JSON-RPC protocol. MUST be exactly "2.0". + pub jsonrpc: String, + + /// This member is REQUIRED on success. + /// This member MUST NOT exist if there was an error invoking the method. + /// The value of this member is determined by the method invoked on the Server. + pub result: Value, + + // This member is REQUIRED on error. + // This member MUST NOT exist if there was no error triggered during invocation. + // The value for this member MUST be an Object as defined in section 5.1. + pub error: Option, + + /// This member is REQUIRED. + /// It MUST be the same as the value of the id member in the Request Object. + /// If there was an error in detecting the id in the Request object (e.g. Parse error/Invalid Request), + /// it MUST be Null. + pub id: Value, +} + +impl Response { + /// Prints out the value as JSON string. + pub fn dump(&self) -> String { + serde_json::to_string(self).expect("Should never failed") + } +} + +impl Default for Response { + fn default() -> Self { + Self { + jsonrpc: JSONRPC_VERSION.into(), + result: Value::Null, + error: None, + id: Value::Null, + } + } +} diff --git a/jsonrpc/src/main.rs b/jsonrpc/src/main.rs new file mode 100644 index 00000000..fd4f0951 --- /dev/null +++ b/jsonrpc/src/main.rs @@ -0,0 +1,164 @@ +use std::error; +use std::sync::Arc; +use std::sync::RwLock; +use std::time::Duration; + +use actix_web::{middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer}; +use futures::{future, Future, Stream}; +use futures_timer::Delay; +use serde_json; +use serde_json::Value; + +#[allow(dead_code)] +mod convention; + +/// The main handler for JSONRPC server. +fn rpc_handler( + req: HttpRequest, + payload: web::Payload, +) -> impl Future { + payload.concat2().from_err().and_then(move |body| { + let reqjson: convention::Request = match serde_json::from_slice(body.as_ref()) { + Ok(ok) => ok, + Err(_) => { + let r = convention::Response { + jsonrpc: String::from(convention::JSONRPC_VERSION), + result: Value::Null, + error: Some(convention::ErrorData::std(-32700)), + id: Value::Null, + }; + return Ok(HttpResponse::Ok() + .content_type("application/json") + .body(r.dump())); + } + }; + let app_state = req.app_data().unwrap(); + let mut result = convention::Response::default(); + result.id = reqjson.id.clone(); + + match rpc_select(&app_state, reqjson.method.as_str(), reqjson.params) { + Ok(ok) => result.result = ok, + Err(e) => result.error = Some(e), + } + + Ok(HttpResponse::Ok() + .content_type("application/json") + .body(result.dump())) + }) +} + +fn rpc_select( + app_state: &AppState, + method: &str, + params: Vec, +) -> Result { + match method { + "ping" => { + let r = app_state.network.read().unwrap().ping(); + Ok(Value::from(r)) + } + "wait" => { + if params.len() != 1 || !params[0].is_u64() { + return Err(convention::ErrorData::std(-32602)); + } + match app_state + .network + .read() + .unwrap() + .wait(params[0].as_u64().unwrap()) + .wait() + { + Ok(ok) => Ok(Value::from(ok)), + Err(e) => Err(convention::ErrorData::new(500, &format!("{:?}", e)[..])), + } + } + "get" => { + let r = app_state.network.read().unwrap().get(); + Ok(Value::from(r)) + } + "inc" => { + app_state.network.write().unwrap().inc(); + Ok(Value::Null) + } + _ => Err(convention::ErrorData::std(-32601)), + } +} + +pub trait ImplNetwork { + fn ping(&self) -> String; + fn wait( + &self, + d: u64, + ) -> Box>>; + + fn get(&self) -> u32; + fn inc(&mut self); +} + +pub struct ObjNetwork { + c: u32, +} + +impl ObjNetwork { + fn new() -> Self { + Self { c: 0 } + } +} + +impl ImplNetwork for ObjNetwork { + fn ping(&self) -> String { + String::from("pong") + } + + fn wait( + &self, + d: u64, + ) -> Box>> { + if let Err(e) = Delay::new(Duration::from_secs(d)).wait() { + let e: Box = Box::new(e); + return Box::new(future::err(e)); + }; + Box::new(future::ok(String::from("pong"))) + } + + fn get(&self) -> u32 { + self.c + } + + fn inc(&mut self) { + self.c += 1; + } +} + +#[derive(Clone)] +pub struct AppState { + network: Arc>, +} + +impl AppState { + pub fn new(network: Arc>) -> Self { + Self { network } + } +} + +fn main() { + std::env::set_var("RUST_LOG", "info"); + env_logger::init(); + + let network = Arc::new(RwLock::new(ObjNetwork::new())); + + let sys = actix::System::new("actix_jrpc"); + HttpServer::new(move || { + let app_state = AppState::new(network.clone()); + App::new() + .data(app_state) + .wrap(middleware::Logger::default()) + .service(web::resource("/").route(web::post().to_async(rpc_handler))) + }) + .bind("127.0.0.1:8080") + .unwrap() + .workers(1) + .start(); + + let _ = sys.run(); +} diff --git a/jsonrpc/tests/test_client.py b/jsonrpc/tests/test_client.py new file mode 100644 index 00000000..1df92bbd --- /dev/null +++ b/jsonrpc/tests/test_client.py @@ -0,0 +1,38 @@ +import requests + +print('ping: pong immediately') +r = requests.post('http://127.0.0.1:8080/', json={ + 'jsonrpc': '2.0', + 'method': 'ping', + 'params': [], + 'id': 1 +}) +print(r.json()) + + +print('ping: pong after 4 secs') +r = requests.post('http://127.0.0.1:8080/', json={ + 'jsonrpc': '2.0', + 'method': 'wait', + 'params': [4], + 'id': 1 +}) +print(r.json()) + +for i in range(10): + print(f'inc {i:>02}') + r = requests.post('http://127.0.0.1:8080/', json={ + 'jsonrpc': '2.0', + 'method': 'inc', + 'params': [], + 'id': 1 + }) + +print(f'get') +r = requests.post('http://127.0.0.1:8080/', json={ + 'jsonrpc': '2.0', + 'method': 'get', + 'params': [], + 'id': 1 +}) +print(r.json()) diff --git a/juniper/Cargo.toml b/juniper/Cargo.toml index d19b3527..d3c02049 100644 --- a/juniper/Cargo.toml +++ b/juniper/Cargo.toml @@ -2,17 +2,14 @@ name = "juniper-example" version = "0.1.0" authors = ["pyros2097 "] -workspace = "../" +workspace = ".." +edition = "2018" [dependencies] -env_logger = "0.5" - -actix = "0.7" -actix-web = "0.7" - +actix-web = "1.0.0" +env_logger = "0.6" futures = "0.1" serde = "1.0" serde_json = "1.0" serde_derive = "1.0" - juniper = "0.9.2" diff --git a/juniper/src/main.rs b/juniper/src/main.rs index d06cc5e7..2615d34e 100644 --- a/juniper/src/main.rs +++ b/juniper/src/main.rs @@ -1,107 +1,59 @@ //! Actix web juniper example //! //! A simple example integrating juniper in actix-web -extern crate serde; -extern crate serde_json; -#[macro_use] -extern crate serde_derive; +use std::io; +use std::sync::Arc; + #[macro_use] extern crate juniper; -extern crate actix; -extern crate actix_web; -extern crate env_logger; -extern crate futures; -use actix::prelude::*; -use actix_web::{ - http, middleware, server, App, AsyncResponder, Error, FutureResponse, HttpRequest, - HttpResponse, Json, State, -}; +use actix_web::{middleware, web, App, Error, HttpResponse, HttpServer}; use futures::future::Future; use juniper::http::graphiql::graphiql_source; use juniper::http::GraphQLRequest; mod schema; -use schema::create_schema; -use schema::Schema; +use crate::schema::{create_schema, Schema}; -struct AppState { - executor: Addr, -} - -#[derive(Serialize, Deserialize)] -pub struct GraphQLData(GraphQLRequest); - -impl Message for GraphQLData { - type Result = Result; -} - -pub struct GraphQLExecutor { - schema: std::sync::Arc, -} - -impl GraphQLExecutor { - fn new(schema: std::sync::Arc) -> GraphQLExecutor { - GraphQLExecutor { schema: schema } - } -} - -impl Actor for GraphQLExecutor { - type Context = SyncContext; -} - -impl Handler for GraphQLExecutor { - type Result = Result; - - fn handle(&mut self, msg: GraphQLData, _: &mut Self::Context) -> Self::Result { - let res = msg.0.execute(&self.schema, &()); - let res_text = serde_json::to_string(&res)?; - Ok(res_text) - } -} - -fn graphiql(_req: &HttpRequest) -> Result { +fn graphiql() -> HttpResponse { let html = graphiql_source("http://127.0.0.1:8080/graphql"); - Ok(HttpResponse::Ok() + HttpResponse::Ok() .content_type("text/html; charset=utf-8") - .body(html)) + .body(html) } fn graphql( - (st, data): (State, Json), -) -> FutureResponse { - st.executor - .send(data.0) - .from_err() - .and_then(|res| match res { - Ok(user) => Ok(HttpResponse::Ok() - .content_type("application/json") - .body(user)), - Err(_) => Ok(HttpResponse::InternalServerError().into()), - }) - .responder() + st: web::Data>, + data: web::Json, +) -> impl Future { + web::block(move || { + let res = data.execute(&st, &()); + Ok::<_, serde_json::error::Error>(serde_json::to_string(&res)?) + }) + .map_err(Error::from) + .and_then(|user| { + Ok(HttpResponse::Ok() + .content_type("application/json") + .body(user)) + }) } -fn main() { - ::std::env::set_var("RUST_LOG", "actix_web=info"); +fn main() -> io::Result<()> { + std::env::set_var("RUST_LOG", "actix_web=info"); env_logger::init(); - let sys = actix::System::new("juniper-example"); + // Create Juniper schema let schema = std::sync::Arc::new(create_schema()); - let addr = SyncArbiter::start(3, move || GraphQLExecutor::new(schema.clone())); // Start http server - server::new(move || { - App::with_state(AppState{executor: addr.clone()}) - // enable logger - .middleware(middleware::Logger::default()) - .resource("/graphql", |r| r.method(http::Method::POST).with(graphql)) - .resource("/graphiql", |r| r.method(http::Method::GET).h(graphiql)) - }).bind("127.0.0.1:8080") - .unwrap() - .start(); - - println!("Started http server: 127.0.0.1:8080"); - let _ = sys.run(); + HttpServer::new(move || { + App::new() + .data(schema.clone()) + .wrap(middleware::Logger::default()) + .service(web::resource("/graphql").route(web::post().to_async(graphql))) + .service(web::resource("/graphiql").route(web::get().to(graphiql))) + }) + .bind("127.0.0.1:8080")? + .run() } diff --git a/middleware/Cargo.toml b/middleware/Cargo.toml index b14354f4..da904716 100644 --- a/middleware/Cargo.toml +++ b/middleware/Cargo.toml @@ -2,7 +2,12 @@ name = "middleware-example" version = "0.1.0" authors = ["Gorm Casper "] +edition = "2018" +workspace = ".." [dependencies] -actix = "0.7" -actix-web = "0.7" +actix-service = "0.4.1" +actix-web = "1.0.0" +futures = "0.1.25" +env_logger = "0.6" +bytes = "0.4" diff --git a/middleware/README.md b/middleware/README.md index 056ce654..401b9fd9 100644 --- a/middleware/README.md +++ b/middleware/README.md @@ -1,7 +1,11 @@ -## Middleware example +# middleware examples + +This example showcases a bunch of different uses of middlewares. See also the [Middleware guide](https://actix.rs/docs/middleware/).. + +## Usage ```bash -cd form +cd middleware cargo run # Started http server: 127.0.0.1:8080 ``` @@ -9,3 +13,17 @@ cargo run Look in `src/main.rs` and comment the different middlewares in/out to see how they function. +## Middlewares + +### redirect::CheckLogin + +A middleware implementing a request guard which sketches a rough approximation of what a login could look like. + +### read_body::Logging + +A middleware demonstrating how to read out the incoming request body. + +### simple::SayHi + +A minimal middleware demonstrating the sequence of operations in an actix middleware. +There is a second version of the same middleware using `wrap_fn` which shows how easily a middleware can be implemented in actix. diff --git a/middleware/src/main.rs b/middleware/src/main.rs index 539a3076..49dfb98f 100644 --- a/middleware/src/main.rs +++ b/middleware/src/main.rs @@ -1,29 +1,40 @@ -extern crate actix; -extern crate actix_web; - -use actix_web::{server, App}; +use actix_web::{web, App, HttpServer}; +use actix_service::Service; +use futures::future::Future; #[allow(dead_code)] mod redirect; #[allow(dead_code)] +mod read_body; +#[allow(dead_code)] mod simple; -fn main() { - let sys = actix::System::new("middleware-example"); +fn main() -> std::io::Result<()> { + std::env::set_var("RUST_LOG", "actix_web=debug"); + env_logger::init(); - let _addr = server::new(|| { + HttpServer::new(|| { App::new() - .middleware(simple::SayHi) - // .middleware(redirect::CheckLogin) - .resource("/login", |r| { - r.f(|_| "You are on /login. Go to src/redirect.rs to change this behavior.") - }) - .resource("/", |r| { - r.f(|_| "Hello, middleware! Check the console where the server is run.") - }) - }).bind("127.0.0.1:8080") - .unwrap() - .start(); + .wrap(redirect::CheckLogin) + .wrap(read_body::Logging) + .wrap(simple::SayHi) + .wrap_fn(|req, srv| { + println!("Hi from start. You requested: {}", req.path()); - let _ = sys.run(); + srv.call(req).map(|res| { + println!("Hi from response"); + res + }) + }) + .service(web::resource("/login").to(|| { + "You are on /login. Go to src/redirect.rs to change this behavior." + })) + .service( + web::resource("/").to(|| { + "Hello, middleware! Check the console where the server is run." + }), + ) + }) + .bind("127.0.0.1:8080")? + .run() } diff --git a/middleware/src/read_body.rs b/middleware/src/read_body.rs new file mode 100644 index 00000000..48aed2cc --- /dev/null +++ b/middleware/src/read_body.rs @@ -0,0 +1,70 @@ +use actix_service::{Service, Transform}; +use actix_web::error::PayloadError; +use actix_web::{dev::ServiceRequest, dev::ServiceResponse, Error, HttpMessage}; +use bytes::BytesMut; +use futures::future::{ok, FutureResult}; +use futures::stream::Stream; +use futures::{Future, Poll}; +use std::cell::RefCell; +use std::rc::Rc; + +pub struct Logging; + +impl Transform for Logging +where + S: Service, Error = Error>, + S::Future: 'static, + B: 'static, +{ + type Request = ServiceRequest; + type Response = ServiceResponse; + type Error = Error; + type InitError = (); + type Transform = LoggingMiddleware; + type Future = FutureResult; + + fn new_transform(&self, service: S) -> Self::Future { + ok(LoggingMiddleware { + service: Rc::new(RefCell::new(service)), + }) + } +} + +pub struct LoggingMiddleware { + // This is special: We need this to avoid lifetime issues. + service: Rc>, +} + +impl Service for LoggingMiddleware +where + S: Service, Error = Error> + + 'static, + S::Future: 'static, + B: 'static, +{ + type Request = ServiceRequest; + type Response = ServiceResponse; + type Error = Error; + type Future = Box>; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + self.service.poll_ready() + } + + fn call(&mut self, mut req: ServiceRequest) -> Self::Future { + let mut svc = self.service.clone(); + + Box::new( + req.take_payload() + .fold(BytesMut::new(), move |mut body, chunk| { + body.extend_from_slice(&chunk); + Ok::<_, PayloadError>(body) + }) + .map_err(|e| e.into()) + .and_then(move |bytes| { + println!("request body: {:?}", bytes); + svc.call(req).and_then(|res| Ok(res)) + }), + ) + } +} diff --git a/middleware/src/redirect.rs b/middleware/src/redirect.rs index a0d5de6c..f5c0e61e 100644 --- a/middleware/src/redirect.rs +++ b/middleware/src/redirect.rs @@ -1,28 +1,64 @@ -extern crate actix_web; - -use actix_web::middleware::{Middleware, Started}; -use actix_web::{http, HttpRequest, HttpResponse, Result}; +use actix_service::{Service, Transform}; +use actix_web::dev::{ServiceRequest, ServiceResponse}; +use actix_web::{http, Error, HttpResponse}; +use futures::future::{ok, Either, FutureResult}; +use futures::Poll; pub struct CheckLogin; -impl Middleware for CheckLogin { - // We only need to hook into the `start` for this middleware. - fn start(&self, req: &HttpRequest) -> Result { +impl Transform for CheckLogin +where + S: Service, Error = Error>, + S::Future: 'static, +{ + type Request = ServiceRequest; + type Response = ServiceResponse; + type Error = Error; + type InitError = (); + type Transform = CheckLoginMiddleware; + type Future = FutureResult; + + fn new_transform(&self, service: S) -> Self::Future { + ok(CheckLoginMiddleware { service }) + } +} +pub struct CheckLoginMiddleware { + service: S, +} + +impl Service for CheckLoginMiddleware +where + S: Service, Error = Error>, + S::Future: 'static, +{ + type Request = ServiceRequest; + type Response = ServiceResponse; + type Error = Error; + type Future = Either>; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + self.service.poll_ready() + } + + fn call(&mut self, req: ServiceRequest) -> Self::Future { + // We only need to hook into the `start` for this middleware. + let is_logged_in = false; // Change this to see the change in outcome in the browser if is_logged_in { - return Ok(Started::Done); + Either::A(self.service.call(req)) + } else { + // Don't forward to /login if we are already on /login + if req.path() == "/login" { + Either::A(self.service.call(req)) + } else { + Either::B(ok(req.into_response( + HttpResponse::Found() + .header(http::header::LOCATION, "/login") + .finish() + .into_body(), + ))) + } } - - // Don't forward to /login if we are already on /login - if req.path() == "/login" { - return Ok(Started::Done); - } - - Ok(Started::Response( - HttpResponse::Found() - .header(http::header::LOCATION, "/login") - .finish(), - )) } } diff --git a/middleware/src/simple.rs b/middleware/src/simple.rs index 254f263d..a96e156b 100644 --- a/middleware/src/simple.rs +++ b/middleware/src/simple.rs @@ -1,25 +1,60 @@ -extern crate actix_web; +use actix_service::{Service, Transform}; +use actix_web::{dev::ServiceRequest, dev::ServiceResponse, Error}; +use futures::future::{ok, FutureResult}; +use futures::{Future, Poll}; -use actix_web::middleware::{Finished, Middleware, Response, Started}; -use actix_web::{HttpRequest, HttpResponse, Result}; - -// Middleware can get called at three stages during the request/response handling. Below is a -// struct that implements all three of them. +// There are two steps in middleware processing. +// 1. Middleware initialization, middleware factory gets called with +// next service in chain as parameter. +// 2. Middleware's call method gets called with normal request. pub struct SayHi; -impl Middleware for SayHi { - fn start(&self, req: &HttpRequest) -> Result { - println!("Hi from start. You requested: {}", req.path()); - Ok(Started::Done) - } +// Middleware factory is `Transform` trait from actix-service crate +// `S` - type of the next service +// `B` - type of response's body +impl Transform for SayHi +where + S: Service, Error = Error>, + S::Future: 'static, + B: 'static, +{ + type Request = ServiceRequest; + type Response = ServiceResponse; + type Error = Error; + type InitError = (); + type Transform = SayHiMiddleware; + type Future = FutureResult; - fn response(&self, _req: &HttpRequest, resp: HttpResponse) -> Result { - println!("Hi from response"); - Ok(Response::Done(resp)) - } - - fn finish(&self, _req: &HttpRequest, _resp: &HttpResponse) -> Finished { - println!("Hi from finish"); - Finished::Done + fn new_transform(&self, service: S) -> Self::Future { + ok(SayHiMiddleware { service }) + } +} + +pub struct SayHiMiddleware { + service: S, +} + +impl Service for SayHiMiddleware +where + S: Service, Error = Error>, + S::Future: 'static, + B: 'static, +{ + type Request = ServiceRequest; + type Response = ServiceResponse; + type Error = Error; + type Future = Box>; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + self.service.poll_ready() + } + + fn call(&mut self, req: ServiceRequest) -> Self::Future { + println!("Hi from start. You requested: {}", req.path()); + + Box::new(self.service.call(req).and_then(|res| { + println!("Hi from response"); + Ok(res) + })) } } diff --git a/multipart/Cargo.toml b/multipart/Cargo.toml index 771e28f7..00089b01 100644 --- a/multipart/Cargo.toml +++ b/multipart/Cargo.toml @@ -2,15 +2,16 @@ name = "multipart-example" version = "0.1.0" authors = ["Nikolay Kim "] -workspace = "../" +workspace = ".." +edition = "2018" [[bin]] name = "multipart" path = "src/main.rs" [dependencies] -env_logger = "*" -futures = "0.1" +actix-web = "1.0.0" +actix-multipart = "0.1.1" -actix = "0.7" -actix-web = "0.7" +env_logger = "0.6" +futures = "0.1.25" diff --git a/multipart/README.md b/multipart/README.md index 0da2dbf8..5eb57b38 100644 --- a/multipart/README.md +++ b/multipart/README.md @@ -12,13 +12,14 @@ cargo run (or ``cargo watch -x run``) # Started http server: 127.0.0.1:8080 ``` -### client +### browser -- ``pip install aiohttp`` -- ``python client.py`` +- go to ``http://localhost:8080`` +- upload file +- you should see the action reflected in server console + +### client (optional) + +- ``./client.sh`` - you must see in server console multipart fields -if ubuntu : - -- ``pip3 install aiohttp`` -- ``python3 client.py`` diff --git a/multipart/client.py b/multipart/client.py deleted file mode 100644 index 95905760..00000000 --- a/multipart/client.py +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env python3 - -# This script could be used for actix-web multipart example test -# just start server and run client.py - -import asyncio -import aiohttp - -async def req1(): - with aiohttp.MultipartWriter() as writer: - writer.append('test') - writer.append_json({'passed': True}) - - resp = await aiohttp.ClientSession().request( - "post", 'http://localhost:8080/multipart', - data=writer, headers=writer.headers) - print(resp) - assert 200 == resp.status - - -async def req2(): - with aiohttp.MultipartWriter() as writer: - writer.append('test') - writer.append_json({'passed': True}) - writer.append(open('src/main.rs')) - - resp = await aiohttp.ClientSession().request( - "post", 'http://localhost:8080/multipart', - data=writer, headers=writer.headers) - print(resp) - assert 200 == resp.status - - -loop = asyncio.get_event_loop() -loop.run_until_complete(req1()) -loop.run_until_complete(req2()) diff --git a/multipart/client.sh b/multipart/client.sh new file mode 100755 index 00000000..10d82343 --- /dev/null +++ b/multipart/client.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +function SubmitFile () { + curl -X POST \ + -H "Content-Type: multipart/related" \ + --form "data=@example.png;type=image/png" http://localhost:8080 +} + +SubmitFile & SubmitFile & SubmitFile & +SubmitFile & SubmitFile & SubmitFile & +SubmitFile & SubmitFile & SubmitFile diff --git a/multipart/example.png b/multipart/example.png new file mode 100644 index 00000000..28991040 Binary files /dev/null and b/multipart/example.png differ diff --git a/multipart/src/main.rs b/multipart/src/main.rs index 6cea2536..791b7941 100644 --- a/multipart/src/main.rs +++ b/multipart/src/main.rs @@ -1,45 +1,43 @@ -#![allow(unused_variables)] -extern crate actix; -extern crate actix_web; -extern crate env_logger; -extern crate futures; - use std::cell::Cell; use std::fs; use std::io::Write; -use actix_web::{ - dev, error, http, middleware, multipart, server, App, Error, FutureResponse, - HttpMessage, HttpRequest, HttpResponse, -}; - -use futures::future; +use actix_multipart::{Field, Multipart, MultipartError}; +use actix_web::{error, middleware, web, App, Error, HttpResponse, HttpServer}; +use futures::future::{err, Either}; use futures::{Future, Stream}; pub struct AppState { pub counter: Cell, } -pub fn save_file( - field: multipart::Field, -) -> Box> { +pub fn save_file(field: Field) -> impl Future { let file_path_string = "upload.png"; - let mut file = match fs::File::create(file_path_string) { + let file = match fs::File::create(file_path_string) { Ok(file) => file, - Err(e) => return Box::new(future::err(error::ErrorInternalServerError(e))), + Err(e) => return Either::A(err(error::ErrorInternalServerError(e))), }; - Box::new( + Either::B( field - .fold(0i64, move |acc, bytes| { - let rt = file - .write_all(bytes.as_ref()) - .map(|_| acc + bytes.len() as i64) - .map_err(|e| { + .fold((file, 0i64), move |(mut file, mut acc), bytes| { + // fs operations are blocking, we have to execute writes + // on threadpool + web::block(move || { + file.write_all(bytes.as_ref()).map_err(|e| { println!("file.write_all failed: {:?}", e); - error::MultipartError::Payload(error::PayloadError::Io(e)) - }); - future::result(rt) + MultipartError::Payload(error::PayloadError::Io(e)) + })?; + acc += bytes.len() as i64; + Ok((file, acc)) + }) + .map_err(|e: error::BlockingError| { + match e { + error::BlockingError::Error(e) => e, + error::BlockingError::Canceled => MultipartError::Incomplete, + } + }) }) + .map(|(_, acc)| acc) .map_err(|e| { println!("save_file failed, {:?}", e); error::ErrorInternalServerError(e) @@ -47,39 +45,26 @@ pub fn save_file( ) } -pub fn handle_multipart_item( - item: multipart::MultipartItem, -) -> Box> { - match item { - multipart::MultipartItem::Field(field) => { - Box::new(save_file(field).into_stream()) - } - multipart::MultipartItem::Nested(mp) => Box::new( - mp.map_err(error::ErrorInternalServerError) - .map(handle_multipart_item) - .flatten(), - ), - } +pub fn upload( + multipart: Multipart, + counter: web::Data>, +) -> impl Future { + counter.set(counter.get() + 1); + println!("{:?}", counter.get()); + + multipart + .map_err(error::ErrorInternalServerError) + .map(|field| save_file(field).into_stream()) + .flatten() + .collect() + .map(|sizes| HttpResponse::Ok().json(sizes)) + .map_err(|e| { + println!("failed: {}", e); + e + }) } -pub fn upload(req: HttpRequest) -> FutureResponse { - req.state().counter.set(req.state().counter.get() + 1); - println!("{:?}", req.state().counter.get()); - Box::new( - req.multipart() - .map_err(error::ErrorInternalServerError) - .map(handle_multipart_item) - .flatten() - .collect() - .map(|sizes| HttpResponse::Ok().json(sizes)) - .map_err(|e| { - println!("failed: {}", e); - e - }), - ) -} - -fn index(_req: HttpRequest) -> Result { +fn index() -> HttpResponse { let html = r#" Upload Test @@ -90,26 +75,23 @@ fn index(_req: HttpRequest) -> Result { "#; - Ok(HttpResponse::Ok().body(html)) + HttpResponse::Ok().body(html) } -fn main() { - ::std::env::set_var("RUST_LOG", "actix_web=info"); +fn main() -> std::io::Result<()> { + std::env::set_var("RUST_LOG", "actix_server=info,actix_web=info"); env_logger::init(); - let sys = actix::System::new("multipart-example"); - server::new(|| { - App::with_state(AppState { - counter: Cell::new(0), - }).middleware(middleware::Logger::default()) - .resource("/", |r| { - r.method(http::Method::GET).with(index); - r.method(http::Method::POST).with(upload); - }) - }).bind("127.0.0.1:8080") - .unwrap() - .start(); - - println!("Starting http server: 127.0.0.1:8080"); - let _ = sys.run(); + HttpServer::new(|| { + App::new() + .data(Cell::new(0usize)) + .wrap(middleware::Logger::default()) + .service( + web::resource("/") + .route(web::get().to(index)) + .route(web::post().to_async(upload)), + ) + }) + .bind("127.0.0.1:8080")? + .run() } diff --git a/protobuf/Cargo.toml b/protobuf/Cargo.toml index 130581e8..b9f69d92 100644 --- a/protobuf/Cargo.toml +++ b/protobuf/Cargo.toml @@ -2,16 +2,17 @@ name = "protobuf-example" version = "0.1.0" authors = ["kingxsp "] -workspace = "../" +workspace = ".." +edition = "2018" [dependencies] bytes = "0.4" futures = "0.1" -failure = "0.1" -env_logger = "*" +env_logger = "0.6" +derive_more = "0.14" +prost = "0.4.0" +prost-derive = "0.4.0" -prost = "0.2.0" -prost-derive = "0.2.0" - -actix = "0.7" -actix-web = "0.7" +actix = "0.8.2" +actix-web = "1.0.0" +actix-protobuf = "0.4.0" diff --git a/protobuf/client.py b/protobuf/client.py index ab91365d..965f11b4 100644 --- a/protobuf/client.py +++ b/protobuf/client.py @@ -46,7 +46,7 @@ async def fetch(session): obj = test_pb2.MyObj() obj.number = 9 obj.name = 'USB' - async with session.post('http://localhost:8080/', data=obj.SerializeToString(), + async with session.post('http://localhost:8081/', data=obj.SerializeToString(), headers={"content-type": "application/protobuf"}) as resp: print(resp.status) data = await resp.read() diff --git a/protobuf/src/main.rs b/protobuf/src/main.rs index aaae1f18..7cce23b0 100644 --- a/protobuf/src/main.rs +++ b/protobuf/src/main.rs @@ -1,23 +1,16 @@ extern crate actix; +extern crate actix_protobuf; extern crate actix_web; extern crate bytes; -extern crate futures; -#[macro_use] -extern crate failure; extern crate env_logger; extern crate prost; #[macro_use] extern crate prost_derive; -use actix_web::{ - http, middleware, server, App, AsyncResponder, Error, HttpRequest, HttpResponse, -}; -use futures::Future; +use actix_protobuf::*; +use actix_web::*; -mod protobuf; -use protobuf::ProtoBufResponseBuilder; - -#[derive(Clone, Debug, PartialEq, Message)] +#[derive(Clone, PartialEq, Message)] pub struct MyObj { #[prost(int32, tag = "1")] pub number: i32, @@ -25,31 +18,26 @@ pub struct MyObj { pub name: String, } -/// This handler uses `ProtoBufMessage` for loading protobuf object. -fn index(req: &HttpRequest) -> Box> { - protobuf::ProtoBufMessage::new(req) - .from_err() // convert all errors into `Error` - .and_then(|val: MyObj| { - println!("model: {:?}", val); - Ok(HttpResponse::Ok().protobuf(val)?) // <- send response - }) - .responder() +fn index(msg: ProtoBuf) -> Result { + println!("model: {:?}", msg); + HttpResponse::Ok().protobuf(msg.0) // <- send response } fn main() { - ::std::env::set_var("RUST_LOG", "actix_web=info"); + ::std::env::set_var("RUST_LOG", "actix_web=info,actix_server=info"); env_logger::init(); let sys = actix::System::new("protobuf-example"); - server::new(|| { + HttpServer::new(|| { App::new() - .middleware(middleware::Logger::default()) - .resource("/", |r| r.method(http::Method::POST).f(index)) - }).bind("127.0.0.1:8080") - .unwrap() - .shutdown_timeout(1) - .start(); + .wrap(middleware::Logger::default()) + .service(web::resource("/").route(web::post().to(index))) + }) + .bind("127.0.0.1:8081") + .unwrap() + .shutdown_timeout(1) + .start(); - println!("Started http server: 127.0.0.1:8080"); + println!("Started http server: 127.0.0.1:8081"); let _ = sys.run(); } diff --git a/protobuf/src/protobuf.rs b/protobuf/src/protobuf.rs deleted file mode 100644 index a9c22651..00000000 --- a/protobuf/src/protobuf.rs +++ /dev/null @@ -1,118 +0,0 @@ -use bytes::BytesMut; -use futures::{Future, Poll, Stream}; - -use bytes::IntoBuf; -use prost::DecodeError as ProtoBufDecodeError; -use prost::EncodeError as ProtoBufEncodeError; -use prost::Message; - -use actix_web::dev::HttpResponseBuilder; -use actix_web::error::{Error, PayloadError, ResponseError}; -use actix_web::http::header::CONTENT_TYPE; -use actix_web::{HttpMessage, HttpRequest, HttpResponse, Responder}; - -#[derive(Fail, Debug)] -pub enum ProtoBufPayloadError { - /// Payload size is bigger than 256k - #[fail(display = "Payload size is bigger than 256k")] - Overflow, - /// Content type error - #[fail(display = "Content type error")] - ContentType, - /// Serialize error - #[fail(display = "ProtoBud serialize error: {}", _0)] - Serialize(#[cause] ProtoBufEncodeError), - /// Deserialize error - #[fail(display = "ProtoBud deserialize error: {}", _0)] - Deserialize(#[cause] ProtoBufDecodeError), - /// Payload error - #[fail(display = "Error that occur during reading payload: {}", _0)] - Payload(#[cause] PayloadError), -} - -impl ResponseError for ProtoBufPayloadError { - fn error_response(&self) -> HttpResponse { - match *self { - ProtoBufPayloadError::Overflow => HttpResponse::PayloadTooLarge().into(), - _ => HttpResponse::BadRequest().into(), - } - } -} - -impl From for ProtoBufPayloadError { - fn from(err: PayloadError) -> ProtoBufPayloadError { - ProtoBufPayloadError::Payload(err) - } -} - -impl From for ProtoBufPayloadError { - fn from(err: ProtoBufDecodeError) -> ProtoBufPayloadError { - ProtoBufPayloadError::Deserialize(err) - } -} - -#[derive(Debug)] -pub struct ProtoBuf(pub T); - -impl Responder for ProtoBuf { - type Item = HttpResponse; - type Error = Error; - - fn respond_to(self, _: &HttpRequest) -> Result { - let mut buf = Vec::new(); - self.0 - .encode(&mut buf) - .map_err(|e| Error::from(ProtoBufPayloadError::Serialize(e))) - .and_then(|()| { - Ok(HttpResponse::Ok() - .content_type("application/protobuf") - .body(buf) - .into()) - }) - } -} - -pub struct ProtoBufMessage { - fut: Box>, -} - -impl ProtoBufMessage { - /// Create `ProtoBufMessage` for request. - pub fn new(req: &HttpRequest) -> Self { - let fut = req - .payload() - .map_err(|e| ProtoBufPayloadError::Payload(e)) - .fold(BytesMut::new(), move |mut body, chunk| { - body.extend_from_slice(&chunk); - Ok::<_, ProtoBufPayloadError>(body) - }) - .and_then(|body| Ok(::decode(&mut body.into_buf())?)); - - ProtoBufMessage { fut: Box::new(fut) } - } -} - -impl Future for ProtoBufMessage where { - type Item = U; - type Error = ProtoBufPayloadError; - - fn poll(&mut self) -> Poll { - self.fut.poll() - } -} - -pub trait ProtoBufResponseBuilder { - fn protobuf(&mut self, value: T) -> Result; -} - -impl ProtoBufResponseBuilder for HttpResponseBuilder { - fn protobuf(&mut self, value: T) -> Result { - self.header(CONTENT_TYPE, "application/protobuf"); - - let mut body = Vec::new(); - value - .encode(&mut body) - .map_err(|e| ProtoBufPayloadError::Serialize(e))?; - Ok(self.body(body)) - } -} diff --git a/r2d2/Cargo.toml b/r2d2/Cargo.toml index 884d84c4..bfc51465 100644 --- a/r2d2/Cargo.toml +++ b/r2d2/Cargo.toml @@ -2,20 +2,17 @@ name = "r2d2-example" version = "0.1.0" authors = ["Nikolay Kim "] -workspace = "../" +edition = "2018" +workspace = ".." [dependencies] -env_logger = "0.5" - -actix = "0.7" -actix-web = "0.7" +actix-rt = "0.2" +actix-web = "1.0.0" futures = "0.1" -uuid = { version = "0.5", features = ["serde", "v4"] } -serde = "1.0" -serde_json = "1.0" -serde_derive = "1.0" +env_logger = "0.6" +uuid = { version = "0.7", features = ["v4"] } -r2d2 = "*" -r2d2_sqlite = "*" -rusqlite = "*" +r2d2 = "0.8" +r2d2_sqlite = "0.8" +rusqlite = "0.16" diff --git a/r2d2/src/db.rs b/r2d2/src/db.rs deleted file mode 100644 index 5df9b3ff..00000000 --- a/r2d2/src/db.rs +++ /dev/null @@ -1,44 +0,0 @@ -//! Db executor actor -use actix::prelude::*; -use actix_web::*; -use r2d2::Pool; -use r2d2_sqlite::SqliteConnectionManager; -use std::io; -use uuid; - -/// This is db executor actor. We are going to run 3 of them in parallel. -pub struct DbExecutor(pub Pool); - -/// This is only message that this actor can handle, but it is easy to extend -/// number of messages. -pub struct CreateUser { - pub name: String, -} - -impl Message for CreateUser { - type Result = Result; -} - -impl Actor for DbExecutor { - type Context = SyncContext; -} - -impl Handler for DbExecutor { - type Result = Result; - - fn handle(&mut self, msg: CreateUser, _: &mut Self::Context) -> Self::Result { - let conn = self.0.get().unwrap(); - - let uuid = format!("{}", uuid::Uuid::new_v4()); - conn.execute( - "INSERT INTO users (id, name) VALUES ($1, $2)", - &[&uuid, &msg.name], - ).unwrap(); - - Ok(conn - .query_row("SELECT name FROM users WHERE id=$1", &[&uuid], |row| { - row.get(0) - }) - .map_err(|_| io::Error::new(io::ErrorKind::Other, "db error"))?) - } -} diff --git a/r2d2/src/main.rs b/r2d2/src/main.rs index e3cdb421..c323784c 100644 --- a/r2d2/src/main.rs +++ b/r2d2/src/main.rs @@ -1,68 +1,56 @@ //! Actix web r2d2 example -extern crate actix; -extern crate actix_web; -extern crate env_logger; -extern crate futures; -extern crate r2d2; -extern crate r2d2_sqlite; -extern crate rusqlite; -extern crate serde; -extern crate serde_json; -extern crate uuid; +use std::io; -use actix::prelude::*; -use actix_web::{ - http, middleware, server, App, AsyncResponder, Error, HttpRequest, HttpResponse, -}; -use futures::future::Future; +use actix_web::{middleware, web, App, Error, HttpResponse, HttpServer}; +use futures::Future; +use r2d2::Pool; use r2d2_sqlite::SqliteConnectionManager; +use uuid; -mod db; -use db::{CreateUser, DbExecutor}; +/// Async request handler. Ddb pool is stored in application state. +fn index( + path: web::Path, + db: web::Data>, +) -> impl Future { + // execute sync code in threadpool + web::block(move || { + let conn = db.get().unwrap(); -/// State with DbExecutor address -struct State { - db: Addr, + let uuid = format!("{}", uuid::Uuid::new_v4()); + conn.execute( + "INSERT INTO users (id, name) VALUES ($1, $2)", + &[&uuid, &path.into_inner()], + ) + .unwrap(); + + conn.query_row("SELECT name FROM users WHERE id=$1", &[&uuid], |row| { + row.get::<_, String>(0) + }) + }) + .then(|res| match res { + Ok(user) => Ok(HttpResponse::Ok().json(user)), + Err(_) => Ok(HttpResponse::InternalServerError().into()), + }) } -/// Async request handler -fn index(req: &HttpRequest) -> Box> { - let name = &req.match_info()["name"]; - - req.state() - .db - .send(CreateUser { - name: name.to_owned(), - }) - .from_err() - .and_then(|res| match res { - Ok(user) => Ok(HttpResponse::Ok().json(user)), - Err(_) => Ok(HttpResponse::InternalServerError().into()), - }) - .responder() -} - -fn main() { - ::std::env::set_var("RUST_LOG", "actix_web=debug"); +fn main() -> io::Result<()> { + std::env::set_var("RUST_LOG", "actix_web=debug"); env_logger::init(); - let sys = actix::System::new("r2d2-example"); + let sys = actix_rt::System::new("r2d2-example"); // r2d2 pool let manager = SqliteConnectionManager::file("test.db"); let pool = r2d2::Pool::new(manager).unwrap(); - // Start db executor actors - let addr = SyncArbiter::start(3, move || DbExecutor(pool.clone())); + // start http server + HttpServer::new(move || { + App::new() + .data(pool.clone()) // <- store db pool in app state + .wrap(middleware::Logger::default()) + .route("/{name}", web::get().to_async(index)) + }) + .bind("127.0.0.1:8080")? + .start(); - // Start http server - server::new(move || { - App::with_state(State{db: addr.clone()}) - // enable logger - .middleware(middleware::Logger::default()) - .resource("/{name}", |r| r.method(http::Method::GET).a(index)) - }).bind("127.0.0.1:8080") - .unwrap() - .start(); - - let _ = sys.run(); + sys.run() } diff --git a/redis-session/Cargo.toml b/redis-session/Cargo.toml index 8b6152a6..edd40825 100644 --- a/redis-session/Cargo.toml +++ b/redis-session/Cargo.toml @@ -1,11 +1,18 @@ [package] -name = "redis-session" +name = "redis_session" version = "0.1.0" authors = ["Nikolay Kim "] -workspace = "../" +workspace = ".." +edition = "2018" [dependencies] -env_logger = "0.5" -actix = "0.7" -actix-web = "0.7.3" -actix-redis = { version = "0.5.1", features = ["web"] } +actix-web = "1.0.3" +actix-session = "0.2.0" +actix-redis = { version = "0.6.0", features = ["web"] } +env_logger = "0.6" +serde = { version = "^1.0", features = ["derive"] } +actix-service = "0.4.1" +actix-http-test = "0.2.2" +actix-http = "0.2.5" +serde_json = "1.0.40" +time = "0.1.42" diff --git a/redis-session/README.md b/redis-session/README.md new file mode 100644 index 00000000..e69de29b diff --git a/redis-session/src/main.rs b/redis-session/src/main.rs index 3b68ab56..e85b59e0 100644 --- a/redis-session/src/main.rs +++ b/redis-session/src/main.rs @@ -1,48 +1,329 @@ -//! Example of redis based session +//! Example of login and logout using redis-based sessions //! -//! [User guide](https://actix.rs/book/actix-web/sec-9-middlewares.html#user-sessions) -extern crate actix; -extern crate actix_redis; -extern crate actix_web; -extern crate env_logger; +//! Every request gets a session, corresponding to a cache entry and cookie. +//! At login, the session key changes and session state in cache re-assigns. +//! At logout, session state in cache is removed and cookie is invalidated. +//! +use actix_redis::RedisSession; +use actix_session::Session; +use actix_web::{ + middleware, web, + web::{get, post, resource}, + App, HttpResponse, HttpServer, Result, +}; +use serde::{Deserialize, Serialize}; -use actix_redis::RedisSessionBackend; -use actix_web::middleware::session::{RequestSession, SessionStorage}; -use actix_web::{middleware, server, App, HttpRequest, HttpResponse, Result}; +#[derive(Serialize, Deserialize, Debug, PartialEq)] +pub struct IndexResponse { + user_id: Option, + counter: i32, +} -/// simple handler -fn index(req: &HttpRequest) -> Result { - println!("{:?}", req); +fn index(session: Session) -> Result { + let user_id: Option = session.get::("user_id").unwrap(); + let counter: i32 = session + .get::("counter") + .unwrap_or(Some(0)) + .unwrap_or(0); - // session - if let Some(count) = req.session().get::("counter")? { - println!("SESSION value: {}", count); - req.session().set("counter", count + 1)?; + Ok(HttpResponse::Ok().json(IndexResponse { user_id, counter })) +} + +fn do_something(session: Session) -> Result { + let user_id: Option = session.get::("user_id").unwrap(); + let counter: i32 = session + .get::("counter") + .unwrap_or(Some(0)) + .map_or(1, |inner| inner + 1); + session.set("counter", counter)?; + + Ok(HttpResponse::Ok().json(IndexResponse { user_id, counter })) +} + +#[derive(Deserialize)] +struct Identity { + user_id: String, +} +fn login(user_id: web::Json, session: Session) -> Result { + let id = user_id.into_inner().user_id; + session.set("user_id", &id)?; + session.renew(); + + let counter: i32 = session + .get::("counter") + .unwrap_or(Some(0)) + .unwrap_or(0); + + Ok(HttpResponse::Ok().json(IndexResponse { + user_id: Some(id), + counter, + })) +} + +fn logout(session: Session) -> Result { + let id: Option = session.get("user_id")?; + if let Some(x) = id { + session.purge(); + Ok(format!("Logged out: {}", x).into()) } else { - req.session().set("counter", 1)?; + Ok("Could not log out anonymous user".into()) } - - Ok("Welcome!".into()) } -fn main() { - ::std::env::set_var("RUST_LOG", "actix_web=info,actix_redis=info"); +fn main() -> std::io::Result<()> { + std::env::set_var("RUST_LOG", "actix_web=info,actix_redis=info"); env_logger::init(); - let sys = actix::System::new("basic-example"); - server::new(|| { + HttpServer::new(|| { App::new() - // enable logger - .middleware(middleware::Logger::default()) // redis session middleware - .middleware(SessionStorage::new( - RedisSessionBackend::new("127.0.0.1:6379", &[0; 32]) - )) - // register simple route, handle all methods - .resource("/", |r| r.f(index)) - }).bind("127.0.0.1:8080") - .unwrap() - .start(); - - let _ = sys.run(); + .wrap(RedisSession::new("127.0.0.1:6379", &[0; 32])) + // enable logger - always register actix-web Logger middleware last + .wrap(middleware::Logger::default()) + .service(resource("/").route(get().to(index))) + .service(resource("/do_something").route(post().to(do_something))) + .service(resource("/login").route(post().to(login))) + .service(resource("/logout").route(post().to(logout))) + }) + .bind("127.0.0.1:8080")? + .run() +} + +#[cfg(test)] +mod test { + use super::*; + use actix_http::{httpmessage::HttpMessage, HttpService}; + use actix_http_test::{block_on, TestServer}; + use actix_web::{ + middleware, + web::{get, post, resource}, + App, + }; + use serde_json::json; + use time; + + #[test] + fn test_workflow() { + // Step 1: GET index + // - set-cookie actix-session will be in response (session cookie #1) + // - response should be: {"counter": 0, "user_id": None} + // Step 2: GET index, including session cookie #1 in request + // - set-cookie will *not* be in response + // - response should be: {"counter": 0, "user_id": None} + // Step 3: POST to do_something, including session cookie #1 in request + // - adds new session state in redis: {"counter": 1} + // - response should be: {"counter": 1, "user_id": None} + // Step 4: POST again to do_something, including session cookie #1 in request + // - updates session state in redis: {"counter": 2} + // - response should be: {"counter": 2, "user_id": None} + // Step 5: POST to login, including session cookie #1 in request + // - set-cookie actix-session will be in response (session cookie #2) + // - updates session state in redis: {"counter": 2, "user_id": "ferris"} + // Step 6: GET index, including session cookie #2 in request + // - response should be: {"counter": 2, "user_id": "ferris"} + // Step 7: POST again to do_something, including session cookie #2 in request + // - updates session state in redis: {"counter": 3, "user_id": "ferris"} + // - response should be: {"counter": 2, "user_id": None} + // Step 8: GET index, including session cookie #1 in request + // - set-cookie actix-session will be in response (session cookie #3) + // - response should be: {"counter": 0, "user_id": None} + // Step 9: POST to logout, including session cookie #2 + // - set-cookie actix-session will be in response with session cookie #2 + // invalidation logic + // Step 10: GET index, including session cookie #2 in request + // - set-cookie actix-session will be in response (session cookie #3) + // - response should be: {"counter": 0, "user_id": None} + + let mut srv = TestServer::new(|| { + HttpService::new( + App::new() + .wrap( + RedisSession::new("127.0.0.1:6379", &[0; 32]) + .cookie_name("test-session"), + ) + .wrap(middleware::Logger::default()) + .service(resource("/").route(get().to(index))) + .service(resource("/do_something").route(post().to(do_something))) + .service(resource("/login").route(post().to(login))) + .service(resource("/logout").route(post().to(logout))), + ) + }); + + // Step 1: GET index + // - set-cookie actix-session will be in response (session cookie #1) + // - response should be: {"counter": 0, "user_id": None} + let req_1a = srv.get("/").send(); + let mut resp_1 = srv.block_on(req_1a).unwrap(); + let cookie_1 = resp_1 + .cookies() + .unwrap() + .clone() + .into_iter() + .find(|c| c.name() == "test-session") + .unwrap(); + let result_1 = block_on(resp_1.json::()).unwrap(); + assert_eq!( + result_1, + IndexResponse { + user_id: None, + counter: 0 + } + ); + + // Step 2: GET index, including session cookie #1 in request + // - set-cookie will *not* be in response + // - response should be: {"counter": 0, "user_id": None} + let req_2 = srv.get("/").cookie(cookie_1.clone()).send(); + let resp_2 = srv.block_on(req_2).unwrap(); + let cookie_2 = resp_2 + .cookies() + .unwrap() + .clone() + .into_iter() + .find(|c| c.name() == "test-session"); + assert_eq!(cookie_2, None); + + // Step 3: POST to do_something, including session cookie #1 in request + // - adds new session state in redis: {"counter": 1} + // - response should be: {"counter": 1, "user_id": None} + let req_3 = srv.post("/do_something").cookie(cookie_1.clone()).send(); + let mut resp_3 = srv.block_on(req_3).unwrap(); + let result_3 = block_on(resp_3.json::()).unwrap(); + assert_eq!( + result_3, + IndexResponse { + user_id: None, + counter: 1 + } + ); + + // Step 4: POST again to do_something, including session cookie #1 in request + // - updates session state in redis: {"counter": 2} + // - response should be: {"counter": 2, "user_id": None} + let req_4 = srv.post("/do_something").cookie(cookie_1.clone()).send(); + let mut resp_4 = srv.block_on(req_4).unwrap(); + let result_4 = block_on(resp_4.json::()).unwrap(); + assert_eq!( + result_4, + IndexResponse { + user_id: None, + counter: 2 + } + ); + + // Step 5: POST to login, including session cookie #1 in request + // - set-cookie actix-session will be in response (session cookie #2) + // - updates session state in redis: {"counter": 2, "user_id": "ferris"} + let req_5 = srv + .post("/login") + .cookie(cookie_1.clone()) + .send_json(&json!({"user_id": "ferris"})); + let mut resp_5 = srv.block_on(req_5).unwrap(); + let cookie_2 = resp_5 + .cookies() + .unwrap() + .clone() + .into_iter() + .find(|c| c.name() == "test-session") + .unwrap(); + assert_eq!( + true, + cookie_1.value().to_string() != cookie_2.value().to_string() + ); + + let result_5 = block_on(resp_5.json::()).unwrap(); + assert_eq!( + result_5, + IndexResponse { + user_id: Some("ferris".into()), + counter: 2 + } + ); + + // Step 6: GET index, including session cookie #2 in request + // - response should be: {"counter": 2, "user_id": "ferris"} + let req_6 = srv.get("/").cookie(cookie_2.clone()).send(); + let mut resp_6 = srv.block_on(req_6).unwrap(); + let result_6 = block_on(resp_6.json::()).unwrap(); + assert_eq!( + result_6, + IndexResponse { + user_id: Some("ferris".into()), + counter: 2 + } + ); + + // Step 7: POST again to do_something, including session cookie #2 in request + // - updates session state in redis: {"counter": 3, "user_id": "ferris"} + // - response should be: {"counter": 2, "user_id": None} + let req_7 = srv.post("/do_something").cookie(cookie_2.clone()).send(); + let mut resp_7 = srv.block_on(req_7).unwrap(); + let result_7 = block_on(resp_7.json::()).unwrap(); + assert_eq!( + result_7, + IndexResponse { + user_id: Some("ferris".into()), + counter: 3 + } + ); + + // Step 8: GET index, including session cookie #1 in request + // - set-cookie actix-session will be in response (session cookie #3) + // - response should be: {"counter": 0, "user_id": None} + let req_8 = srv.get("/").cookie(cookie_1.clone()).send(); + let mut resp_8 = srv.block_on(req_8).unwrap(); + let cookie_3 = resp_8 + .cookies() + .unwrap() + .clone() + .into_iter() + .find(|c| c.name() == "test-session") + .unwrap(); + let result_8 = block_on(resp_8.json::()).unwrap(); + assert_eq!( + result_8, + IndexResponse { + user_id: None, + counter: 0 + } + ); + assert!(cookie_3.value().to_string() != cookie_2.value().to_string()); + + // Step 9: POST to logout, including session cookie #2 + // - set-cookie actix-session will be in response with session cookie #2 + // invalidation logic + let req_9 = srv.post("/logout").cookie(cookie_2.clone()).send(); + let resp_9 = srv.block_on(req_9).unwrap(); + let cookie_4 = resp_9 + .cookies() + .unwrap() + .clone() + .into_iter() + .find(|c| c.name() == "test-session") + .unwrap(); + assert!(&time::now().tm_year != &cookie_4.expires().map(|t| t.tm_year).unwrap()); + + // Step 10: GET index, including session cookie #2 in request + // - set-cookie actix-session will be in response (session cookie #3) + // - response should be: {"counter": 0, "user_id": None} + let req_10 = srv.get("/").cookie(cookie_2.clone()).send(); + let mut resp_10 = srv.block_on(req_10).unwrap(); + let result_10 = block_on(resp_10.json::()).unwrap(); + assert_eq!( + result_10, + IndexResponse { + user_id: None, + counter: 0 + } + ); + + let cookie_5 = resp_10 + .cookies() + .unwrap() + .clone() + .into_iter() + .find(|c| c.name() == "test-session") + .unwrap(); + assert!(cookie_5.value().to_string() != cookie_2.value().to_string()); + } } diff --git a/rustfmt.toml b/rustfmt.toml index 4fff285e..94bd11d5 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,5 +1,2 @@ max_width = 89 reorder_imports = true -#wrap_comments = true -fn_args_density = "Compressed" -#use_small_heuristics = false diff --git a/rustls/Cargo.toml b/rustls/Cargo.toml index 477cb3d2..8e709301 100644 --- a/rustls/Cargo.toml +++ b/rustls/Cargo.toml @@ -2,7 +2,8 @@ name = "rustls-example" version = "0.1.0" authors = ["Nikolay Kim "] -workspace = "../" +workspace = ".." +edition = "2018" [[bin]] name = "rustls-server" @@ -10,6 +11,6 @@ path = "src/main.rs" [dependencies] env_logger = "0.5" -rustls = "0.14" -actix = "0.7" -actix-web = { version = "0.7", features=["rust-tls"] } +rustls = "0.15" +actix-web = { version = "1.0.0", features=["rust-tls"] } +actix-files = "0.1.0" diff --git a/rustls/src/main.rs b/rustls/src/main.rs index 58220215..8ef45663 100644 --- a/rustls/src/main.rs +++ b/rustls/src/main.rs @@ -1,32 +1,24 @@ -extern crate actix; -extern crate actix_web; -extern crate env_logger; -extern crate rustls; - use std::fs::File; use std::io::BufReader; -use actix_web::{http, middleware, server, App, Error, HttpRequest, HttpResponse}; +use actix_files::Files; +use actix_web::{middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer}; use rustls::internal::pemfile::{certs, rsa_private_keys}; use rustls::{NoClientAuth, ServerConfig}; -use server::ServerFlags; - -use actix_web::fs::StaticFiles; /// simple handle -fn index(req: &HttpRequest) -> Result { +fn index(req: HttpRequest) -> Result { println!("{:?}", req); Ok(HttpResponse::Ok() .content_type("text/plain") .body("Welcome!")) } -fn main() { - if ::std::env::var("RUST_LOG").is_err() { - ::std::env::set_var("RUST_LOG", "actix_web=info"); +fn main() -> std::io::Result<()> { + if std::env::var("RUST_LOG").is_err() { + std::env::set_var("RUST_LOG", "actix_web=info"); } env_logger::init(); - let sys = actix::System::new("ws-example"); // load ssl keys let mut config = ServerConfig::new(NoClientAuth::new()); @@ -36,29 +28,20 @@ fn main() { let mut keys = rsa_private_keys(key_file).unwrap(); config.set_single_cert(cert_chain, keys.remove(0)).unwrap(); - // actix acceptor - let acceptor = server::RustlsAcceptor::with_flags( - config, - ServerFlags::HTTP1 | ServerFlags::HTTP2, - ); - - server::new(|| { + HttpServer::new(|| { App::new() // enable logger - .middleware(middleware::Logger::default()) + .wrap(middleware::Logger::default()) // register simple handler, handle all methods - .resource("/index.html", |r| r.f(index)) + .service(web::resource("/index.html").to(index)) // with path parameters - .resource("/", |r| r.method(http::Method::GET).f(|_| { + .service(web::resource("/").route(web::get().to(|| { HttpResponse::Found() .header("LOCATION", "/index.html") .finish() - })) - .handler("/static", StaticFiles::new("static").unwrap()) - }).bind_with("127.0.0.1:8443", move || acceptor.clone()) - .unwrap() - .start(); - - println!("Started http server: 127.0.0.1:8443"); - let _ = sys.run(); -} \ No newline at end of file + }))) + .service(Files::new("/static", "static")) + }) + .bind_rustls("127.0.0.1:8443", config)? + .run() +} diff --git a/simple-auth-server/Cargo.toml b/simple-auth-server/Cargo.toml index 25ba9407..9a96ced1 100644 --- a/simple-auth-server/Cargo.toml +++ b/simple-auth-server/Cargo.toml @@ -2,21 +2,24 @@ name = "simple-auth-server" version = "0.1.0" authors = ["mygnu "] +edition = "2018" +workspace = ".." [dependencies] -actix = "0.7.7" -actix-web = "0.7.14" -bcrypt = "0.2.1" +actix-identity = "0.1.0" +actix-web = "1.0.3" +argonautica = "0.2.0" chrono = { version = "0.4.6", features = ["serde"] } -diesel = { version = "1.3.3", features = ["postgres", "uuid", "r2d2", "chrono"] } -dotenv = "0.13.0" -env_logger = "0.6.0" -failure = "0.1.3" -jsonwebtoken = "5.0" +derive_more = "0.15.0" +diesel = { version = "1.4.2", features = ["postgres","uuidv07", "r2d2", "chrono"] } +dotenv = "0.14.1" +env_logger = "0.6" futures = "0.1" -r2d2 = "0.8.3" -serde_derive="1.0.80" -serde_json="1.0" -serde="1.0" +r2d2 = "0.8" +lazy_static = "1.3.0" +serde = "1.0" +serde_derive = "1.0" +serde_json = "1.0" sparkpost = "0.5.2" -uuid = { version = "0.6.5", features = ["serde", "v4"] } +uuid = { version = "0.7", features = ["serde", "v4"] } + diff --git a/simple-auth-server/README.md b/simple-auth-server/README.md index 4c6048e1..dc25484a 100644 --- a/simple-auth-server/README.md +++ b/simple-auth-server/README.md @@ -1,21 +1,22 @@ +## Auth Web Microservice with rust using Actix-Web 1.0 + ##### Flow of the event would look like this: - Registers with email address ➡ Receive an 📨 with a link to verify - Follow the link ➡ register with same email and a password -- Login with email and password ➡ Get verified and receive jwt token +- Login with email and password ➡ Get verified and receive auth cookie -##### Crates we are going to use +##### Crates Used -- [actix](https://crates.io/crates/actix) // Actix is a Rust actors framework. - [actix-web](https://crates.io/crates/actix-web) // Actix web is a simple, pragmatic and extremely fast web framework for Rust. -- [bcrypt](https://crates.io/crates/bcrypt) // Easily hash and verify passwords using bcrypt. +- [argonautica](https://docs.rs/argonautica) // crate for hashing passwords using the cryptographically-secure Argon2 hashing algorithm. - [chrono](https://crates.io/crates/chrono) // Date and time library for Rust. - [diesel](https://crates.io/crates/diesel) // A safe, extensible ORM and Query Builder for PostgreSQL, SQLite, and MySQL. - [dotenv](https://crates.io/crates/dotenv) // A dotenv implementation for Rust. +- [derive_more](https://crates.io/crates/derive_more) // Convenience macros to derive tarits easily - [env_logger](https://crates.io/crates/env_logger) // A logging implementation for log which is configured via an environment variable. -- [failure](https://crates.io/crates/failure) // Experimental error handling abstraction. -- [jsonwebtoken](https://crates.io/crates/jsonwebtoken) // Create and parse JWT in a strongly typed way. - [futures](https://crates.io/crates/futures) // An implementation of futures and streams featuring zero allocations, composability, and iterator-like interfaces. +- [lazy_static](https://docs.rs/lazy_static) // A macro for declaring lazily evaluated statics. - [r2d2](https://crates.io/crates/r2d2) // A generic connection pool. - [serde](https://crates.io/crates/serde) // A generic serialization/deserialization framework. - [serde_json](https://crates.io/crates/serde_json) // A JSON serialization file format. @@ -24,9 +25,6 @@ - [uuid](https://crates.io/crates/uuid) // A library to generate and parse UUIDs. -Read the full tutorial series on [hgill.io](https://hgill.io) - -- [Auth Web Microservice with rust using Actix-Web - Complete Tutorial Part 1](https://hgill.io/posts/auth-microservice-rust-actix-web-diesel-complete-tutorial-part-1/) -- [Auth Web Microservice with rust using Actix-Web - Complete Tutorial Part 2](https://hgill.io/posts/auth-microservice-rust-actix-web-diesel-complete-tutorial-part-2/) -- [Auth Web Microservice with rust using Actix-Web - Complete Tutorial Part 3](https://hgill.io/posts/auth-microservice-rust-actix-web-diesel-complete-tutorial-part-3/) +Read the full tutorial series on [gill.net.in](https://gill.net.in) +- [Auth Web Microservice with rust using Actix-Web 1.0 - Complete Tutorial](https://gill.net.in/posts/auth-microservice-rust-actix-web1.0-diesel-complete-tutorial/) diff --git a/simple-auth-server/migrations/2018-10-09-101948_users/up.sql b/simple-auth-server/migrations/2018-10-09-101948_users/up.sql index 890b6676..daf25276 100644 --- a/simple-auth-server/migrations/2018-10-09-101948_users/up.sql +++ b/simple-auth-server/migrations/2018-10-09-101948_users/up.sql @@ -1,6 +1,6 @@ -- Your SQL goes here CREATE TABLE users ( email VARCHAR(100) NOT NULL UNIQUE PRIMARY KEY, - password VARCHAR(64) NOT NULL, --bcrypt hash + hash VARCHAR(122) NOT NULL, --argon hash created_at TIMESTAMP NOT NULL ); diff --git a/simple-auth-server/src/app.rs b/simple-auth-server/src/app.rs deleted file mode 100644 index f56be1a2..00000000 --- a/simple-auth-server/src/app.rs +++ /dev/null @@ -1,54 +0,0 @@ -use actix::prelude::*; -use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService}; -use actix_web::{fs, http::Method, middleware::Logger, App}; -use auth_routes::{get_me, login, logout}; -use chrono::Duration; -use invitation_routes::register_email; -use models::DbExecutor; -use register_routes::register_user; - -pub struct AppState { - pub db: Addr, -} - -/// creates and returns the app after mounting all routes/resources -pub fn create_app(db: Addr) -> App { - // secret is a random minimum 32 bytes long base 64 string - let secret: String = std::env::var("SECRET_KEY").unwrap_or_else(|_| "0123".repeat(8)); - let domain: String = std::env::var("DOMAIN").unwrap_or_else(|_| "localhost".to_string()); - - App::with_state(AppState { db }) - .middleware(Logger::default()) - .middleware(IdentityService::new( - CookieIdentityPolicy::new(secret.as_bytes()) - .name("auth") - .path("/") - .domain(domain.as_str()) - .max_age(Duration::days(1)) - .secure(false), // this can only be true if you have https - )) - // everything under '/api/' route - .scope("/api", |api| { - // routes for authentication - api.resource("/auth", |r| { - r.method(Method::POST).with(login); - r.method(Method::DELETE).with(logout); - r.method(Method::GET).with(get_me); - }) - // routes to invitation - .resource("/invitation", |r| { - r.method(Method::POST).with(register_email); - }) - // routes to register as a user after the - .resource("/register/{invitation_id}", |r| { - r.method(Method::POST).with(register_user); - }) - }) - // serve static files - .handler( - "/", - fs::StaticFiles::new("./static/") - .unwrap() - .index_file("index.html"), - ) -} diff --git a/simple-auth-server/src/auth_handler.rs b/simple-auth-server/src/auth_handler.rs index cedfa90e..9c75efc0 100644 --- a/simple-auth-server/src/auth_handler.rs +++ b/simple-auth-server/src/auth_handler.rs @@ -1,10 +1,15 @@ -use actix::{Handler, Message}; +use actix_identity::Identity; +use actix_web::{ + dev::Payload, error::BlockingError, web, Error, FromRequest, HttpRequest, + HttpResponse, +}; use diesel::prelude::*; -use errors::ServiceError; -use models::{DbExecutor, User, SlimUser}; -use bcrypt::verify; -use actix_web::{FromRequest, HttpRequest, middleware::identity::RequestIdentity}; -use utils::decode_token; +use diesel::PgConnection; +use futures::Future; + +use crate::errors::ServiceError; +use crate::models::{Pool, SlimUser, User}; +use crate::utils::verify; #[derive(Debug, Deserialize)] pub struct AuthData { @@ -12,44 +17,66 @@ pub struct AuthData { pub password: String, } -impl Message for AuthData { - type Result = Result; -} - -impl Handler for DbExecutor { - type Result = Result; - fn handle(&mut self, msg: AuthData, _: &mut Self::Context) -> Self::Result { - use schema::users::dsl::{users, email}; - let conn: &PgConnection = &self.0.get().unwrap(); - - let mut items = users - .filter(email.eq(&msg.email)) - .load::(conn)?; - - if let Some(user) = items.pop() { - match verify(&msg.password, &user.password) { - Ok(matching) => if matching { - return Ok(user.into()); - }, - Err(_) => (), - } - } - Err(ServiceError::BadRequest("Username and Password don't match".into())) - } -} - // we need the same data // simple aliasing makes the intentions clear and its more readable pub type LoggedUser = SlimUser; -impl FromRequest for LoggedUser { +impl FromRequest for LoggedUser { type Config = (); - type Result = Result; - fn from_request(req: &HttpRequest, _: &Self::Config) -> Self::Result { - if let Some(identity) = req.identity() { - let user: SlimUser = decode_token(&identity)?; - return Ok(user as LoggedUser); + type Error = Error; + type Future = Result; + + fn from_request(req: &HttpRequest, pl: &mut Payload) -> Self::Future { + if let Some(identity) = Identity::from_request(req, pl)?.identity() { + let user: LoggedUser = serde_json::from_str(&identity)?; + return Ok(user); } - Err(ServiceError::Unauthorized) + Err(ServiceError::Unauthorized.into()) } } + +pub fn logout(id: Identity) -> HttpResponse { + id.forget(); + HttpResponse::Ok().finish() +} + +pub fn login( + auth_data: web::Json, + id: Identity, + pool: web::Data, +) -> impl Future { + web::block(move || query(auth_data.into_inner(), pool)).then( + move |res: Result>| match res { + Ok(user) => { + let user_string = serde_json::to_string(&user).unwrap(); + id.remember(user_string); + Ok(HttpResponse::Ok().finish()) + } + Err(err) => match err { + BlockingError::Error(service_error) => Err(service_error), + BlockingError::Canceled => Err(ServiceError::InternalServerError), + }, + }, + ) +} + +pub fn get_me(logged_user: LoggedUser) -> HttpResponse { + HttpResponse::Ok().json(logged_user) +} +/// Diesel query +fn query(auth_data: AuthData, pool: web::Data) -> Result { + use crate::schema::users::dsl::{email, users}; + let conn: &PgConnection = &pool.get().unwrap(); + let mut items = users + .filter(email.eq(&auth_data.email)) + .load::(conn)?; + + if let Some(user) = items.pop() { + if let Ok(matching) = verify(&user.hash, &auth_data.password) { + if matching { + return Ok(user.into()); + } + } + } + Err(ServiceError::Unauthorized) +} diff --git a/simple-auth-server/src/auth_routes.rs b/simple-auth-server/src/auth_routes.rs deleted file mode 100644 index 48ee15c0..00000000 --- a/simple-auth-server/src/auth_routes.rs +++ /dev/null @@ -1,32 +0,0 @@ -use actix_web::{AsyncResponder, FutureResponse, HttpResponse, HttpRequest, ResponseError, Json}; -use actix_web::middleware::identity::RequestIdentity; -use futures::future::Future; -use utils::create_token; - -use app::AppState; -use auth_handler::{AuthData, LoggedUser}; - -pub fn login((auth_data, req): (Json, HttpRequest)) - -> FutureResponse { - req.state() - .db - .send(auth_data.into_inner()) - .from_err() - .and_then(move |res| match res { - Ok(user) => { - let token = create_token(&user)?; - req.remember(token); - Ok(HttpResponse::Ok().into()) - } - Err(err) => Ok(err.error_response()), - }).responder() -} - -pub fn logout(req: HttpRequest) -> HttpResponse { - req.forget(); - HttpResponse::Ok().into() -} - -pub fn get_me(logged_user: LoggedUser) -> HttpResponse { - HttpResponse::Ok().json(logged_user) -} diff --git a/simple-auth-server/src/email_service.rs b/simple-auth-server/src/email_service.rs index f1db1172..4f22497f 100644 --- a/simple-auth-server/src/email_service.rs +++ b/simple-auth-server/src/email_service.rs @@ -1,16 +1,18 @@ -use models::Invitation; +// email_service.rs +use crate::errors::ServiceError; +use crate::models::Invitation; use sparkpost::transmission::{ EmailAddress, Message, Options, Recipient, Transmission, TransmissionResponse, }; -fn get_api_key() -> String { - std::env::var("SPARKPOST_API_KEY").expect("SPARKPOST_API_KEY must be set") +lazy_static::lazy_static! { +static ref API_KEY: String = std::env::var("SPARKPOST_API_KEY").expect("SPARKPOST_API_KEY must be set"); } -pub fn send_invitation(invitation: &Invitation) { - let tm = Transmission::new_eu(get_api_key()); - let sending_email = - std::env::var("SENDING_EMAIL_ADDRESS").expect("SENDING_EMAIL_ADDRESS must be set"); +pub fn send_invitation(invitation: &Invitation) -> Result<(), ServiceError> { + let tm = Transmission::new_eu(API_KEY.as_str()); + let sending_email = std::env::var("SENDING_EMAIL_ADDRESS") + .expect("SENDING_EMAIL_ADDRESS must be set"); // new email message with sender name and email let mut email = Message::new(EmailAddress::new(sending_email, "Let's Organise")); @@ -39,7 +41,6 @@ pub fn send_invitation(invitation: &Invitation) { .to_string() ); - // complete the email message with details email .add_recipient(recipient) @@ -51,18 +52,19 @@ pub fn send_invitation(invitation: &Invitation) { // Note that we only print out the error response from email api match result { - Ok(res) => { - match res { - TransmissionResponse::ApiResponse(api_res) => { - println!("API Response: \n {:#?}", api_res); - } - TransmissionResponse::ApiError(errors) => { - println!("Response Errors: \n {:#?}", &errors); - } + Ok(res) => match res { + TransmissionResponse::ApiResponse(api_res) => { + println!("API Response: \n {:#?}", api_res); + Ok(()) } - } + TransmissionResponse::ApiError(errors) => { + println!("Response Errors: \n {:#?}", &errors); + Err(ServiceError::InternalServerError) + } + }, Err(error) => { - println!("error \n {:#?}", error); + println!("Send Email Error: \n {:#?}", error); + Err(ServiceError::InternalServerError) } } } diff --git a/simple-auth-server/src/errors.rs b/simple-auth-server/src/errors.rs index b65aa4f7..1cea664b 100644 --- a/simple-auth-server/src/errors.rs +++ b/simple-auth-server/src/errors.rs @@ -1,28 +1,33 @@ use actix_web::{error::ResponseError, HttpResponse}; +use derive_more::Display; +use diesel::result::{DatabaseErrorKind, Error as DBError}; use std::convert::From; -use diesel::result::{DatabaseErrorKind, Error}; -use uuid::ParseError; +use uuid::parser::ParseError; - -#[derive(Fail, Debug)] +#[derive(Debug, Display)] pub enum ServiceError { - #[fail(display = "Internal Server Error")] + #[display(fmt = "Internal Server Error")] InternalServerError, - #[fail(display = "BadRequest: {}", _0)] + #[display(fmt = "BadRequest: {}", _0)] BadRequest(String), - #[fail(display = "Unauthorized")] + #[display(fmt = "Unauthorized")] Unauthorized, } // impl ResponseError trait allows to convert our errors into http responses with appropriate data impl ResponseError for ServiceError { fn error_response(&self) -> HttpResponse { - match *self { - ServiceError::InternalServerError => HttpResponse::InternalServerError().json("Internal Server Error, Please try later"), - ServiceError::BadRequest(ref message) => HttpResponse::BadRequest().json(message), - ServiceError::Unauthorized => HttpResponse::Unauthorized().json("Unauthorized") + match self { + ServiceError::InternalServerError => HttpResponse::InternalServerError() + .json("Internal Server Error, Please try later"), + ServiceError::BadRequest(ref message) => { + HttpResponse::BadRequest().json(message) + } + ServiceError::Unauthorized => { + HttpResponse::Unauthorized().json("Unauthorized") + } } } } @@ -35,19 +40,20 @@ impl From for ServiceError { } } -impl From for ServiceError { - fn from(error: Error) -> ServiceError { +impl From for ServiceError { + fn from(error: DBError) -> ServiceError { // Right now we just care about UniqueViolation from diesel // But this would be helpful to easily map errors as our app grows match error { - Error::DatabaseError(kind, info) => { + DBError::DatabaseError(kind, info) => { if let DatabaseErrorKind::UniqueViolation = kind { - let message = info.details().unwrap_or_else(|| info.message()).to_string(); + let message = + info.details().unwrap_or_else(|| info.message()).to_string(); return ServiceError::BadRequest(message); } ServiceError::InternalServerError } - _ => ServiceError::InternalServerError + _ => ServiceError::InternalServerError, } } } diff --git a/simple-auth-server/src/invitation_handler.rs b/simple-auth-server/src/invitation_handler.rs index 425e4c0a..11b137bd 100644 --- a/simple-auth-server/src/invitation_handler.rs +++ b/simple-auth-server/src/invitation_handler.rs @@ -1,39 +1,53 @@ -use actix::{Handler, Message}; -use chrono::{Duration, Local}; -use diesel::{self, prelude::*}; -use errors::ServiceError; -use models::{DbExecutor, Invitation}; -use uuid::Uuid; +use actix_web::{error::BlockingError, web, HttpResponse}; +use diesel::{prelude::*, PgConnection}; +use futures::Future; + +use crate::email_service::send_invitation; +use crate::errors::ServiceError; +use crate::models::{Invitation, Pool}; #[derive(Deserialize)] -pub struct CreateInvitation { +pub struct InvitationData { pub email: String, } -impl Message for CreateInvitation { - type Result = Result; +pub fn post_invitation( + invitation_data: web::Json, + pool: web::Data, +) -> impl Future { + // run diesel blocking code + web::block(move || create_invitation(invitation_data.into_inner().email, pool)).then( + |res| match res { + Ok(_) => Ok(HttpResponse::Ok().finish()), + Err(err) => match err { + BlockingError::Error(service_error) => Err(service_error), + BlockingError::Canceled => Err(ServiceError::InternalServerError), + }, + }, + ) } -impl Handler for DbExecutor { - type Result = Result; - - fn handle(&mut self, msg: CreateInvitation, _: &mut Self::Context) -> Self::Result { - use schema::invitations::dsl::*; - let conn: &PgConnection = &self.0.get().unwrap(); - - // creating a new Invitation object with expired at time that is 24 hours from now - let new_invitation = Invitation { - id: Uuid::new_v4(), - email: msg.email.clone(), - expires_at: Local::now().naive_local() + Duration::hours(24), - }; - - let inserted_invitation = diesel::insert_into(invitations) - .values(&new_invitation) - .get_result(conn)?; - - Ok(inserted_invitation) - } +fn create_invitation( + eml: String, + pool: web::Data, +) -> Result<(), crate::errors::ServiceError> { + let invitation = dbg!(query(eml, pool)?); + send_invitation(&invitation) } +/// Diesel query +fn query( + eml: String, + pool: web::Data, +) -> Result { + use crate::schema::invitations::dsl::invitations; + let new_invitation: Invitation = eml.into(); + let conn: &PgConnection = &pool.get().unwrap(); + + let inserted_invitation = diesel::insert_into(invitations) + .values(&new_invitation) + .get_result(conn)?; + + Ok(inserted_invitation) +} diff --git a/simple-auth-server/src/invitation_routes.rs b/simple-auth-server/src/invitation_routes.rs deleted file mode 100644 index 9232c6d0..00000000 --- a/simple-auth-server/src/invitation_routes.rs +++ /dev/null @@ -1,22 +0,0 @@ -use actix_web::{AsyncResponder, FutureResponse, HttpResponse, Json, ResponseError, State}; -use futures::future::Future; - -use app::AppState; -use email_service::send_invitation; -use invitation_handler::CreateInvitation; - -pub fn register_email( - (signup_invitation, state): (Json, State), -) -> FutureResponse { - state - .db - .send(signup_invitation.into_inner()) - .from_err() - .and_then(|db_response| match db_response { - Ok(invitation) => { - send_invitation(&invitation); - Ok(HttpResponse::Ok().into()) - } - Err(err) => Ok(err.error_response()), - }).responder() -} diff --git a/simple-auth-server/src/main.rs b/simple-auth-server/src/main.rs index f5d096b2..7271ec3e 100644 --- a/simple-auth-server/src/main.rs +++ b/simple-auth-server/src/main.rs @@ -1,66 +1,73 @@ -// to avoid the warning from diesel macros -#![allow(proc_macro_derive_resolution_fallback)] - -extern crate bcrypt; -extern crate actix; -extern crate actix_web; -extern crate env_logger; -extern crate serde; -extern crate chrono; -extern crate dotenv; -extern crate futures; -extern crate r2d2; -extern crate uuid; -extern crate jsonwebtoken as jwt; -extern crate sparkpost; #[macro_use] extern crate diesel; #[macro_use] extern crate serde_derive; -#[macro_use] -extern crate failure; -mod app; -mod models; -mod schema; -mod errors; +use actix_identity::{CookieIdentityPolicy, IdentityService}; +use actix_web::{middleware, web, App, HttpServer}; +use diesel::prelude::*; +use diesel::r2d2::{self, ConnectionManager}; + mod auth_handler; -mod auth_routes; -mod invitation_handler; -mod invitation_routes; -mod register_handler; -mod register_routes; -mod utils; mod email_service; +mod errors; +mod invitation_handler; +mod models; +mod register_handler; +mod schema; +mod utils; -use models::DbExecutor; -use actix::prelude::*; -use actix_web::server; -use diesel::{r2d2::ConnectionManager, PgConnection}; -use dotenv::dotenv; -use std::env; - - -fn main() { - dotenv().ok(); - std::env::set_var("RUST_LOG", "simple-auth-server=debug,actix_web=info"); - std::env::set_var("RUST_BACKTRACE", "1"); +fn main() -> std::io::Result<()> { + dotenv::dotenv().ok(); + std::env::set_var( + "RUST_LOG", + "simple-auth-server=debug,actix_web=info,actix_server=info", + ); env_logger::init(); - let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set"); - let sys = actix::System::new("Actix_Tutorial"); + let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set"); // create db connection pool let manager = ConnectionManager::::new(database_url); - let pool = r2d2::Pool::builder() + let pool: models::Pool = r2d2::Pool::builder() .build(manager) .expect("Failed to create pool."); + let domain: String = + std::env::var("DOMAIN").unwrap_or_else(|_| "localhost".to_string()); - let address: Addr = SyncArbiter::start(4, move || DbExecutor(pool.clone())); - - server::new(move || app::create_app(address.clone())) - .bind("127.0.0.1:3000") - .expect("Can not bind to '127.0.0.1:3000'") - .start(); - - sys.run(); + // Start http server + HttpServer::new(move || { + App::new() + .data(pool.clone()) + // enable logger + .wrap(middleware::Logger::default()) + .wrap(IdentityService::new( + CookieIdentityPolicy::new(utils::SECRET_KEY.as_bytes()) + .name("auth") + .path("/") + .domain(domain.as_str()) + .max_age_time(chrono::Duration::days(1)) + .secure(false), // this can only be true if you have https + )) + .data(web::JsonConfig::default().limit(4096)) + // everything under '/api/' route + .service( + web::scope("/api") + .service(web::resource("/invitation").route( + web::post().to_async(invitation_handler::post_invitation), + )) + .service( + web::resource("/register/{invitation_id}").route( + web::post().to_async(register_handler::register_user), + ), + ) + .service( + web::resource("/auth") + .route(web::post().to_async(auth_handler::login)) + .route(web::delete().to(auth_handler::logout)) + .route(web::get().to(auth_handler::get_me)), + ), + ) + }) + .bind("127.0.0.1:3000")? + .run() } diff --git a/simple-auth-server/src/models.rs b/simple-auth-server/src/models.rs index fcafb9c5..1f5d9695 100644 --- a/simple-auth-server/src/models.rs +++ b/simple-auth-server/src/models.rs @@ -1,37 +1,23 @@ -use actix::{Actor, SyncContext}; -use diesel::pg::PgConnection; -use diesel::r2d2::{ConnectionManager, Pool}; -use chrono::{NaiveDateTime, Local}; -use uuid::Uuid; -use std::convert::From; +use super::schema::*; +use diesel::{r2d2::ConnectionManager, PgConnection}; -use schema::{users, invitations}; - -/// This is db executor actor. can be run in parallel -pub struct DbExecutor(pub Pool>); - -// Actors communicate exclusively by exchanging messages. -// The sending actor can optionally wait for the response. -// Actors are not referenced directly, but by means of addresses. -// Any rust type can be an actor, it only needs to implement the Actor trait. -impl Actor for DbExecutor { - type Context = SyncContext; -} +// type alias to use in multiple places +pub type Pool = r2d2::Pool>; #[derive(Debug, Serialize, Deserialize, Queryable, Insertable)] #[table_name = "users"] pub struct User { pub email: String, - pub password: String, - pub created_at: NaiveDateTime, + pub hash: String, + pub created_at: chrono::NaiveDateTime, } impl User { - pub fn with_details(email: String, password: String) -> Self { + pub fn from_details, T: Into>(email: S, pwd: T) -> Self { User { - email, - password, - created_at: Local::now().naive_local(), + email: email.into(), + hash: pwd.into(), + created_at: chrono::Local::now().naive_local(), } } } @@ -39,9 +25,23 @@ impl User { #[derive(Debug, Serialize, Deserialize, Queryable, Insertable)] #[table_name = "invitations"] pub struct Invitation { - pub id: Uuid, + pub id: uuid::Uuid, pub email: String, - pub expires_at: NaiveDateTime, + pub expires_at: chrono::NaiveDateTime, +} + +// any type that implements Into can be used to create Invitation +impl From for Invitation +where + T: Into, +{ + fn from(email: T) -> Self { + Invitation { + id: uuid::Uuid::new_v4(), + email: email.into(), + expires_at: chrono::Local::now().naive_local() + chrono::Duration::hours(24), + } + } } #[derive(Debug, Serialize, Deserialize)] @@ -51,8 +51,6 @@ pub struct SlimUser { impl From for SlimUser { fn from(user: User) -> Self { - SlimUser { - email: user.email - } + SlimUser { email: user.email } } } diff --git a/simple-auth-server/src/register_handler.rs b/simple-auth-server/src/register_handler.rs index 7cf98fd3..2b43a006 100644 --- a/simple-auth-server/src/register_handler.rs +++ b/simple-auth-server/src/register_handler.rs @@ -1,60 +1,65 @@ -use actix::{Handler, Message}; -use chrono::Local; +use actix_web::{error::BlockingError, web, HttpResponse}; use diesel::prelude::*; -use errors::ServiceError; -use models::{DbExecutor, Invitation, User, SlimUser}; -use uuid::Uuid; -use utils::hash_password; +use futures::Future; +use crate::errors::ServiceError; +use crate::models::{Invitation, Pool, SlimUser, User}; +use crate::utils::hash_password; // UserData is used to extract data from a post request by the client #[derive(Debug, Deserialize)] pub struct UserData { pub password: String, } -// to be used to send data via the Actix actor system -#[derive(Debug)] -pub struct RegisterUser { - pub invitation_id: String, - pub password: String, +pub fn register_user( + invitation_id: web::Path, + user_data: web::Json, + pool: web::Data, +) -> impl Future { + web::block(move || { + query( + invitation_id.into_inner(), + user_data.into_inner().password, + pool, + ) + }) + .then(|res| match res { + Ok(user) => Ok(HttpResponse::Ok().json(&user)), + Err(err) => match err { + BlockingError::Error(service_error) => Err(service_error), + BlockingError::Canceled => Err(ServiceError::InternalServerError), + }, + }) } -impl Message for RegisterUser { - type Result = Result; -} +fn query( + invitation_id: String, + password: String, + pool: web::Data, +) -> Result { + use crate::schema::invitations::dsl::{id, invitations}; + use crate::schema::users::dsl::users; + let invitation_id = uuid::Uuid::parse_str(&invitation_id)?; - -impl Handler for DbExecutor { - type Result = Result; - fn handle(&mut self, msg: RegisterUser, _: &mut Self::Context) -> Self::Result { - use schema::invitations::dsl::{invitations, id}; - use schema::users::dsl::users; - let conn: &PgConnection = &self.0.get().unwrap(); - - // try parsing the string provided by the user as url parameter - // return early with error that will be converted to ServiceError - let invitation_id = Uuid::parse_str(&msg.invitation_id)?; - - invitations.filter(id.eq(invitation_id)) - .load::(conn) - .map_err(|_db_error| ServiceError::BadRequest("Invalid Invitation".into())) - .and_then(|mut result| { - if let Some(invitation) = result.pop() { - // if invitation is not expired - if invitation.expires_at > Local::now().naive_local() { - // try hashing the password, else return the error that will be converted to ServiceError - let password: String = hash_password(&msg.password)?; - let user = User::with_details(invitation.email, password); - let inserted_user: User = diesel::insert_into(users) - .values(&user) - .get_result(conn)?; - - return Ok(inserted_user.into()); - } + let conn: &PgConnection = &pool.get().unwrap(); + invitations + .filter(id.eq(invitation_id)) + .load::(conn) + .map_err(|_db_error| ServiceError::BadRequest("Invalid Invitation".into())) + .and_then(|mut result| { + if let Some(invitation) = result.pop() { + // if invitation is not expired + if invitation.expires_at > chrono::Local::now().naive_local() { + // try hashing the password, else return the error that will be converted to ServiceError + let password: String = hash_password(&password)?; + dbg!(&password); + let user = User::from_details(invitation.email, password); + let inserted_user: User = + diesel::insert_into(users).values(&user).get_result(conn)?; + dbg!(&inserted_user); + return Ok(inserted_user.into()); } - Err(ServiceError::BadRequest("Invalid Invitation".into())) - }) - } + } + Err(ServiceError::BadRequest("Invalid Invitation".into())) + }) } - - diff --git a/simple-auth-server/src/register_routes.rs b/simple-auth-server/src/register_routes.rs deleted file mode 100644 index 5924ba50..00000000 --- a/simple-auth-server/src/register_routes.rs +++ /dev/null @@ -1,22 +0,0 @@ -use actix_web::{AsyncResponder, FutureResponse, HttpResponse, ResponseError, State, Json, Path}; -use futures::future::Future; - -use app::AppState; -use register_handler::{RegisterUser, UserData}; - - -pub fn register_user((invitation_id, user_data, state): (Path, Json, State)) - -> FutureResponse { - let msg = RegisterUser { - // into_inner() returns the inner string value from Path - invitation_id: invitation_id.into_inner(), - password: user_data.password.clone(), - }; - - state.db.send(msg) - .from_err() - .and_then(|db_response| match db_response { - Ok(slim_user) => Ok(HttpResponse::Ok().json(slim_user)), - Err(service_error) => Ok(service_error.error_response()), - }).responder() -} diff --git a/simple-auth-server/src/schema.rs b/simple-auth-server/src/schema.rs index 646632db..8c48962f 100644 --- a/simple-auth-server/src/schema.rs +++ b/simple-auth-server/src/schema.rs @@ -9,12 +9,9 @@ table! { table! { users (email) { email -> Varchar, - password -> Varchar, + hash -> Varchar, created_at -> Timestamp, } } -allow_tables_to_appear_in_same_query!( - invitations, - users, -); +allow_tables_to_appear_in_same_query!(invitations, users,); diff --git a/simple-auth-server/src/utils.rs b/simple-auth-server/src/utils.rs index 1f65d3e8..ce2e66a9 100644 --- a/simple-auth-server/src/utils.rs +++ b/simple-auth-server/src/utils.rs @@ -1,68 +1,30 @@ -use bcrypt::{hash, DEFAULT_COST}; -use chrono::{Duration, Local}; -use errors::ServiceError; -use jwt::{decode, encode, Header, Validation}; -use models::SlimUser; -use std::convert::From; -use std::env; +use crate::errors::ServiceError; +use argonautica::{Hasher, Verifier}; -pub fn hash_password(plain: &str) -> Result { - // get the hashing cost from the env variable or use default - let hashing_cost: u32 = match env::var("HASH_ROUNDS") { - Ok(cost) => cost.parse().unwrap_or(DEFAULT_COST), - _ => DEFAULT_COST, - }; - println!("{}", &hashing_cost); - hash(plain, hashing_cost).map_err(|_| ServiceError::InternalServerError) +lazy_static::lazy_static! { +pub static ref SECRET_KEY: String = std::env::var("SECRET_KEY").unwrap_or_else(|_| "0123".repeat(8)); } -#[derive(Debug, Serialize, Deserialize)] -struct Claims { - // issuer - iss: String, - // subject - sub: String, - //issued at - iat: i64, - // expiry - exp: i64, - // user email - email: String, +// WARNING THIS IS ONLY FOR DEMO PLEASE DO MORE RESEARCH FOR PRODUCTION USE +pub fn hash_password(password: &str) -> Result { + Hasher::default() + .with_password(password) + .with_secret_key(SECRET_KEY.as_str()) + .hash() + .map_err(|err| { + dbg!(err); + ServiceError::InternalServerError + }) } -// struct to get converted to token and back -impl Claims { - fn with_email(email: &str) -> Self { - Claims { - iss: "localhost".into(), - sub: "auth".into(), - email: email.to_owned(), - iat: Local::now().timestamp(), - exp: (Local::now() + Duration::hours(24)).timestamp(), - } - } -} - -impl From for SlimUser { - fn from(claims: Claims) -> Self { - SlimUser { - email: claims.email, - } - } -} - -pub fn create_token(data: &SlimUser) -> Result { - let claims = Claims::with_email(data.email.as_str()); - encode(&Header::default(), &claims, get_secret().as_ref()) - .map_err(|_err| ServiceError::InternalServerError) -} - -pub fn decode_token(token: &str) -> Result { - decode::(token, get_secret().as_ref(), &Validation::default()) - .map(|data| Ok(data.claims.into())) - .map_err(|_err| ServiceError::Unauthorized)? -} - -fn get_secret() -> String { - env::var("JWT_SECRET").unwrap_or_else(|_| "my secret".into()) +pub fn verify(hash: &str, password: &str) -> Result { + Verifier::default() + .with_hash(hash) + .with_password(password) + .with_secret_key(SECRET_KEY.as_str()) + .verify() + .map_err(|err| { + dbg!(err); + ServiceError::Unauthorized + }) } diff --git a/state/Cargo.toml b/state/Cargo.toml index edf0e382..ee4cbf33 100644 --- a/state/Cargo.toml +++ b/state/Cargo.toml @@ -2,11 +2,10 @@ name = "state" version = "0.1.0" authors = ["Nikolay Kim "] -workspace = "../" +workspace = ".." +edition = "2018" [dependencies] -futures = "0.1" -env_logger = "0.5" - -actix = "0.7" -actix-web = "0.7" +actix-web = "1.0.0" +futures = "0.1.25" +env_logger = "0.6" diff --git a/state/src/main.rs b/state/src/main.rs index be1bd465..c7f6f947 100644 --- a/state/src/main.rs +++ b/state/src/main.rs @@ -1,55 +1,44 @@ -#![cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))] -//! There are two level of statefulness in actix-web. Application has state -//! that is shared across all handlers within same Application. -//! And individual handler can have state. +#![allow(clippy::needless_pass_by_value)] +//! Application may have multiple data objects that are shared across +//! all handlers within same Application. Data could be added +//! with `App::data()` method, multiple different data objects could be added. //! //! > **Note**: http server accepts an application factory rather than an //! application > instance. Http server constructs an application instance for -//! each thread, > thus application state -//! > must be constructed multiple times. If you want to share state between +//! each thread, > thus application data +//! > must be constructed multiple times. If you want to share data between //! different > threads, a shared object should be used, e.g. `Arc`. //! //! Check [user guide](https://actix.rs/book/actix-web/sec-2-application.html) for more info. -extern crate actix; -extern crate actix_web; -extern crate env_logger; - -use std::sync::Arc; +use std::io; use std::sync::Mutex; -use actix_web::{middleware, server, App, HttpRequest, HttpResponse}; - -/// Application state -struct AppState { - counter: Arc>, -} +use actix_web::{middleware, web, App, HttpRequest, HttpResponse, HttpServer}; /// simple handle -fn index(req: &HttpRequest) -> HttpResponse { +fn index(state: web::Data>, req: HttpRequest) -> HttpResponse { println!("{:?}", req); - *(req.state().counter.lock().unwrap()) += 1; + *(state.lock().unwrap()) += 1; - HttpResponse::Ok().body(format!("Num of requests: {}", req.state().counter.lock().unwrap())) + HttpResponse::Ok().body(format!("Num of requests: {}", state.lock().unwrap())) } -fn main() { - ::std::env::set_var("RUST_LOG", "actix_web=info"); +fn main() -> io::Result<()> { + std::env::set_var("RUST_LOG", "actix_web=info"); env_logger::init(); - let sys = actix::System::new("ws-example"); - let counter = Arc::new(Mutex::new(0)); + let counter = web::Data::new(Mutex::new(0usize)); + //move is necessary to give closure below ownership of counter - server::new(move || { - App::with_state(AppState{counter: counter.clone()}) // <- create app with shared state + HttpServer::new(move || { + App::new() + .register_data(counter.clone()) // <- create app with shared state // enable logger - .middleware(middleware::Logger::default()) + .wrap(middleware::Logger::default()) // register simple handler, handle all methods - .resource("/", |r| r.f(index)) - }).bind("127.0.0.1:8080") - .unwrap() - .start(); - - println!("Started http server: 127.0.0.1:8080"); - let _ = sys.run(); + .service(web::resource("/").to(index)) + }) + .bind("127.0.0.1:8080")? + .run() } diff --git a/static_index/Cargo.toml b/static_index/Cargo.toml index 6f427322..7cb6c402 100644 --- a/static_index/Cargo.toml +++ b/static_index/Cargo.toml @@ -2,11 +2,12 @@ name = "static_index" version = "0.1.0" authors = ["Jose Marinez "] -workspace = "../" +workspace = ".." +edition = "2018" [dependencies] futures = "0.1" env_logger = "0.5" -actix = "0.7" -actix-web = "0.7" +actix-web = "1.0.0" +actix-files = "0.1.1" diff --git a/static_index/src/main.rs b/static_index/src/main.rs index 25c77c03..ced7b689 100644 --- a/static_index/src/main.rs +++ b/static_index/src/main.rs @@ -1,28 +1,19 @@ -extern crate actix; -extern crate actix_web; -extern crate env_logger; +use actix_files as fs; +use actix_web::{middleware, App, HttpServer}; -use actix_web::{fs, middleware, server, App}; - -fn main() { - ::std::env::set_var("RUST_LOG", "actix_web=info"); - ::std::env::set_var("RUST_BACKTRACE", "1"); +fn main() -> std::io::Result<()> { + std::env::set_var("RUST_LOG", "actix_web=info"); env_logger::init(); - let sys = actix::System::new("static_index"); - - server::new(|| { + HttpServer::new(|| { App::new() - // enable logger - .middleware(middleware::Logger::default()) - .handler( - "/", - fs::StaticFiles::new("./static/").unwrap().index_file("index.html") + // enable logger + .wrap(middleware::Logger::default()) + .service( + // static files + fs::Files::new("/", "./static/").index_file("index.html"), ) - }).bind("127.0.0.1:8080") - .expect("Can not start server on given IP/Port") - .start(); - - println!("Started http server: 127.0.0.1:8080"); - let _ = sys.run(); + }) + .bind("127.0.0.1:8080")? + .run() } diff --git a/template_askama/Cargo.toml b/template_askama/Cargo.toml index 5eba342c..6d9cb6b8 100644 --- a/template_askama/Cargo.toml +++ b/template_askama/Cargo.toml @@ -2,14 +2,13 @@ name = "template-askama" version = "0.1.0" authors = ["Nikolay Kim "] -workspace = "../" +workspace = ".." +edition = "2018" [dependencies] -env_logger = "0.5" -askama = "0.6" - -actix = "0.7" -actix-web = "0.7" +actix-web = "1.0.0" +env_logger = "0.6" +askama = "0.8" [build-dependencies] -askama = "0.6" +askama = "0.8" diff --git a/template_askama/build.rs b/template_askama/build.rs deleted file mode 100644 index 89e3e6b1..00000000 --- a/template_askama/build.rs +++ /dev/null @@ -1,5 +0,0 @@ -extern crate askama; - -fn main() { - askama::rerun_if_templates_changed(); -} diff --git a/template_askama/src/main.rs b/template_askama/src/main.rs index 4a34abf1..f117ab36 100644 --- a/template_askama/src/main.rs +++ b/template_askama/src/main.rs @@ -1,11 +1,6 @@ -extern crate actix; -extern crate actix_web; -#[macro_use] -extern crate askama; - use std::collections::HashMap; -use actix_web::{http, server, App, HttpResponse, Query, Result}; +use actix_web::{web, App, HttpResponse, HttpServer, Result}; use askama::Template; #[derive(Template)] @@ -19,29 +14,25 @@ struct UserTemplate<'a> { #[template(path = "index.html")] struct Index; -fn index(query: Query>) -> Result { +fn index(query: web::Query>) -> Result { let s = if let Some(name) = query.get("name") { UserTemplate { - name: name, + name, text: "Welcome!", - }.render() - .unwrap() + } + .render() + .unwrap() } else { Index.render().unwrap() }; Ok(HttpResponse::Ok().content_type("text/html").body(s)) } -fn main() { - let sys = actix::System::new("template-askama"); - +fn main() -> std::io::Result<()> { // start http server - server::new(move || { - App::new().resource("/", |r| r.method(http::Method::GET).with(index)) - }).bind("127.0.0.1:8080") - .unwrap() - .start(); - - println!("Started http server: 127.0.0.1:8080"); - let _ = sys.run(); + HttpServer::new(move || { + App::new().service(web::resource("/").route(web::get().to(index))) + }) + .bind("127.0.0.1:8080")? + .run() } diff --git a/template_handlebars/Cargo.toml b/template_handlebars/Cargo.toml new file mode 100644 index 00000000..11fb0c58 --- /dev/null +++ b/template_handlebars/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "template_handlebars" +version = "0.1.0" +authors = ["Alexandru Tiniuc "] +edition = "2018" + +[dependencies] +actix-web = "1.0" +handlebars = "2.0.0-beta.2" +serde_json = "1.0" \ No newline at end of file diff --git a/template_handlebars/src/README.md b/template_handlebars/src/README.md new file mode 100644 index 00000000..88139535 --- /dev/null +++ b/template_handlebars/src/README.md @@ -0,0 +1,7 @@ +# Handlebars + +This is an example of how to use Actix Web with the [Handlebars templating language](https://crates.io/crates/handlebars), which is currently the most popular crate that achieves this. After starting the server with `cargo run`, you may visit the following pages: + +- http://localhost:8080 +- http://localhost:8080/Emma/documents +- http://localhost:8080/Bob/passwordds \ No newline at end of file diff --git a/template_handlebars/src/main.rs b/template_handlebars/src/main.rs new file mode 100644 index 00000000..7d1f97bd --- /dev/null +++ b/template_handlebars/src/main.rs @@ -0,0 +1,54 @@ +#[macro_use] +extern crate actix_web; + +#[macro_use] +extern crate serde_json; + +use actix_web::web; +use actix_web::{App, HttpResponse, HttpServer}; + +use handlebars::Handlebars; + +use std::io; + +// Macro documentation can be found in the actix_web_codegen crate +#[get("/")] +fn index(hb: web::Data) -> HttpResponse { + let data = json!({ + "name": "Handlebars" + }); + let body = hb.render("index", &data).unwrap(); + + HttpResponse::Ok().body(body) +} + +#[get("/{user}/{data}")] +fn user(hb: web::Data, info: web::Path<(String, String)>) -> HttpResponse { + let data = json!({ + "user": info.0, + "data": info.1 + }); + let body = hb.render("user", &data).unwrap(); + + HttpResponse::Ok().body(body) +} + +fn main() -> io::Result<()> { + // Handlebars uses a repository for the compiled templates. This object must be + // shared between the application threads, and is therefore passed to the + // Application Builder as an atomic reference-counted pointer. + let mut handlebars = Handlebars::new(); + handlebars + .register_templates_directory(".html", "./static/templates") + .unwrap(); + let handlebars_ref = web::Data::new(handlebars); + + HttpServer::new(move || { + App::new() + .register_data(handlebars_ref.clone()) + .service(index) + .service(user) + }) + .bind("127.0.0.1:8080")? + .run() +} diff --git a/template_handlebars/static/templates/index.html b/template_handlebars/static/templates/index.html new file mode 100644 index 00000000..867c114e --- /dev/null +++ b/template_handlebars/static/templates/index.html @@ -0,0 +1,12 @@ + + + + + {{name}} Example + + + +

{{name}} example

+

This is an example of how to use {{name}} with Actix-Web.

+ + \ No newline at end of file diff --git a/template_handlebars/static/templates/user.html b/template_handlebars/static/templates/user.html new file mode 100644 index 00000000..59e2f1fa --- /dev/null +++ b/template_handlebars/static/templates/user.html @@ -0,0 +1,12 @@ + + + + + {{user}}'s homepage + + + +

Welcome back, {{user}}

+

Here's your {{data}}.

+ + \ No newline at end of file diff --git a/template_tera/Cargo.toml b/template_tera/Cargo.toml index 9e55bf4a..cbf2d300 100644 --- a/template_tera/Cargo.toml +++ b/template_tera/Cargo.toml @@ -2,10 +2,10 @@ name = "template-tera" version = "0.1.0" authors = ["Nikolay Kim "] -workspace = "../" +workspace = ".." +edition = "2018" [dependencies] -env_logger = "0.5" -tera = "*" -actix = "0.7" -actix-web = "0.7" +env_logger = "0.6" +tera = "0.11" +actix-web = "1.0.0" diff --git a/template_tera/src/main.rs b/template_tera/src/main.rs index e7e2c780..a5942140 100644 --- a/template_tera/src/main.rs +++ b/template_tera/src/main.rs @@ -1,57 +1,42 @@ -extern crate actix; -extern crate actix_web; -extern crate env_logger; #[macro_use] extern crate tera; use std::collections::HashMap; -use actix_web::{ - error, http, middleware, server, App, Error, HttpResponse, Query, State, -}; - -struct AppState { - template: tera::Tera, // <- store tera template in application state -} +use actix_web::{error, middleware, web, App, Error, HttpResponse, HttpServer}; +// store tera template in application state fn index( - (state, query): (State, Query>), + tmpl: web::Data, + query: web::Query>, ) -> Result { let s = if let Some(name) = query.get("name") { - // <- submitted form + // submitted form let mut ctx = tera::Context::new(); - ctx.add("name", &name.to_owned()); - ctx.add("text", &"Welcome!".to_owned()); - state - .template - .render("user.html", &ctx) + ctx.insert("name", &name.to_owned()); + ctx.insert("text", &"Welcome!".to_owned()); + tmpl.render("user.html", &ctx) .map_err(|_| error::ErrorInternalServerError("Template error"))? } else { - state - .template - .render("index.html", &tera::Context::new()) + tmpl.render("index.html", &tera::Context::new()) .map_err(|_| error::ErrorInternalServerError("Template error"))? }; Ok(HttpResponse::Ok().content_type("text/html").body(s)) } -fn main() { - ::std::env::set_var("RUST_LOG", "actix_web=info"); +fn main() -> std::io::Result<()> { + std::env::set_var("RUST_LOG", "actix_web=info"); env_logger::init(); - let sys = actix::System::new("tera-example"); - server::new(|| { + HttpServer::new(|| { let tera = compile_templates!(concat!(env!("CARGO_MANIFEST_DIR"), "/templates/**/*")); - App::with_state(AppState{template: tera}) - // enable logger - .middleware(middleware::Logger::default()) - .resource("/", |r| r.method(http::Method::GET).with(index)) - }).bind("127.0.0.1:8080") - .unwrap() - .start(); - - println!("Started http server: 127.0.0.1:8080"); - let _ = sys.run(); + App::new() + .data(tera) + .wrap(middleware::Logger::default()) // enable logger + .service(web::resource("/").route(web::get().to(index))) + }) + .bind("127.0.0.1:8080")? + .run() } diff --git a/template_yarte/Cargo.toml b/template_yarte/Cargo.toml new file mode 100644 index 00000000..0069a0df --- /dev/null +++ b/template_yarte/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "template_yarte" +version = "0.0.1" +authors = ["Rust-iendo "] +publish = false +edition = "2018" + +workspace = ".." + +[dependencies] +env_logger = "0.6" + +yarte = { version = "0.3", features=["with-actix-web"] } + +actix-web = "1.0" + +[build-dependencies] +yarte = { version = "0.3", features=["with-actix-web"] } + +[dev-dependencies] +bytes = "0.4" diff --git a/template_yarte/README.md b/template_yarte/README.md new file mode 100644 index 00000000..21de4169 --- /dev/null +++ b/template_yarte/README.md @@ -0,0 +1,12 @@ +# yarte + +Minimal example of using template [yarte](https://gitlab.com/r-iendo/yarte) that displays a form. + +[Template benchmarks in stable](https://gitlab.com/botika/template-benchmarks-rs) + +```bash +cargo test + +cargo run +``` +> open `localhost:8080` diff --git a/template_yarte/build.rs b/template_yarte/build.rs new file mode 100644 index 00000000..da256d18 --- /dev/null +++ b/template_yarte/build.rs @@ -0,0 +1,5 @@ +use yarte::recompile; + +fn main() { + recompile::when_changed(); +} diff --git a/template_yarte/src/main.rs b/template_yarte/src/main.rs new file mode 100644 index 00000000..6a7faa0c --- /dev/null +++ b/template_yarte/src/main.rs @@ -0,0 +1,127 @@ +#[macro_use] +extern crate actix_web; + +use std::collections::HashMap; + +use actix_web::{middleware::Logger, web, App, HttpServer, Responder}; +use yarte::Template; + +#[derive(Template)] +#[template(path = "index.hbs")] +struct IndexTemplate { + query: web::Query>, +} + +#[get("/")] +pub fn index(query: web::Query>) -> impl Responder { + IndexTemplate { query } +} + +fn main() -> std::io::Result<()> { + std::env::set_var("RUST_LOG", "actix_web=info"); + env_logger::init(); + + // start http server + HttpServer::new(move || App::new().wrap(Logger::default()).service(index)) + .bind("127.0.0.1:8080")? + .run() +} + +#[cfg(test)] +mod test { + use super::*; + use actix_web::{http, test as atest}; + use bytes::Bytes; + + #[test] + fn test() { + let mut app = atest::init_service(App::new().service(index)); + + let req = atest::TestRequest::with_uri("/").to_request(); + let resp = atest::call_service(&mut app, req); + + assert!(resp.status().is_success()); + + assert_eq!( + resp.headers().get(http::header::CONTENT_TYPE).unwrap(), + "text/html" + ); + + let bytes = atest::read_body(resp); + assert_eq!( + bytes, + Bytes::from_static( + "\ + \ + Actix web\ +

Welcome!

\ +

What is your name?

\ +
\ + Name: \ +
Last name: \ +

\ +
\ + " + .as_ref() + ) + ); + + let req = atest::TestRequest::with_uri("/?name=foo&lastname=bar").to_request(); + let resp = atest::call_service(&mut app, req); + + assert!(resp.status().is_success()); + + assert_eq!( + resp.headers().get(http::header::CONTENT_TYPE).unwrap(), + "text/html" + ); + + let bytes = atest::read_body(resp); + assert_eq!( + bytes, + Bytes::from_static( + "\ + \ + Actix web\ + \ +

Hi, foo bar!

Welcome

\ + " + .as_ref() + ) + ); + + let req = atest::TestRequest::with_uri("/?name=foo").to_request(); + let resp = atest::call_service(&mut app, req); + + assert!(resp.status().is_server_error()); + + let req = atest::TestRequest::with_uri("/?lastname=bar").to_request(); + let resp = atest::call_service(&mut app, req); + + assert!(resp.status().is_success()); + + assert_eq!( + resp.headers().get(http::header::CONTENT_TYPE).unwrap(), + "text/html" + ); + + let bytes = atest::read_body(resp); + assert_eq!( + bytes, + Bytes::from_static( + "\ + \ + Actix web\ +

Welcome!

\ +

What is your name?

\ +
\ + Name: \ +
Last name: \ +

\ +
\ + " + .as_ref() + ) + ); + } +} diff --git a/template_yarte/templates/deep/more/card/form.hbs b/template_yarte/templates/deep/more/card/form.hbs new file mode 100644 index 00000000..a5b75a60 --- /dev/null +++ b/template_yarte/templates/deep/more/card/form.hbs @@ -0,0 +1,20 @@ +{{! + Form: What is your name? +!}} +{{> ../deep/welcome id = "welcome", tag = "h1", tail = '!' ~}} +
+ {{! Title !}} +

What is your name?

+ + {{! Form !}} +
+ {{! Input name !}} + Name: + {{! Input last name !}} +
Last name: + + {{! Submit !}} +

+ + {{! Order and remove whitespace with comments !}} +
diff --git a/template_yarte/templates/deep/more/card/hi.hbs b/template_yarte/templates/deep/more/card/hi.hbs new file mode 100644 index 00000000..7bd929f1 --- /dev/null +++ b/template_yarte/templates/deep/more/card/hi.hbs @@ -0,0 +1,8 @@ +{{! + Hi message: + args: + - lastname + - name +!}} +

Hi, {{ name }} {{ lastname }}!

+{{~> alias/welcome id = "hi", tag = 'p', tail = "" }} diff --git a/template_yarte/templates/deep/more/deep/welcome.hbs b/template_yarte/templates/deep/more/deep/welcome.hbs new file mode 100644 index 00000000..e0fe08b1 --- /dev/null +++ b/template_yarte/templates/deep/more/deep/welcome.hbs @@ -0,0 +1,8 @@ +{{! + Welcome card: + args: + - tag + - id + - tail +!}} +<{{ tag }} id="{{ id }}" class="welcome">Welcome{{ tail }} diff --git a/template_yarte/templates/deep/more/doc/head.hbs b/template_yarte/templates/deep/more/doc/head.hbs new file mode 100644 index 00000000..309846d0 --- /dev/null +++ b/template_yarte/templates/deep/more/doc/head.hbs @@ -0,0 +1 @@ +{{ title }} diff --git a/template_yarte/templates/deep/more/doc/t.hbs b/template_yarte/templates/deep/more/doc/t.hbs new file mode 100644 index 00000000..0e76edd6 --- /dev/null +++ b/template_yarte/templates/deep/more/doc/t.hbs @@ -0,0 +1 @@ + diff --git a/template_yarte/templates/index.hbs b/template_yarte/templates/index.hbs new file mode 100644 index 00000000..b3afdf48 --- /dev/null +++ b/template_yarte/templates/index.hbs @@ -0,0 +1,12 @@ +{{! Simple example !}} +{{> doc/t ~}} + +{{~> doc/head title = "Actix web" ~}} + + {{~#if let Some(name) = query.get("name") }} + {{ let lastname = query.get("lastname").ok_or(yarte::Error)? }} + {{> card/hi ~}} + {{ else ~}} + {{> card/form ~}} + {{/if ~}} + diff --git a/template_yarte/yarte.toml b/template_yarte/yarte.toml new file mode 100644 index 00000000..b3c60a67 --- /dev/null +++ b/template_yarte/yarte.toml @@ -0,0 +1,9 @@ +# root dir of templates +[main] +dir = "templates" + +# Alias for partials. In call, change the start of partial path with one of this, if exist. +[partials] +alias = "./deep/more/deep" +doc = "./deep/more/doc" +card = "./deep/more/card" diff --git a/tls/Cargo.toml b/tls/Cargo.toml index dcae3d35..f68f361b 100644 --- a/tls/Cargo.toml +++ b/tls/Cargo.toml @@ -1,15 +1,16 @@ [package] name = "tls-example" -version = "0.1.0" +version = "0.2.0" authors = ["Nikolay Kim "] -workspace = "../" +edition = "2018" +workspace = ".." [[bin]] name = "tls-server" path = "src/main.rs" [dependencies] -env_logger = "0.5" +actix-rt = "0.2" +actix-web = { version="1.0.0", features=["ssl"] } +env_logger = "0.6" openssl = { version="0.10" } -actix = "0.7" -actix-web = { version = "0.7", features=["ssl"] } diff --git a/tls/cert.pem b/tls/cert.pem index 159aacea..9a744d16 100644 --- a/tls/cert.pem +++ b/tls/cert.pem @@ -1,31 +1,16 @@ -----BEGIN CERTIFICATE----- -MIIFPjCCAyYCCQDvLYiYD+jqeTANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQGEwJV -UzELMAkGA1UECAwCQ0ExCzAJBgNVBAcMAlNGMRAwDgYDVQQKDAdDb21wYW55MQww -CgYDVQQLDANPcmcxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xODAxMjUx -NzQ2MDFaFw0xOTAxMjUxNzQ2MDFaMGExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJD -QTELMAkGA1UEBwwCU0YxEDAOBgNVBAoMB0NvbXBhbnkxDDAKBgNVBAsMA09yZzEY -MBYGA1UEAwwPd3d3LmV4YW1wbGUuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A -MIICCgKCAgEA2WzIA2IpVR9Tb9EFhITlxuhE5rY2a3S6qzYNzQVgSFggxXEPn8k1 -sQEcer5BfAP986Sck3H0FvB4Bt/I8PwOtUCmhwcc8KtB5TcGPR4fjXnrpC+MIK5U -NLkwuyBDKziYzTdBj8kUFX1WxmvEHEgqToPOZfBgsS71cJAR/zOWraDLSRM54jXy -voLZN4Ti9rQagQrvTQ44Vz5ycDQy7UxtbUGh1CVv69vNVr7/SOOh/Nw5FNOZWLWr -odGyoec5wh9iqRZgRqiTUc6Lt7V2RWc2X2gjwST2UfI+U46Ip3oaQ7ZD4eAkoqND -xdniBZAykVG3c/99ux4BAESTF8fsNch6UticBxYMuTu+ouvP0psfI9wwwNliJDmA -CRUTB9AgRynbL1AzhqQoDfsb98IZfjfNOpwnwuLwpMAPhbgd5KNdZaIJ4Hb6/stI -yFElOExxd3TAxF2Gshd/lq1JcNHAZ1DSXV5MvOWT/NWgXwbIzUgQ8eIi+HuDYX2U -UuaB6R8tbd52H7rbUv6HrfinuSlKWqjSYLkiKHkwUpoMw8y9UycRSzs1E9nPwPTO -vRXb0mNCQeBCV9FvStNVXdCUTT8LGPv87xSD2pmt7LijlE6mHLG8McfcWkzA69un -CEHIFAFDimTuN7EBljc119xWFTcHMyoZAfFF+oTqwSbBGImruCxnaJECAwEAATAN -BgkqhkiG9w0BAQsFAAOCAgEApavsgsn7SpPHfhDSN5iZs1ILZQRewJg0Bty0xPfk -3tynSW6bNH3nSaKbpsdmxxomthNSQgD2heOq1By9YzeOoNR+7Pk3s4FkASnf3ToI -JNTUasBFFfaCG96s4Yvs8KiWS/k84yaWuU8c3Wb1jXs5Rv1qE1Uvuwat1DSGXSoD -JNluuIkCsC4kWkyq5pWCGQrabWPRTWsHwC3PTcwSRBaFgYLJaR72SloHB1ot02zL -d2age9dmFRFLLCBzP+D7RojBvL37qS/HR+rQ4SoQwiVc/JzaeqSe7ZbvEH9sZYEu -ALowJzgbwro7oZflwTWunSeSGDSltkqKjvWvZI61pwfHKDahUTmZ5h2y67FuGEaC -CIOUI8dSVSPKITxaq3JL4ze2e9/0Lt7hj19YK2uUmtMAW5Tirz4Yx5lyGH9U8Wur -y/X8VPxTc4A9TMlJgkyz0hqvhbPOT/zSWB10zXh0glKAsSBryAOEDxV1UygmSir7 -YV8Qaq+oyKUTMc1MFq5vZ07M51EPaietn85t8V2Y+k/8XYltRp32NxsypxAJuyxh -g/ko6RVTrWa1sMvz/F9LFqAdKiK5eM96lh9IU4xiLg4ob8aS/GRAA8oIFkZFhLrt -tOwjIUPmEPyHWFi8dLpNuQKYalLYhuwZftG/9xV+wqhKGZO9iPrpHSYBRTap8w2y -1QU= +MIICljCCAX4CCQDztMNlxk6oeTANBgkqhkiG9w0BAQsFADANMQswCQYDVQQIDAJj +YTAeFw0xOTAzMDcwNzEyNThaFw0yMDAzMDYwNzEyNThaMA0xCzAJBgNVBAgMAmNh +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0GMP3YzDVFWgNhRiHnfe +d192131Zi23p8WiutneD9I5WO42c79fOXsxLWn+2HSqPvCPHIBLoMX8o9lgCxt2P +/JUCAWbrE2EuvhkMrWk6/q7xB211XZYfnkqdt7mA0jMUC5o32AX3ew456TAq5P8Y +dq9H/qXdRtAvKD0QdkFfq8ePCiqOhcqacZ/NWva7R4HdgTnbL1DRQjGBXszI07P9 +1yw8GOym46uxNHRujQp3lYEhc1V3JTF9kETpSBHyEAkQ8WHxGf8UBHDhh7hcc+KI +JHMlVYy5wDv4ZJeYsY1rD6/n4tyd3r0yzBM57UGf6qrVZEYmLB7Jad+8Df5vIoGh +WwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQB1DEu9NiShCfQuA17MG5O0Jr2/PS1z +/+HW7oW15WXpqDKOEJalid31/Bzwvwq0bE12xKE4ZLdbqJHmJTdSUoGfOfBZKka6 +R2thOjqH7hFvxjfgS7kBy5BrRZewM9xKIJ6zU6+6mxR64x9vmkOmppV0fx5clZjH +c7qn5kSNWTMsFbjPnb5BeJJwZdqpMLs99jgoMvGtCUmkyVYODGhh65g6tR9kIPvM +zu/Cw122/y7tFfkuknMSYwGEYF3XcZpXt54a6Lu5hk6PuOTsK+7lC+HX7CSF1dpv +u1szL5fDgiCBFCnyKeOqF61mxTCUht3U++37VDFvhzN1t6HIVTYm2JJ7 -----END CERTIFICATE----- diff --git a/tls/key.pem b/tls/key.pem index aac387c6..4416facc 100644 --- a/tls/key.pem +++ b/tls/key.pem @@ -1,51 +1,28 @@ ------BEGIN RSA PRIVATE KEY----- -MIIJKAIBAAKCAgEA2WzIA2IpVR9Tb9EFhITlxuhE5rY2a3S6qzYNzQVgSFggxXEP -n8k1sQEcer5BfAP986Sck3H0FvB4Bt/I8PwOtUCmhwcc8KtB5TcGPR4fjXnrpC+M -IK5UNLkwuyBDKziYzTdBj8kUFX1WxmvEHEgqToPOZfBgsS71cJAR/zOWraDLSRM5 -4jXyvoLZN4Ti9rQagQrvTQ44Vz5ycDQy7UxtbUGh1CVv69vNVr7/SOOh/Nw5FNOZ -WLWrodGyoec5wh9iqRZgRqiTUc6Lt7V2RWc2X2gjwST2UfI+U46Ip3oaQ7ZD4eAk -oqNDxdniBZAykVG3c/99ux4BAESTF8fsNch6UticBxYMuTu+ouvP0psfI9wwwNli -JDmACRUTB9AgRynbL1AzhqQoDfsb98IZfjfNOpwnwuLwpMAPhbgd5KNdZaIJ4Hb6 -/stIyFElOExxd3TAxF2Gshd/lq1JcNHAZ1DSXV5MvOWT/NWgXwbIzUgQ8eIi+HuD -YX2UUuaB6R8tbd52H7rbUv6HrfinuSlKWqjSYLkiKHkwUpoMw8y9UycRSzs1E9nP -wPTOvRXb0mNCQeBCV9FvStNVXdCUTT8LGPv87xSD2pmt7LijlE6mHLG8McfcWkzA -69unCEHIFAFDimTuN7EBljc119xWFTcHMyoZAfFF+oTqwSbBGImruCxnaJECAwEA -AQKCAgAME3aoeXNCPxMrSri7u4Xnnk71YXl0Tm9vwvjRQlMusXZggP8VKN/KjP0/ -9AE/GhmoxqPLrLCZ9ZE1EIjgmZ9Xgde9+C8rTtfCG2RFUL7/5J2p6NonlocmxoJm -YkxYwjP6ce86RTjQWL3RF3s09u0inz9/efJk5O7M6bOWMQ9VZXDlBiRY5BYvbqUR -6FeSzD4MnMbdyMRoVBeXE88gTvZk8xhB6DJnLzYgc0tKiRoeKT0iYv5JZw25VyRM -ycLzfTrFmXCPfB1ylb483d9Ly4fBlM8nkx37PzEnAuukIawDxsPOb9yZC+hfvNJI -7NFiMN+3maEqG2iC00w4Lep4skHY7eHUEUMl+Wjr+koAy2YGLWAwHZQTm7iXn9Ab -L6adL53zyCKelRuEQOzbeosJAqS+5fpMK0ekXyoFIuskj7bWuIoCX7K/kg6q5IW+ -vC2FrlsrbQ79GztWLVmHFO1I4J9M5r666YS0qdh8c+2yyRl4FmSiHfGxb3eOKpxQ -b6uI97iZlkxPF9LYUCSc7wq0V2gGz+6LnGvTHlHrOfVXqw/5pLAKhXqxvnroDTwz -0Ay/xFF6ei/NSxBY5t8ztGCBm45wCU3l8pW0X6dXqwUipw5b4MRy1VFRu6rqlmbL -OPSCuLxqyqsigiEYsBgS/icvXz9DWmCQMPd2XM9YhsHvUq+R4QKCAQEA98EuMMXI -6UKIt1kK2t/3OeJRyDd4iv/fCMUAnuPjLBvFE4cXD/SbqCxcQYqb+pue3PYkiTIC -71rN8OQAc5yKhzmmnCE5N26br/0pG4pwEjIr6mt8kZHmemOCNEzvhhT83nfKmV0g -9lNtuGEQMiwmZrpUOF51JOMC39bzcVjYX2Cmvb7cFbIq3lR0zwM+aZpQ4P8LHCIu -bgHmwbdlkLyIULJcQmHIbo6nPFB3ZZE4mqmjwY+rA6Fh9rgBa8OFCfTtrgeYXrNb -IgZQ5U8GoYRPNC2ot0vpTinraboa/cgm6oG4M7FW1POCJTl+/ktHEnKuO5oroSga -/BSg7hCNFVaOhwKCAQEA4Kkys0HtwEbV5mY/NnvUD5KwfXX7BxoXc9lZ6seVoLEc -KjgPYxqYRVrC7dB2YDwwp3qcRTi/uBAgFNm3iYlDzI4xS5SeaudUWjglj7BSgXE2 -iOEa7EwcvVPluLaTgiWjlzUKeUCNNHWSeQOt+paBOT+IgwRVemGVpAgkqQzNh/nP -tl3p9aNtgzEm1qVlPclY/XUCtf3bcOR+z1f1b4jBdn0leu5OhnxkC+Htik+2fTXD -jt6JGrMkanN25YzsjnD3Sn+v6SO26H99wnYx5oMSdmb8SlWRrKtfJHnihphjG/YY -l1cyorV6M/asSgXNQfGJm4OuJi0I4/FL2wLUHnU+JwKCAQEAzh4WipcRthYXXcoj -gMKRkMOb3GFh1OpYqJgVExtudNTJmZxq8GhFU51MR27Eo7LycMwKy2UjEfTOnplh -Us2qZiPtW7k8O8S2m6yXlYUQBeNdq9IuuYDTaYD94vsazscJNSAeGodjE+uGvb1q -1wLqE87yoE7dUInYa1cOA3+xy2/CaNuviBFJHtzOrSb6tqqenQEyQf6h9/12+DTW -t5pSIiixHrzxHiFqOoCLRKGToQB+71rSINwTf0nITNpGBWmSj5VcC3VV3TG5/XxI -fPlxV2yhD5WFDPVNGBGvwPDSh4jSMZdZMSNBZCy4XWFNSKjGEWoK4DFYed3DoSt9 -5IG1YwKCAQA63ntHl64KJUWlkwNbboU583FF3uWBjee5VqoGKHhf3CkKMxhtGqnt -+oN7t5VdUEhbinhqdx1dyPPvIsHCS3K1pkjqii4cyzNCVNYa2dQ00Qq+QWZBpwwc -3GAkz8rFXsGIPMDa1vxpU6mnBjzPniKMcsZ9tmQDppCEpBGfLpio2eAA5IkK8eEf -cIDB3CM0Vo94EvI76CJZabaE9IJ+0HIJb2+jz9BJ00yQBIqvJIYoNy9gP5Xjpi+T -qV/tdMkD5jwWjHD3AYHLWKUGkNwwkAYFeqT/gX6jpWBP+ZRPOp011X3KInJFSpKU -DT5GQ1Dux7EMTCwVGtXqjO8Ym5wjwwsfAoIBAEcxlhIW1G6BiNfnWbNPWBdh3v/K -5Ln98Rcrz8UIbWyl7qNPjYb13C1KmifVG1Rym9vWMO3KuG5atK3Mz2yLVRtmWAVc -fxzR57zz9MZFDun66xo+Z1wN3fVxQB4CYpOEI4Lb9ioX4v85hm3D6RpFukNtRQEc -Gfr4scTjJX4jFWDp0h6ffMb8mY+quvZoJ0TJqV9L9Yj6Ksdvqez/bdSraev97bHQ -4gbQxaTZ6WjaD4HjpPQefMdWp97Metg0ZQSS8b8EzmNFgyJ3XcjirzwliKTAQtn6 -I2sd0NCIooelrKRD8EJoDUwxoOctY7R97wpZ7/wEHU45cBCbRV3H4JILS5c= ------END RSA PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDQYw/djMNUVaA2 +FGIed953X3bXfVmLbenxaK62d4P0jlY7jZzv185ezEtaf7YdKo+8I8cgEugxfyj2 +WALG3Y/8lQIBZusTYS6+GQytaTr+rvEHbXVdlh+eSp23uYDSMxQLmjfYBfd7Djnp +MCrk/xh2r0f+pd1G0C8oPRB2QV+rx48KKo6Fyppxn81a9rtHgd2BOdsvUNFCMYFe +zMjTs/3XLDwY7Kbjq7E0dG6NCneVgSFzVXclMX2QROlIEfIQCRDxYfEZ/xQEcOGH +uFxz4ogkcyVVjLnAO/hkl5ixjWsPr+fi3J3evTLMEzntQZ/qqtVkRiYsHslp37wN +/m8igaFbAgMBAAECggEAJI278rkGany6pcHdlEqik34DcrliQ7r8FoSuYQOF+hgd +uESXCttoL+jWLwHICEW3AOGlxFKMuGH95Xh6xDeJUl0xBN3wzm11rZLnTmPvHU3C +qfLha5Ex6qpcECZSGo0rLv3WXeZuCv/r2KPCYnj86ZTFpD2kGw/Ztc1AXf4Jsi/1 +478Mf23QmAvCAPimGCyjLQx2c9/vg/6K7WnDevY4tDuDKLeSJxKZBSHUn3cM1Bwj +2QzaHfSFA5XljOF5PLeR3cY5ncrrVLWChT9XuGt9YMdLAcSQxgE6kWV1RSCq+lbj +e6OOe879IrrqwBvMQfKQqnm1kl8OrfPMT5CNWKvEgQKBgQD8q5E4x9taDS9RmhRO +07ptsr/I795tX8CaJd/jc4xGuCGBqpNw/hVebyNNYQvpiYzDNBSEhtd59957VyET +hcrGyxD0ByKm8F/lPgFw5y6wi3RUnucCV/jxkMHmxVzYMbFUEGCQ0pIU9/GFS7RZ +9VjqRDeE86U3yHO+WCFoHtd8aQKBgQDTIhi0uq0oY87bUGnWbrrkR0UVRNPDG1BT +cuXACYlv/DV/XpxPC8iPK1UwG4XaOVxodtIRjdBqvb8fUM6HSY6qll64N/4/1jre +Ho+d4clE4tK6a9WU96CKxwHn2BrWUZJPtoldaCZJFJ7SfiHuLlqW7TtYFrOfPIjN +ADiqK+bHIwKBgQCpfIiAVwebo0Z/bWR77+iZFxMwvT4tjdJLVGaXUvXgpjjLmtkm +LTm2S8SZbiSodfz3H+M3dp/pj8wsXiiwyMlZifOITZT/+DPLOUmMK3cVM6ZH8QMy +fkJd/+UhYHhECSlTI10zKByXdi4LZNnIkhwfoLzBMRI9lfeV0dYu2qlfKQKBgEVI +kRbtk1kHt5/ceX62g3nZsV/TYDJMSkW4FJC6EHHBL8UGRQDjewMQUzogLgJ4hEx7 +gV/lS5lbftZF7CAVEU4FXjvRlAtav6KYIMTMjQGf9UrbjBEAWZxwxb1Q+y2NQxgJ +bHZMcRPWQnAMmBHTAEM6whicCoGcmb+77Nxa37ZFAoGBALBuUNeD3fKvQR8v6GoA +spv+RYL9TB4wz2Oe9EYSp9z5EiWlTmuvFz3zk8pHDSpntxYH5O5HJ/3OzwhHz9ym ++DNE9AP9LW9hAzMuu7Gob1h8ShGwJVYwrQN3q/83ooUL7WSAuVOLpzJ7BFFlcCjp +MhFvd9iOt/R0N30/3AbQXkOp +-----END PRIVATE KEY----- diff --git a/tls/src/main.rs b/tls/src/main.rs index 329e15db..7e95309a 100644 --- a/tls/src/main.rs +++ b/tls/src/main.rs @@ -1,26 +1,21 @@ -#![allow(unused_variables)] -extern crate actix; -extern crate actix_web; -extern crate env_logger; -extern crate openssl; +use std::io; -use actix_web::{http, middleware, server, App, Error, HttpRequest, HttpResponse}; +use actix_web::{middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer}; use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; /// simple handle -fn index(req: &HttpRequest) -> Result { +fn index(req: HttpRequest) -> Result { println!("{:?}", req); Ok(HttpResponse::Ok() .content_type("text/plain") .body("Welcome!")) } -fn main() { - if ::std::env::var("RUST_LOG").is_err() { - ::std::env::set_var("RUST_LOG", "actix_web=info"); - } +fn main() -> io::Result<()> { + std::env::set_var("RUST_LOG", "actix_web=debug"); env_logger::init(); - let sys = actix::System::new("ws-example"); + + let sys = actix_rt::System::new("tls-example"); // load ssl keys let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); @@ -29,22 +24,22 @@ fn main() { .unwrap(); builder.set_certificate_chain_file("cert.pem").unwrap(); - server::new(|| { + HttpServer::new(|| { App::new() // enable logger - .middleware(middleware::Logger::default()) + .wrap(middleware::Logger::default()) // register simple handler, handle all methods - .resource("/index.html", |r| r.f(index)) + .service(web::resource("/index.html").to(index)) // with path parameters - .resource("/", |r| r.method(http::Method::GET).f(|req| { + .service(web::resource("/").route(web::get().to(|| { HttpResponse::Found() .header("LOCATION", "/index.html") .finish() - })) - }).bind_ssl("127.0.0.1:8443", builder) - .unwrap() - .start(); + }))) + }) + .bind_ssl("127.0.0.1:8443", builder)? + .start(); println!("Started http server: 127.0.0.1:8443"); - let _ = sys.run(); + sys.run() } diff --git a/unix-socket/Cargo.toml b/unix-socket/Cargo.toml index 423ac7b1..0eba6e2e 100644 --- a/unix-socket/Cargo.toml +++ b/unix-socket/Cargo.toml @@ -2,11 +2,11 @@ name = "unix-socket" version = "0.1.0" authors = ["Messense Lv "] -workspace = "../" +workspace = ".." +edition = "2018" [dependencies] env_logger = "0.5" tokio-uds = "0.2" -actix = "0.7" -actix-web = "0.7" +actix-web = { version = "1.0.5", features = ["uds"] } diff --git a/unix-socket/README.md b/unix-socket/README.md index 03b0066a..94870320 100644 --- a/unix-socket/README.md +++ b/unix-socket/README.md @@ -7,7 +7,7 @@ Hello world! Although this will only one thread for handling incoming connections according to the -[documentation](https://actix.github.io/actix-web/actix_web/struct.HttpServer.html#method.start_incoming). +[documentation](https://actix.github.io/actix-web/actix_web/struct.HttpServer.html#method.bind_uds). And it does not delete the socket file (`/tmp/actix-uds.socket`) when stopping the server so it will fail to start next time you run it unless you delete diff --git a/unix-socket/src/main.rs b/unix-socket/src/main.rs index d999f8e9..055b597c 100644 --- a/unix-socket/src/main.rs +++ b/unix-socket/src/main.rs @@ -1,30 +1,22 @@ -extern crate actix; -extern crate actix_web; -extern crate env_logger; -extern crate tokio_uds; +use actix_web::{middleware, web, App, HttpRequest, HttpServer}; -use actix::*; -use actix_web::{middleware, server, App, HttpRequest}; -use tokio_uds::UnixListener; - -fn index(_req: &HttpRequest) -> &'static str { +fn index(_req: HttpRequest) -> &'static str { "Hello world!" } -fn main() { - ::std::env::set_var("RUST_LOG", "actix_web=info"); +fn main() -> std::io::Result<()> { + ::std::env::set_var("RUST_LOG", "actix_server=info,actix_web=info"); env_logger::init(); - let sys = actix::System::new("unix-socket"); - let listener = UnixListener::bind("/tmp/actix-uds.socket").expect("bind failed"); - server::new(|| { + HttpServer::new(|| { App::new() - // enable logger - .middleware(middleware::Logger::default()) - .resource("/index.html", |r| r.f(|_| "Hello world!")) - .resource("/", |r| r.f(index)) - }).start_incoming(listener.incoming(), false); - - println!("Started http server: /tmp/actix-uds.socket"); - let _ = sys.run(); + // enable logger - always register actix-web Logger middleware last + .wrap(middleware::Logger::default()) + .service( + web::resource("/index.html").route(web::get().to(|| "Hello world!")), + ) + .service(web::resource("/").to(index)) + }) + .bind_uds("/tmp/actix-uds.socket")? + .run() } diff --git a/web-cors/README.md b/web-cors/README.md index 6dd3d77f..cff3c1d0 100644 --- a/web-cors/README.md +++ b/web-cors/README.md @@ -10,6 +10,6 @@ $ cargo run ```bash $ cd web-cors/frontend $ npm install -$ npm run dev +$ npm run serve ``` -then open browser 'http://localhost:1234/' +then open browser 'http://localhost:8080' diff --git a/web-cors/backend/Cargo.toml b/web-cors/backend/Cargo.toml index f78d7adc..6800e5d6 100644 --- a/web-cors/backend/Cargo.toml +++ b/web-cors/backend/Cargo.toml @@ -3,16 +3,14 @@ name = "actix-web-cors" version = "0.1.0" authors = ["krircc "] workspace = "../../" +edition = "2018" [dependencies] +actix-web = "1.0.0" +actix-cors = "0.1.0" serde = "1.0" serde_derive = "1.0" serde_json = "1.0" -http = "0.1" - -actix = "0.7" -actix-web = "0.7" - dotenv = "0.10" -env_logger = "0.5" +env_logger = "0.6" futures = "0.1" diff --git a/web-cors/backend/src/main.rs b/web-cors/backend/src/main.rs index 8191ceeb..ed7b71b6 100644 --- a/web-cors/backend/src/main.rs +++ b/web-cors/backend/src/main.rs @@ -1,48 +1,28 @@ #[macro_use] extern crate serde_derive; -extern crate actix; -extern crate actix_web; -extern crate env_logger; -extern crate futures; -extern crate serde; -extern crate serde_json; -use actix_web::{ - http::{header, Method}, - middleware, - middleware::cors::Cors, - server, App, -}; -use std::env; +use actix_cors::Cors; +use actix_web::{http::header, middleware::Logger, web, App, HttpServer}; mod user; -use user::info; -fn main() { - env::set_var("RUST_LOG", "actix_web=info"); +fn main() -> std::io::Result<()> { + std::env::set_var("RUST_LOG", "actix_web=info"); env_logger::init(); - let sys = actix::System::new("Actix-web-CORS"); - - server::new(move || { + HttpServer::new(move || { App::new() - .middleware(middleware::Logger::default()) - .configure(|app| { - Cors::for_app(app) - .allowed_origin("http://localhost:1234") + .wrap( + Cors::new() + .allowed_origin("http://localhost:8080") .allowed_methods(vec!["GET", "POST"]) .allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT]) .allowed_header(header::CONTENT_TYPE) - .max_age(3600) - .resource("/user/info", |r| { - r.method(Method::POST).with(info); - }) - .register() - }) - }).bind("127.0.0.1:8000") - .unwrap() - .shutdown_timeout(2) - .start(); - - let _ = sys.run(); + .max_age(3600), + ) + .wrap(Logger::default()) + .service(web::resource("/user/info").route(web::post().to(user::info))) + }) + .bind("127.0.0.1:8000")? + .run() } diff --git a/web-cors/backend/src/user.rs b/web-cors/backend/src/user.rs index aca44cbf..ed795589 100644 --- a/web-cors/backend/src/user.rs +++ b/web-cors/backend/src/user.rs @@ -1,4 +1,4 @@ -use actix_web::{Json, Result}; +use actix_web::web; #[derive(Deserialize, Serialize, Debug)] pub struct Info { @@ -8,12 +8,12 @@ pub struct Info { confirm_password: String, } -pub fn info(info: Json) -> Result> { +pub fn info(info: web::Json) -> web::Json { println!("=========={:?}=========", info); - Ok(Json(Info { + web::Json(Info { username: info.username.clone(), email: info.email.clone(), password: info.password.clone(), confirm_password: info.confirm_password.clone(), - })) + }) } diff --git a/web-cors/frontend/.babelrc b/web-cors/frontend/.babelrc deleted file mode 100644 index 002b4aa0..00000000 --- a/web-cors/frontend/.babelrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "presets": ["env"] -} diff --git a/web-cors/frontend/.gitignore b/web-cors/frontend/.gitignore index 8875af86..765645ee 100644 --- a/web-cors/frontend/.gitignore +++ b/web-cors/frontend/.gitignore @@ -5,6 +5,7 @@ node_modules/ npm-debug.log* yarn-debug.log* yarn-error.log* +package-lock.json # Editor directories and files .idea diff --git a/web-cors/frontend/index.html b/web-cors/frontend/index.html index d71de81c..483dec27 100644 --- a/web-cors/frontend/index.html +++ b/web-cors/frontend/index.html @@ -6,8 +6,6 @@ webapp -
- +
- - \ No newline at end of file + diff --git a/web-cors/frontend/package-lock.json b/web-cors/frontend/package-lock.json deleted file mode 100644 index d89f0754..00000000 --- a/web-cors/frontend/package-lock.json +++ /dev/null @@ -1,7407 +0,0 @@ -{ - "name": "actix-web-cors", - "version": "0.1.0", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true - }, - "acorn": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.5.3.tgz", - "integrity": "sha512-jd5MkIUlbbmb07nXH0DT3y7rDVtkzDi4XZOUVWAer8ajmF/DTSSbl5oNFyDOl/OXA33Bl79+ypHhl2pN20VeOQ==", - "dev": true - }, - "alphanum-sort": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", - "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=", - "dev": true - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "requires": { - "micromatch": "3.1.10", - "normalize-path": "2.1.1" - } - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "1.0.3" - } - }, - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true - }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true - }, - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, - "asn1.js": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", - "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", - "dev": true, - "requires": { - "bn.js": "4.11.8", - "inherits": "2.0.3", - "minimalistic-assert": "1.0.1" - } - }, - "assert": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz", - "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=", - "dev": true, - "requires": { - "util": "0.10.3" - } - }, - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true - }, - "async-each": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", - "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=", - "dev": true - }, - "async-limiter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", - "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", - "dev": true - }, - "atob": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.0.tgz", - "integrity": "sha512-SuiKH8vbsOyCALjA/+EINmt/Kdl+TQPrtFgW7XZZcwtryFu9e5kQoX3bjCW6mIvGH1fbeAZZuvwGR5IlBRznGw==", - "dev": true - }, - "autoprefixer": { - "version": "6.7.7", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-6.7.7.tgz", - "integrity": "sha1-Hb0cg1ZY41zj+ZhAmdsAWFx4IBQ=", - "dev": true, - "requires": { - "browserslist": "1.7.7", - "caniuse-db": "1.0.30000828", - "normalize-range": "0.1.2", - "num2fraction": "1.2.2", - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0" - }, - "dependencies": { - "browserslist": { - "version": "1.7.7", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-1.7.7.tgz", - "integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=", - "dev": true, - "requires": { - "caniuse-db": "1.0.30000828", - "electron-to-chromium": "1.3.42" - } - }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "1.1.3", - "js-base64": "2.4.3", - "source-map": "0.5.7", - "supports-color": "3.2.3" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "1.0.0" - } - } - } - }, - "axios": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.17.1.tgz", - "integrity": "sha1-LY4+XQvb1zJ/kbyBT1xXZg+Bgk0=", - "requires": { - "follow-redirects": "1.4.1", - "is-buffer": "1.1.6" - } - }, - "babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", - "dev": true, - "requires": { - "chalk": "1.1.3", - "esutils": "2.0.2", - "js-tokens": "3.0.2" - } - }, - "babel-core": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.0.tgz", - "integrity": "sha1-rzL3izGm/O8RnIew/Y2XU/A6C7g=", - "dev": true, - "requires": { - "babel-code-frame": "6.26.0", - "babel-generator": "6.26.1", - "babel-helpers": "6.24.1", - "babel-messages": "6.23.0", - "babel-register": "6.26.0", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0", - "babylon": "6.18.0", - "convert-source-map": "1.5.1", - "debug": "2.6.9", - "json5": "0.5.1", - "lodash": "4.17.5", - "minimatch": "3.0.4", - "path-is-absolute": "1.0.1", - "private": "0.1.8", - "slash": "1.0.0", - "source-map": "0.5.7" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, - "babel-generator": { - "version": "6.26.1", - "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", - "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", - "dev": true, - "requires": { - "babel-messages": "6.23.0", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "detect-indent": "4.0.0", - "jsesc": "1.3.0", - "lodash": "4.17.5", - "source-map": "0.5.7", - "trim-right": "1.0.1" - }, - "dependencies": { - "jsesc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", - "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, - "babel-helper-builder-binary-assignment-operator-visitor": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz", - "integrity": "sha1-zORReto1b0IgvK6KAsKzRvmlZmQ=", - "dev": true, - "requires": { - "babel-helper-explode-assignable-expression": "6.24.1", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-helper-builder-react-jsx": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-6.26.0.tgz", - "integrity": "sha1-Of+DE7dci2Xc7/HzHTg+D/KkCKA=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "esutils": "2.0.2" - } - }, - "babel-helper-call-delegate": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz", - "integrity": "sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340=", - "dev": true, - "requires": { - "babel-helper-hoist-variables": "6.24.1", - "babel-runtime": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-helper-define-map": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz", - "integrity": "sha1-pfVtq0GiX5fstJjH66ypgZ+Vvl8=", - "dev": true, - "requires": { - "babel-helper-function-name": "6.24.1", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "lodash": "4.17.5" - } - }, - "babel-helper-explode-assignable-expression": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz", - "integrity": "sha1-8luCz33BBDPFX3BZLVdGQArCLKo=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-helper-function-name": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz", - "integrity": "sha1-00dbjAPtmCQqJbSDUasYOZ01gKk=", - "dev": true, - "requires": { - "babel-helper-get-function-arity": "6.24.1", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-helper-get-function-arity": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz", - "integrity": "sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-helper-hoist-variables": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz", - "integrity": "sha1-HssnaJydJVE+rbyZFKc/VAi+enY=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-helper-optimise-call-expression": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz", - "integrity": "sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-helper-regex": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz", - "integrity": "sha1-MlxZ+QL4LyS3T6zu0DY5VPZJXnI=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "lodash": "4.17.5" - } - }, - "babel-helper-remap-async-to-generator": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz", - "integrity": "sha1-XsWBgnrXI/7N04HxySg5BnbkVRs=", - "dev": true, - "requires": { - "babel-helper-function-name": "6.24.1", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-helper-replace-supers": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz", - "integrity": "sha1-v22/5Dk40XNpohPKiov3S2qQqxo=", - "dev": true, - "requires": { - "babel-helper-optimise-call-expression": "6.24.1", - "babel-messages": "6.23.0", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-helpers": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", - "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-template": "6.26.0" - } - }, - "babel-messages": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", - "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-check-es2015-constants": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz", - "integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-syntax-async-functions": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz", - "integrity": "sha1-ytnK0RkbWtY0vzCuCHI5HgZHvpU=", - "dev": true - }, - "babel-plugin-syntax-exponentiation-operator": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz", - "integrity": "sha1-nufoM3KQ2pUoggGmpX9BcDF4MN4=", - "dev": true - }, - "babel-plugin-syntax-jsx": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", - "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=", - "dev": true - }, - "babel-plugin-syntax-trailing-function-commas": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz", - "integrity": "sha1-ugNgk3+NBuQBgKQ/4NVhb/9TLPM=", - "dev": true - }, - "babel-plugin-transform-async-to-generator": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz", - "integrity": "sha1-ZTbjeK/2yx1VF6wOQOs+n8jQh2E=", - "dev": true, - "requires": { - "babel-helper-remap-async-to-generator": "6.24.1", - "babel-plugin-syntax-async-functions": "6.13.0", - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-es2015-arrow-functions": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz", - "integrity": "sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-es2015-block-scoped-functions": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz", - "integrity": "sha1-u8UbSflk1wy42OC5ToICRs46YUE=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-es2015-block-scoping": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz", - "integrity": "sha1-1w9SmcEwjQXBL0Y4E7CgnnOxiV8=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0", - "lodash": "4.17.5" - } - }, - "babel-plugin-transform-es2015-classes": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz", - "integrity": "sha1-WkxYpQyclGHlZLSyo7+ryXolhNs=", - "dev": true, - "requires": { - "babel-helper-define-map": "6.26.0", - "babel-helper-function-name": "6.24.1", - "babel-helper-optimise-call-expression": "6.24.1", - "babel-helper-replace-supers": "6.24.1", - "babel-messages": "6.23.0", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-plugin-transform-es2015-computed-properties": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz", - "integrity": "sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-template": "6.26.0" - } - }, - "babel-plugin-transform-es2015-destructuring": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz", - "integrity": "sha1-mXux8auWf2gtKwh2/jWNYOdlxW0=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-es2015-duplicate-keys": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz", - "integrity": "sha1-c+s9MQypaePvnskcU3QabxV2Qj4=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-plugin-transform-es2015-for-of": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz", - "integrity": "sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-es2015-function-name": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz", - "integrity": "sha1-g0yJhTvDaxrw86TF26qU/Y6sqos=", - "dev": true, - "requires": { - "babel-helper-function-name": "6.24.1", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-plugin-transform-es2015-literals": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz", - "integrity": "sha1-T1SgLWzWbPkVKAAZox0xklN3yi4=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-es2015-modules-amd": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz", - "integrity": "sha1-Oz5UAXI5hC1tGcMBHEvS8AoA0VQ=", - "dev": true, - "requires": { - "babel-plugin-transform-es2015-modules-commonjs": "6.26.0", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0" - } - }, - "babel-plugin-transform-es2015-modules-commonjs": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.0.tgz", - "integrity": "sha1-DYOUApt9xqvhqX7xgeAHWN0uXYo=", - "dev": true, - "requires": { - "babel-plugin-transform-strict-mode": "6.24.1", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-plugin-transform-es2015-modules-systemjs": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz", - "integrity": "sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM=", - "dev": true, - "requires": { - "babel-helper-hoist-variables": "6.24.1", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0" - } - }, - "babel-plugin-transform-es2015-modules-umd": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz", - "integrity": "sha1-rJl+YoXNGO1hdq22B9YCNErThGg=", - "dev": true, - "requires": { - "babel-plugin-transform-es2015-modules-amd": "6.24.1", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0" - } - }, - "babel-plugin-transform-es2015-object-super": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz", - "integrity": "sha1-JM72muIcuDp/hgPa0CH1cusnj40=", - "dev": true, - "requires": { - "babel-helper-replace-supers": "6.24.1", - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-es2015-parameters": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz", - "integrity": "sha1-V6w1GrScrxSpfNE7CfZv3wpiXys=", - "dev": true, - "requires": { - "babel-helper-call-delegate": "6.24.1", - "babel-helper-get-function-arity": "6.24.1", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-plugin-transform-es2015-shorthand-properties": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz", - "integrity": "sha1-JPh11nIch2YbvZmkYi5R8U3jiqA=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-plugin-transform-es2015-spread": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz", - "integrity": "sha1-1taKmfia7cRTbIGlQujdnxdG+NE=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-es2015-sticky-regex": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz", - "integrity": "sha1-AMHNsaynERLN8M9hJsLta0V8zbw=", - "dev": true, - "requires": { - "babel-helper-regex": "6.26.0", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-plugin-transform-es2015-template-literals": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz", - "integrity": "sha1-qEs0UPfp+PH2g51taH2oS7EjbY0=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-es2015-typeof-symbol": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz", - "integrity": "sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-es2015-unicode-regex": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz", - "integrity": "sha1-04sS9C6nMj9yk4fxinxa4frrNek=", - "dev": true, - "requires": { - "babel-helper-regex": "6.26.0", - "babel-runtime": "6.26.0", - "regexpu-core": "2.0.0" - } - }, - "babel-plugin-transform-exponentiation-operator": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz", - "integrity": "sha1-KrDJx/MJj6SJB3cruBP+QejeOg4=", - "dev": true, - "requires": { - "babel-helper-builder-binary-assignment-operator-visitor": "6.24.1", - "babel-plugin-syntax-exponentiation-operator": "6.13.0", - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-react-jsx": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx/-/babel-plugin-transform-react-jsx-6.24.1.tgz", - "integrity": "sha1-hAoCjn30YN/DotKfDA2R9jduZqM=", - "dev": true, - "requires": { - "babel-helper-builder-react-jsx": "6.26.0", - "babel-plugin-syntax-jsx": "6.18.0", - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-regenerator": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz", - "integrity": "sha1-4HA2lvveJ/Cj78rPi03KL3s6jy8=", - "dev": true, - "requires": { - "regenerator-transform": "0.10.1" - } - }, - "babel-plugin-transform-strict-mode": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz", - "integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-preset-env": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/babel-preset-env/-/babel-preset-env-1.6.1.tgz", - "integrity": "sha512-W6VIyA6Ch9ePMI7VptNn2wBM6dbG0eSz25HEiL40nQXCsXGTGZSTZu1Iap+cj3Q0S5a7T9+529l/5Bkvd+afNA==", - "dev": true, - "requires": { - "babel-plugin-check-es2015-constants": "6.22.0", - "babel-plugin-syntax-trailing-function-commas": "6.22.0", - "babel-plugin-transform-async-to-generator": "6.24.1", - "babel-plugin-transform-es2015-arrow-functions": "6.22.0", - "babel-plugin-transform-es2015-block-scoped-functions": "6.22.0", - "babel-plugin-transform-es2015-block-scoping": "6.26.0", - "babel-plugin-transform-es2015-classes": "6.24.1", - "babel-plugin-transform-es2015-computed-properties": "6.24.1", - "babel-plugin-transform-es2015-destructuring": "6.23.0", - "babel-plugin-transform-es2015-duplicate-keys": "6.24.1", - "babel-plugin-transform-es2015-for-of": "6.23.0", - "babel-plugin-transform-es2015-function-name": "6.24.1", - "babel-plugin-transform-es2015-literals": "6.22.0", - "babel-plugin-transform-es2015-modules-amd": "6.24.1", - "babel-plugin-transform-es2015-modules-commonjs": "6.26.0", - "babel-plugin-transform-es2015-modules-systemjs": "6.24.1", - "babel-plugin-transform-es2015-modules-umd": "6.24.1", - "babel-plugin-transform-es2015-object-super": "6.24.1", - "babel-plugin-transform-es2015-parameters": "6.24.1", - "babel-plugin-transform-es2015-shorthand-properties": "6.24.1", - "babel-plugin-transform-es2015-spread": "6.22.0", - "babel-plugin-transform-es2015-sticky-regex": "6.24.1", - "babel-plugin-transform-es2015-template-literals": "6.22.0", - "babel-plugin-transform-es2015-typeof-symbol": "6.23.0", - "babel-plugin-transform-es2015-unicode-regex": "6.24.1", - "babel-plugin-transform-exponentiation-operator": "6.24.1", - "babel-plugin-transform-regenerator": "6.26.0", - "browserslist": "2.11.3", - "invariant": "2.2.4", - "semver": "5.5.0" - } - }, - "babel-register": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", - "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", - "dev": true, - "requires": { - "babel-core": "6.26.0", - "babel-runtime": "6.26.0", - "core-js": "2.5.5", - "home-or-tmp": "2.0.0", - "lodash": "4.17.5", - "mkdirp": "0.5.1", - "source-map-support": "0.4.18" - } - }, - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "2.5.5", - "regenerator-runtime": "0.11.1" - } - }, - "babel-template": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", - "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0", - "babylon": "6.18.0", - "lodash": "4.17.5" - } - }, - "babel-traverse": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", - "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", - "dev": true, - "requires": { - "babel-code-frame": "6.26.0", - "babel-messages": "6.23.0", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "babylon": "6.18.0", - "debug": "2.6.9", - "globals": "9.18.0", - "invariant": "2.2.4", - "lodash": "4.17.5" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } - } - }, - "babel-types": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", - "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "esutils": "2.0.2", - "lodash": "4.17.5", - "to-fast-properties": "1.0.3" - } - }, - "babylon": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", - "dev": true - }, - "babylon-walk": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/babylon-walk/-/babylon-walk-1.0.2.tgz", - "integrity": "sha1-OxWl3btIKni0zpwByLoYFwLZ1s4=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "lodash.clone": "4.5.0" - } - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, - "requires": { - "cache-base": "1.0.1", - "class-utils": "0.3.6", - "component-emitter": "1.2.1", - "define-property": "1.0.0", - "isobject": "3.0.1", - "mixin-deep": "1.3.1", - "pascalcase": "0.1.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "1.0.2" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "6.0.2" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "6.0.2" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "1.0.0", - "is-data-descriptor": "1.0.0", - "kind-of": "6.0.2" - } - } - } - }, - "base64-js": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.3.tgz", - "integrity": "sha512-MsAhsUW1GxCdgYSO6tAfZrNapmUKk7mWx/k5mFY/A1gBtkaCaNapTg+FExCw1r9yeaZhqx/xPg43xgTFH6KL5w==", - "dev": true - }, - "big.js": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", - "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==", - "dev": true - }, - "binary-extensions": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.11.0.tgz", - "integrity": "sha1-RqoXUftqL5PuXmibsQh9SxTGwgU=", - "dev": true - }, - "bindings": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz", - "integrity": "sha1-FK1hE4EtLTfXLme0ystLtyZQXxE=", - "dev": true - }, - "bluebird": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", - "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==", - "dev": true - }, - "bn.js": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", - "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", - "dev": true - }, - "boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", - "dev": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "1.1.0", - "array-unique": "0.3.2", - "extend-shallow": "2.0.1", - "fill-range": "4.0.0", - "isobject": "3.0.1", - "repeat-element": "1.1.2", - "snapdragon": "0.8.2", - "snapdragon-node": "2.1.1", - "split-string": "3.1.0", - "to-regex": "3.0.2" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "0.1.1" - } - } - } - }, - "brfs": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/brfs/-/brfs-1.6.1.tgz", - "integrity": "sha512-OfZpABRQQf+Xsmju8XE9bDjs+uU4vLREGolP7bDgcpsI17QREyZ4Bl+2KLxxx1kCgA0fAIhKQBaBYh+PEcCqYQ==", - "dev": true, - "requires": { - "quote-stream": "1.0.2", - "resolve": "1.7.1", - "static-module": "2.2.4", - "through2": "2.0.3" - } - }, - "brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", - "dev": true - }, - "browserify-aes": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", - "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", - "dev": true, - "requires": { - "buffer-xor": "1.0.3", - "cipher-base": "1.0.4", - "create-hash": "1.2.0", - "evp_bytestokey": "1.0.3", - "inherits": "2.0.3", - "safe-buffer": "5.1.1" - } - }, - "browserify-cipher": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", - "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", - "dev": true, - "requires": { - "browserify-aes": "1.2.0", - "browserify-des": "1.0.1", - "evp_bytestokey": "1.0.3" - } - }, - "browserify-des": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.1.tgz", - "integrity": "sha512-zy0Cobe3hhgpiOM32Tj7KQ3Vl91m0njwsjzZQK1L+JDf11dzP9qIvjreVinsvXrgfjhStXwUWAEpB9D7Gwmayw==", - "dev": true, - "requires": { - "cipher-base": "1.0.4", - "des.js": "1.0.0", - "inherits": "2.0.3" - } - }, - "browserify-rsa": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", - "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", - "dev": true, - "requires": { - "bn.js": "4.11.8", - "randombytes": "2.0.6" - } - }, - "browserify-sign": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", - "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", - "dev": true, - "requires": { - "bn.js": "4.11.8", - "browserify-rsa": "4.0.1", - "create-hash": "1.2.0", - "create-hmac": "1.1.7", - "elliptic": "6.4.0", - "inherits": "2.0.3", - "parse-asn1": "5.1.1" - } - }, - "browserify-zlib": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", - "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", - "dev": true, - "requires": { - "pako": "1.0.6" - }, - "dependencies": { - "pako": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.6.tgz", - "integrity": "sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg==", - "dev": true - } - } - }, - "browserslist": { - "version": "2.11.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-2.11.3.tgz", - "integrity": "sha512-yWu5cXT7Av6mVwzWc8lMsJMHWn4xyjSuGYi4IozbVTLUOEYPSagUB8kiMDUHA1fS3zjr8nkxkn9jdvug4BBRmA==", - "dev": true, - "requires": { - "caniuse-lite": "1.0.30000828", - "electron-to-chromium": "1.3.42" - } - }, - "buffer": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", - "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", - "dev": true, - "requires": { - "base64-js": "1.2.3", - "ieee754": "1.1.11", - "isarray": "1.0.0" - } - }, - "buffer-equal": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz", - "integrity": "sha1-kbx0sR6kBbyRa8aqkI+q+ltKrEs=", - "dev": true - }, - "buffer-from": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.0.0.tgz", - "integrity": "sha512-83apNb8KK0Se60UE1+4Ukbe3HbfELJ6UlI4ldtOGs7So4KD26orJM8hIY9lxdzP+UpItH1Yh/Y8GUvNFWFFRxA==", - "dev": true - }, - "buffer-xor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", - "dev": true - }, - "builtin-status-codes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", - "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", - "dev": true - }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, - "requires": { - "collection-visit": "1.0.0", - "component-emitter": "1.2.1", - "get-value": "2.0.6", - "has-value": "1.0.0", - "isobject": "3.0.1", - "set-value": "2.0.0", - "to-object-path": "0.3.0", - "union-value": "1.0.0", - "unset-value": "1.0.0" - } - }, - "caniuse-api": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-1.6.1.tgz", - "integrity": "sha1-tTTnxzTE+B7F++isoq0kNUuWLGw=", - "dev": true, - "requires": { - "browserslist": "1.7.7", - "caniuse-db": "1.0.30000828", - "lodash.memoize": "4.1.2", - "lodash.uniq": "4.5.0" - }, - "dependencies": { - "browserslist": { - "version": "1.7.7", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-1.7.7.tgz", - "integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=", - "dev": true, - "requires": { - "caniuse-db": "1.0.30000828", - "electron-to-chromium": "1.3.42" - } - } - } - }, - "caniuse-db": { - "version": "1.0.30000828", - "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000828.tgz", - "integrity": "sha1-7W1vA7WoH7KRw8DgiIKLEacJSL8=", - "dev": true - }, - "caniuse-lite": { - "version": "1.0.30000828", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000828.tgz", - "integrity": "sha512-v+ySC6Ih8N8CyGZYd4svPipuFIqskKsTOi18chFM0qtu1G8mGuSYajb+h49XDWgmzX8MRDOp1Agw6KQaPUdIhg==", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" - } - }, - "chokidar": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.3.tgz", - "integrity": "sha512-zW8iXYZtXMx4kux/nuZVXjkLP+CyIK5Al5FHnj1OgTKGZfp4Oy6/ymtMSKFv3GD8DviEmUPmJg9eFdJ/JzudMg==", - "dev": true, - "requires": { - "anymatch": "2.0.0", - "async-each": "1.0.1", - "braces": "2.3.2", - "fsevents": "1.1.3", - "glob-parent": "3.1.0", - "inherits": "2.0.3", - "is-binary-path": "1.0.1", - "is-glob": "4.0.0", - "normalize-path": "2.1.1", - "path-is-absolute": "1.0.1", - "readdirp": "2.1.0", - "upath": "1.0.4" - } - }, - "cipher-base": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "dev": true, - "requires": { - "inherits": "2.0.3", - "safe-buffer": "5.1.1" - } - }, - "clap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/clap/-/clap-1.2.3.tgz", - "integrity": "sha512-4CoL/A3hf90V3VIEjeuhSvlGFEHKzOz+Wfc2IVZc+FaUgU0ZQafJTP49fvnULipOPcAfqhyI2duwQyns6xqjYA==", - "dev": true, - "requires": { - "chalk": "1.1.3" - } - }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, - "requires": { - "arr-union": "3.1.0", - "define-property": "0.2.5", - "isobject": "3.0.1", - "static-extend": "0.1.2" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "0.1.6" - } - } - } - }, - "clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", - "dev": true - }, - "clones": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/clones/-/clones-1.1.0.tgz", - "integrity": "sha1-h+kEEy1hQMXAtyAGwIwNBb17Y7M=", - "dev": true - }, - "coa": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/coa/-/coa-1.0.4.tgz", - "integrity": "sha1-qe8VNmDWqGqL3sAomlxoTSF0Mv0=", - "dev": true, - "requires": { - "q": "1.5.1" - } - }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "dev": true, - "requires": { - "map-visit": "1.0.0", - "object-visit": "1.0.1" - } - }, - "color": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/color/-/color-0.11.4.tgz", - "integrity": "sha1-bXtcdPtl6EHNSHkq0e1eB7kE12Q=", - "dev": true, - "requires": { - "clone": "1.0.4", - "color-convert": "1.9.1", - "color-string": "0.3.0" - } - }, - "color-convert": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", - "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "color-string": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-0.3.0.tgz", - "integrity": "sha1-J9RvtnAlxcL6JZk7+/V55HhBuZE=", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "colormin": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/colormin/-/colormin-1.1.2.tgz", - "integrity": "sha1-6i90IKcrlogaOKrlnsEkpvcpgTM=", - "dev": true, - "requires": { - "color": "0.11.4", - "css-color-names": "0.0.4", - "has": "1.0.1" - } - }, - "colors": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", - "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", - "dev": true - }, - "command-exists": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.4.tgz", - "integrity": "sha512-9NoZJMIzkBfe26RwXIGr0thU/bZw68pw3dHjPav+m+AIns73SDe2ggMLTGXkzed4R67D8eTUnRZ2fraxAffQqw==", - "dev": true - }, - "commander": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", - "dev": true - }, - "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "requires": { - "buffer-from": "1.0.0", - "inherits": "2.0.3", - "readable-stream": "2.3.6", - "typedarray": "0.0.6" - } - }, - "config-chain": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.11.tgz", - "integrity": "sha1-q6CXR9++TD5w52am5BWG4YWfxvI=", - "dev": true, - "requires": { - "ini": "1.3.5", - "proto-list": "1.2.4" - } - }, - "console-browserify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", - "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", - "dev": true, - "requires": { - "date-now": "0.1.4" - } - }, - "consolidate": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/consolidate/-/consolidate-0.14.5.tgz", - "integrity": "sha1-WiUEe8dvcwcmZ8jLUsmJiI9JTGM=", - "dev": true, - "requires": { - "bluebird": "3.5.1" - } - }, - "constants-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", - "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", - "dev": true - }, - "convert-source-map": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.1.tgz", - "integrity": "sha1-uCeAl7m8IpNl3lxiz1/K7YtVmeU=", - "dev": true - }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true - }, - "core-js": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.5.tgz", - "integrity": "sha1-sU3ek2xkDAV5prUMq8wTLdYSfjs=", - "dev": true - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - }, - "cosmiconfig": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-2.2.2.tgz", - "integrity": "sha512-GiNXLwAFPYHy25XmTPpafYvn3CLAkJ8FLsscq78MQd1Kh0OU6Yzhn4eV2MVF4G9WEQZoWEGltatdR+ntGPMl5A==", - "dev": true, - "requires": { - "is-directory": "0.3.1", - "js-yaml": "3.11.0", - "minimist": "1.2.0", - "object-assign": "4.1.1", - "os-homedir": "1.0.2", - "parse-json": "2.2.0", - "require-from-string": "1.2.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - } - } - }, - "create-ecdh": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.1.tgz", - "integrity": "sha512-iZvCCg8XqHQZ1ioNBTzXS/cQSkqkqcPs8xSX4upNB+DAk9Ht3uzQf2J32uAHNCne8LDmKr29AgZrEs4oIrwLuQ==", - "dev": true, - "requires": { - "bn.js": "4.11.8", - "elliptic": "6.4.0" - } - }, - "create-hash": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", - "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "dev": true, - "requires": { - "cipher-base": "1.0.4", - "inherits": "2.0.3", - "md5.js": "1.3.4", - "ripemd160": "2.0.1", - "sha.js": "2.4.11" - } - }, - "create-hmac": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", - "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", - "dev": true, - "requires": { - "cipher-base": "1.0.4", - "create-hash": "1.2.0", - "inherits": "2.0.3", - "ripemd160": "2.0.1", - "safe-buffer": "5.1.1", - "sha.js": "2.4.11" - } - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "1.0.4", - "path-key": "2.0.1", - "semver": "5.5.0", - "shebang-command": "1.2.0", - "which": "1.3.0" - } - }, - "crypto-browserify": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", - "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", - "dev": true, - "requires": { - "browserify-cipher": "1.0.1", - "browserify-sign": "4.0.4", - "create-ecdh": "4.0.1", - "create-hash": "1.2.0", - "create-hmac": "1.1.7", - "diffie-hellman": "5.0.3", - "inherits": "2.0.3", - "pbkdf2": "3.0.14", - "public-encrypt": "4.0.2", - "randombytes": "2.0.6", - "randomfill": "1.0.4" - } - }, - "css-color-names": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", - "integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=", - "dev": true - }, - "css-select": { - "version": "1.3.0-rc0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.3.0-rc0.tgz", - "integrity": "sha1-b5MZaqrnN2ZuoQNqjLFKj8t6kjE=", - "dev": true, - "requires": { - "boolbase": "1.0.0", - "css-what": "2.1.0", - "domutils": "1.5.1", - "nth-check": "1.0.1" - }, - "dependencies": { - "domutils": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", - "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", - "dev": true, - "requires": { - "dom-serializer": "0.1.0", - "domelementtype": "1.3.0" - } - } - } - }, - "css-select-base-adapter": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.0.tgz", - "integrity": "sha1-AQKz0UYw34bD65+p9UVicBBs+ZA=", - "dev": true - }, - "css-tree": { - "version": "1.0.0-alpha25", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha25.tgz", - "integrity": "sha512-XC6xLW/JqIGirnZuUWHXCHRaAjje2b3OIB0Vj5RIJo6mIi/AdJo30quQl5LxUl0gkXDIrTrFGbMlcZjyFplz1A==", - "dev": true, - "requires": { - "mdn-data": "1.1.1", - "source-map": "0.5.7" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, - "css-url-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/css-url-regex/-/css-url-regex-1.1.0.tgz", - "integrity": "sha1-g4NCMMyfdMRX3lnuvRVD/uuDt+w=", - "dev": true - }, - "css-what": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.0.tgz", - "integrity": "sha1-lGfQMsOM+u+58teVASUwYvh/ob0=", - "dev": true - }, - "cssnano": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-3.10.0.tgz", - "integrity": "sha1-Tzj2zqK5sX+gFJDyPx3GjqZcHDg=", - "dev": true, - "requires": { - "autoprefixer": "6.7.7", - "decamelize": "1.2.0", - "defined": "1.0.0", - "has": "1.0.1", - "object-assign": "4.1.1", - "postcss": "5.2.18", - "postcss-calc": "5.3.1", - "postcss-colormin": "2.2.2", - "postcss-convert-values": "2.6.1", - "postcss-discard-comments": "2.0.4", - "postcss-discard-duplicates": "2.1.0", - "postcss-discard-empty": "2.1.0", - "postcss-discard-overridden": "0.1.1", - "postcss-discard-unused": "2.2.3", - "postcss-filter-plugins": "2.0.2", - "postcss-merge-idents": "2.1.7", - "postcss-merge-longhand": "2.0.2", - "postcss-merge-rules": "2.1.2", - "postcss-minify-font-values": "1.0.5", - "postcss-minify-gradients": "1.0.5", - "postcss-minify-params": "1.2.2", - "postcss-minify-selectors": "2.1.1", - "postcss-normalize-charset": "1.1.1", - "postcss-normalize-url": "3.0.8", - "postcss-ordered-values": "2.2.3", - "postcss-reduce-idents": "2.4.0", - "postcss-reduce-initial": "1.0.1", - "postcss-reduce-transforms": "1.0.4", - "postcss-svgo": "2.1.6", - "postcss-unique-selectors": "2.0.2", - "postcss-value-parser": "3.3.0", - "postcss-zindex": "2.2.0" - }, - "dependencies": { - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "1.1.3", - "js-base64": "2.4.3", - "source-map": "0.5.7", - "supports-color": "3.2.3" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "1.0.0" - } - } - } - }, - "csso": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/csso/-/csso-2.3.2.tgz", - "integrity": "sha1-3dUsWHAz9J6Utx/FVWnyUuj/X4U=", - "dev": true, - "requires": { - "clap": "1.2.3", - "source-map": "0.5.7" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, - "date-now": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", - "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", - "dev": true - }, - "de-indent": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", - "integrity": "sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=", - "dev": true - }, - "deasync": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/deasync/-/deasync-0.1.12.tgz", - "integrity": "sha512-gpacYo8FBZh3INBp2KOtrQp9kCO5faHvOmEZx3/cZTr3Mm8/kAYs7/Ws3E3OAH0ApBNK6Y6N+7+Dka2Zn2Fldw==", - "dev": true, - "requires": { - "bindings": "1.2.1", - "nan": "2.10.0" - } - }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "requires": { - "ms": "2.0.0" - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true - }, - "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "dev": true - }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true - }, - "define-properties": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz", - "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=", - "dev": true, - "requires": { - "foreach": "2.0.5", - "object-keys": "1.0.11" - } - }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "requires": { - "is-descriptor": "1.0.2", - "isobject": "3.0.1" - }, - "dependencies": { - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "6.0.2" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "6.0.2" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "1.0.0", - "is-data-descriptor": "1.0.0", - "kind-of": "6.0.2" - } - } - } - }, - "defined": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", - "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", - "dev": true - }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", - "dev": true - }, - "des.js": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", - "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", - "dev": true, - "requires": { - "inherits": "2.0.3", - "minimalistic-assert": "1.0.1" - } - }, - "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", - "dev": true - }, - "detect-indent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", - "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", - "dev": true, - "requires": { - "repeating": "2.0.1" - } - }, - "diffie-hellman": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", - "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", - "dev": true, - "requires": { - "bn.js": "4.11.8", - "miller-rabin": "4.0.1", - "randombytes": "2.0.6" - } - }, - "dom-serializer": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", - "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", - "dev": true, - "requires": { - "domelementtype": "1.1.3", - "entities": "1.1.1" - }, - "dependencies": { - "domelementtype": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", - "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=", - "dev": true - } - } - }, - "domain-browser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", - "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", - "dev": true - }, - "domelementtype": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz", - "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=", - "dev": true - }, - "domhandler": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.1.tgz", - "integrity": "sha1-iS5HAAqZvlW783dP/qBWHYh5wlk=", - "dev": true, - "requires": { - "domelementtype": "1.3.0" - } - }, - "domutils": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", - "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", - "dev": true, - "requires": { - "dom-serializer": "0.1.0", - "domelementtype": "1.3.0" - } - }, - "dotenv": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-5.0.1.tgz", - "integrity": "sha512-4As8uPrjfwb7VXC+WnLCbXK7y+Ueb2B3zgNCePYfhxS1PYeaO1YTeplffTEcbfLhvFNGLAz90VvJs9yomG7bow==", - "dev": true - }, - "duplexer2": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", - "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", - "dev": true, - "requires": { - "readable-stream": "2.3.6" - } - }, - "editorconfig": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.13.3.tgz", - "integrity": "sha512-WkjsUNVCu+ITKDj73QDvi0trvpdDWdkDyHybDGSXPfekLCqwmpD7CP7iPbvBgosNuLcI96XTDwNa75JyFl7tEQ==", - "dev": true, - "requires": { - "bluebird": "3.5.1", - "commander": "2.15.1", - "lru-cache": "3.2.0", - "semver": "5.5.0", - "sigmund": "1.0.1" - } - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", - "dev": true - }, - "electron-to-chromium": { - "version": "1.3.42", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.42.tgz", - "integrity": "sha1-lcM78B0MxAVVauyJn+Yf1NduoPk=", - "dev": true - }, - "elliptic": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.0.tgz", - "integrity": "sha1-ysmvh2LIWDYYcAPI3+GT5eLq5d8=", - "dev": true, - "requires": { - "bn.js": "4.11.8", - "brorand": "1.1.0", - "hash.js": "1.1.3", - "hmac-drbg": "1.0.1", - "inherits": "2.0.3", - "minimalistic-assert": "1.0.1", - "minimalistic-crypto-utils": "1.0.1" - } - }, - "emojis-list": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", - "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", - "dev": true - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", - "dev": true - }, - "entities": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", - "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=", - "dev": true - }, - "errno": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", - "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", - "dev": true, - "requires": { - "prr": "1.0.1" - } - }, - "error-ex": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", - "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", - "dev": true, - "requires": { - "is-arrayish": "0.2.1" - } - }, - "es-abstract": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.11.0.tgz", - "integrity": "sha512-ZnQrE/lXTTQ39ulXZ+J1DTFazV9qBy61x2bY071B+qGco8Z8q1QddsLdt/EF8Ai9hcWH72dWS0kFqXLxOxqslA==", - "dev": true, - "requires": { - "es-to-primitive": "1.1.1", - "function-bind": "1.1.1", - "has": "1.0.1", - "is-callable": "1.1.3", - "is-regex": "1.0.4" - } - }, - "es-to-primitive": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz", - "integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=", - "dev": true, - "requires": { - "is-callable": "1.1.3", - "is-date-object": "1.0.1", - "is-symbol": "1.0.1" - } - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "escodegen": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.9.1.tgz", - "integrity": "sha512-6hTjO1NAWkHnDk3OqQ4YrCuwwmGHL9S3nPlzBOUG/R44rda3wLNrfvQ5fkSGjyhHFKM7ALPKcKGrwvCLe0lC7Q==", - "dev": true, - "requires": { - "esprima": "3.1.3", - "estraverse": "4.2.0", - "esutils": "2.0.2", - "optionator": "0.8.2", - "source-map": "0.6.1" - }, - "dependencies": { - "esprima": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", - "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", - "dev": true - } - } - }, - "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", - "dev": true - }, - "estraverse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", - "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", - "dev": true - }, - "esutils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", - "dev": true - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", - "dev": true - }, - "events": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", - "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", - "dev": true - }, - "evp_bytestokey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", - "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", - "dev": true, - "requires": { - "md5.js": "1.3.4", - "safe-buffer": "5.1.1" - } - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "requires": { - "debug": "2.6.9", - "define-property": "0.2.5", - "extend-shallow": "2.0.1", - "posix-character-classes": "0.1.1", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "0.1.6" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "0.1.1" - } - } - } - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "requires": { - "assign-symbols": "1.0.0", - "is-extendable": "1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "2.0.4" - } - } - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "requires": { - "array-unique": "0.3.2", - "define-property": "1.0.0", - "expand-brackets": "2.1.4", - "extend-shallow": "2.0.1", - "fragment-cache": "0.2.1", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "1.0.2" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "0.1.1" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "6.0.2" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "6.0.2" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "1.0.0", - "is-data-descriptor": "1.0.0", - "kind-of": "6.0.2" - } - } - } - }, - "falafel": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/falafel/-/falafel-2.1.0.tgz", - "integrity": "sha1-lrsXdh2rqU9G0AFzizzt86Z/4Gw=", - "dev": true, - "requires": { - "acorn": "5.5.3", - "foreach": "2.0.5", - "isarray": "0.0.1", - "object-keys": "1.0.11" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - } - } - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "filesize": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/filesize/-/filesize-3.6.1.tgz", - "integrity": "sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg==", - "dev": true - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "2.0.1", - "is-number": "3.0.0", - "repeat-string": "1.6.1", - "to-regex-range": "2.1.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "0.1.1" - } - } - } - }, - "flatten": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.2.tgz", - "integrity": "sha1-2uRqnXj74lKSJYzB54CkHZXAN4I=", - "dev": true - }, - "follow-redirects": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.4.1.tgz", - "integrity": "sha512-uxYePVPogtya1ktGnAAXOacnbIuRMB4dkvqeNz2qTtTQsuzSfbDolV+wMMKxAmCx0bLgAKLbBOkjItMbbkR1vg==", - "requires": { - "debug": "3.1.0" - } - }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true - }, - "foreach": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", - "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", - "dev": true - }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dev": true, - "requires": { - "map-cache": "0.2.2" - } - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", - "dev": true - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "fsevents": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.3.tgz", - "integrity": "sha512-WIr7iDkdmdbxu/Gh6eKEZJL6KPE74/5MEsf2whTOFNxbIoIixogroLdKYqB6FDav4Wavh/lZdzzd3b2KxIXC5Q==", - "dev": true, - "optional": true, - "requires": { - "nan": "2.10.0", - "node-pre-gyp": "0.6.39" - }, - "dependencies": { - "abbrev": { - "version": "1.1.0", - "bundled": true, - "dev": true, - "optional": true - }, - "ajv": { - "version": "4.11.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "co": "4.6.0", - "json-stable-stringify": "1.0.1" - } - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true, - "dev": true - }, - "aproba": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "delegates": "1.0.0", - "readable-stream": "2.2.9" - } - }, - "asn1": { - "version": "0.2.3", - "bundled": true, - "dev": true, - "optional": true - }, - "assert-plus": { - "version": "0.2.0", - "bundled": true, - "dev": true, - "optional": true - }, - "asynckit": { - "version": "0.4.0", - "bundled": true, - "dev": true, - "optional": true - }, - "aws-sign2": { - "version": "0.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "aws4": { - "version": "1.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "balanced-match": { - "version": "0.4.2", - "bundled": true, - "dev": true - }, - "bcrypt-pbkdf": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "tweetnacl": "0.14.5" - } - }, - "block-stream": { - "version": "0.0.9", - "bundled": true, - "dev": true, - "requires": { - "inherits": "2.0.3" - } - }, - "boom": { - "version": "2.10.1", - "bundled": true, - "dev": true, - "requires": { - "hoek": "2.16.3" - } - }, - "brace-expansion": { - "version": "1.1.7", - "bundled": true, - "dev": true, - "requires": { - "balanced-match": "0.4.2", - "concat-map": "0.0.1" - } - }, - "buffer-shims": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "caseless": { - "version": "0.12.0", - "bundled": true, - "dev": true, - "optional": true - }, - "co": { - "version": "4.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true, - "dev": true - }, - "combined-stream": { - "version": "1.0.5", - "bundled": true, - "dev": true, - "requires": { - "delayed-stream": "1.0.0" - } - }, - "concat-map": { - "version": "0.0.1", - "bundled": true, - "dev": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true, - "dev": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "cryptiles": { - "version": "2.0.5", - "bundled": true, - "dev": true, - "requires": { - "boom": "2.10.1" - } - }, - "dashdash": { - "version": "1.14.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "assert-plus": "1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "debug": { - "version": "2.6.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ms": "2.0.0" - } - }, - "deep-extend": { - "version": "0.4.2", - "bundled": true, - "dev": true, - "optional": true - }, - "delayed-stream": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "detect-libc": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "ecc-jsbn": { - "version": "0.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "jsbn": "0.1.1" - } - }, - "extend": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "extsprintf": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "forever-agent": { - "version": "0.6.1", - "bundled": true, - "dev": true, - "optional": true - }, - "form-data": { - "version": "2.1.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "asynckit": "0.4.0", - "combined-stream": "1.0.5", - "mime-types": "2.1.15" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "fstream": { - "version": "1.0.11", - "bundled": true, - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "inherits": "2.0.3", - "mkdirp": "0.5.1", - "rimraf": "2.6.1" - } - }, - "fstream-ignore": { - "version": "1.0.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "fstream": "1.0.11", - "inherits": "2.0.3", - "minimatch": "3.0.4" - } - }, - "gauge": { - "version": "2.7.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "aproba": "1.1.1", - "console-control-strings": "1.1.0", - "has-unicode": "2.0.1", - "object-assign": "4.1.1", - "signal-exit": "3.0.2", - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wide-align": "1.1.2" - } - }, - "getpass": { - "version": "0.1.7", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "assert-plus": "1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "glob": { - "version": "7.1.2", - "bundled": true, - "dev": true, - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "graceful-fs": { - "version": "4.1.11", - "bundled": true, - "dev": true - }, - "har-schema": { - "version": "1.0.5", - "bundled": true, - "dev": true, - "optional": true - }, - "har-validator": { - "version": "4.2.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ajv": "4.11.8", - "har-schema": "1.0.5" - } - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "hawk": { - "version": "3.1.3", - "bundled": true, - "dev": true, - "requires": { - "boom": "2.10.1", - "cryptiles": "2.0.5", - "hoek": "2.16.3", - "sntp": "1.0.9" - } - }, - "hoek": { - "version": "2.16.3", - "bundled": true, - "dev": true - }, - "http-signature": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "assert-plus": "0.2.0", - "jsprim": "1.4.0", - "sshpk": "1.13.0" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "dev": true, - "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" - } - }, - "inherits": { - "version": "2.0.3", - "bundled": true, - "dev": true - }, - "ini": { - "version": "1.3.4", - "bundled": true, - "dev": true, - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "requires": { - "number-is-nan": "1.0.1" - } - }, - "is-typedarray": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "isarray": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "isstream": { - "version": "0.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "jodid25519": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "jsbn": "0.1.1" - } - }, - "jsbn": { - "version": "0.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "json-schema": { - "version": "0.2.3", - "bundled": true, - "dev": true, - "optional": true - }, - "json-stable-stringify": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "jsonify": "0.0.0" - } - }, - "json-stringify-safe": { - "version": "5.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "jsonify": { - "version": "0.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "jsprim": { - "version": "1.4.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.0.2", - "json-schema": "0.2.3", - "verror": "1.3.6" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "mime-db": { - "version": "1.27.0", - "bundled": true, - "dev": true - }, - "mime-types": { - "version": "2.1.15", - "bundled": true, - "dev": true, - "requires": { - "mime-db": "1.27.0" - } - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "dev": true, - "requires": { - "brace-expansion": "1.1.7" - } - }, - "minimist": { - "version": "0.0.8", - "bundled": true, - "dev": true - }, - "mkdirp": { - "version": "0.5.1", - "bundled": true, - "dev": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "node-pre-gyp": { - "version": "0.6.39", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "detect-libc": "1.0.2", - "hawk": "3.1.3", - "mkdirp": "0.5.1", - "nopt": "4.0.1", - "npmlog": "4.1.0", - "rc": "1.2.1", - "request": "2.81.0", - "rimraf": "2.6.1", - "semver": "5.3.0", - "tar": "2.2.1", - "tar-pack": "3.4.0" - } - }, - "nopt": { - "version": "4.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "abbrev": "1.1.0", - "osenv": "0.1.4" - } - }, - "npmlog": { - "version": "4.1.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "are-we-there-yet": "1.1.4", - "console-control-strings": "1.1.0", - "gauge": "2.7.4", - "set-blocking": "2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "oauth-sign": { - "version": "0.8.2", - "bundled": true, - "dev": true, - "optional": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "dev": true, - "requires": { - "wrappy": "1.0.2" - } - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "osenv": { - "version": "0.1.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "os-homedir": "1.0.2", - "os-tmpdir": "1.0.2" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "performance-now": { - "version": "0.2.0", - "bundled": true, - "dev": true, - "optional": true - }, - "process-nextick-args": { - "version": "1.0.7", - "bundled": true, - "dev": true - }, - "punycode": { - "version": "1.4.1", - "bundled": true, - "dev": true, - "optional": true - }, - "qs": { - "version": "6.4.0", - "bundled": true, - "dev": true, - "optional": true - }, - "rc": { - "version": "1.2.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "deep-extend": "0.4.2", - "ini": "1.3.4", - "minimist": "1.2.0", - "strip-json-comments": "2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "readable-stream": { - "version": "2.2.9", - "bundled": true, - "dev": true, - "requires": { - "buffer-shims": "1.0.0", - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "string_decoder": "1.0.1", - "util-deprecate": "1.0.2" - } - }, - "request": { - "version": "2.81.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "aws-sign2": "0.6.0", - "aws4": "1.6.0", - "caseless": "0.12.0", - "combined-stream": "1.0.5", - "extend": "3.0.1", - "forever-agent": "0.6.1", - "form-data": "2.1.4", - "har-validator": "4.2.1", - "hawk": "3.1.3", - "http-signature": "1.1.1", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.15", - "oauth-sign": "0.8.2", - "performance-now": "0.2.0", - "qs": "6.4.0", - "safe-buffer": "5.0.1", - "stringstream": "0.0.5", - "tough-cookie": "2.3.2", - "tunnel-agent": "0.6.0", - "uuid": "3.0.1" - } - }, - "rimraf": { - "version": "2.6.1", - "bundled": true, - "dev": true, - "requires": { - "glob": "7.1.2" - } - }, - "safe-buffer": { - "version": "5.0.1", - "bundled": true, - "dev": true - }, - "semver": { - "version": "5.3.0", - "bundled": true, - "dev": true, - "optional": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "sntp": { - "version": "1.0.9", - "bundled": true, - "dev": true, - "requires": { - "hoek": "2.16.3" - } - }, - "sshpk": { - "version": "1.13.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "asn1": "0.2.3", - "assert-plus": "1.0.0", - "bcrypt-pbkdf": "1.0.1", - "dashdash": "1.14.1", - "ecc-jsbn": "0.1.1", - "getpass": "0.1.7", - "jodid25519": "1.0.2", - "jsbn": "0.1.1", - "tweetnacl": "0.14.5" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" - } - }, - "string_decoder": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "requires": { - "safe-buffer": "5.0.1" - } - }, - "stringstream": { - "version": "0.0.5", - "bundled": true, - "dev": true, - "optional": true - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "requires": { - "ansi-regex": "2.1.1" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "tar": { - "version": "2.2.1", - "bundled": true, - "dev": true, - "requires": { - "block-stream": "0.0.9", - "fstream": "1.0.11", - "inherits": "2.0.3" - } - }, - "tar-pack": { - "version": "3.4.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "debug": "2.6.8", - "fstream": "1.0.11", - "fstream-ignore": "1.0.5", - "once": "1.4.0", - "readable-stream": "2.2.9", - "rimraf": "2.6.1", - "tar": "2.2.1", - "uid-number": "0.0.6" - } - }, - "tough-cookie": { - "version": "2.3.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "punycode": "1.4.1" - } - }, - "tunnel-agent": { - "version": "0.6.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "bundled": true, - "dev": true, - "optional": true - }, - "uid-number": { - "version": "0.0.6", - "bundled": true, - "dev": true, - "optional": true - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "uuid": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "verror": { - "version": "1.3.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "extsprintf": "1.0.2" - } - }, - "wide-align": { - "version": "1.1.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "string-width": "1.0.2" - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true, - "dev": true - } - } - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "get-port": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz", - "integrity": "sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw=", - "dev": true - }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true - }, - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "dev": true, - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "dev": true, - "requires": { - "is-glob": "3.1.0", - "path-dirname": "1.0.2" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "requires": { - "is-extglob": "2.1.1" - } - } - } - }, - "globals": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", - "dev": true - }, - "graceful-fs": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", - "dev": true - }, - "grapheme-breaker": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/grapheme-breaker/-/grapheme-breaker-0.3.2.tgz", - "integrity": "sha1-W55reMODJFLSuiuxy4MPlidkEKw=", - "dev": true, - "requires": { - "brfs": "1.6.1", - "unicode-trie": "0.3.1" - } - }, - "has": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz", - "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=", - "dev": true, - "requires": { - "function-bind": "1.1.1" - } - }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, - "requires": { - "ansi-regex": "2.1.1" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "dev": true, - "requires": { - "get-value": "2.0.6", - "has-values": "1.0.0", - "isobject": "3.0.1" - } - }, - "has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "dev": true, - "requires": { - "is-number": "3.0.0", - "kind-of": "4.0.0" - }, - "dependencies": { - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "hash-base": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", - "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", - "dev": true, - "requires": { - "inherits": "2.0.3", - "safe-buffer": "5.1.1" - } - }, - "hash-sum": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-1.0.2.tgz", - "integrity": "sha1-M7QHd3VMZDJXPBIMw4CLvRDUfwQ=", - "dev": true - }, - "hash.js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz", - "integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==", - "dev": true, - "requires": { - "inherits": "2.0.3", - "minimalistic-assert": "1.0.1" - } - }, - "he": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", - "dev": true - }, - "hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", - "dev": true, - "requires": { - "hash.js": "1.1.3", - "minimalistic-assert": "1.0.1", - "minimalistic-crypto-utils": "1.0.1" - } - }, - "home-or-tmp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", - "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", - "dev": true, - "requires": { - "os-homedir": "1.0.2", - "os-tmpdir": "1.0.2" - } - }, - "html-comment-regex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.1.tgz", - "integrity": "sha1-ZouTd26q5V696POtRkswekljYl4=", - "dev": true - }, - "htmlnano": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/htmlnano/-/htmlnano-0.1.7.tgz", - "integrity": "sha512-t8Gy+r/loFP2VXAJl6ClaNIomGI609oyQcT7O3IoJE6VcDCLR6PYWXaSh+hfd/dnoZ6KPbpgPek/Crm3havqig==", - "dev": true, - "requires": { - "cssnano": "3.10.0", - "object-assign": "4.1.1", - "posthtml": "0.11.3", - "posthtml-render": "1.1.3", - "svgo": "1.0.5", - "uglify-js": "3.3.21" - }, - "dependencies": { - "coa": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.1.tgz", - "integrity": "sha512-5wfTTO8E2/ja4jFSxePXlG5nRu5bBtL/r1HCIpJW/lzT6yDtKl0u0Z4o/Vpz32IpKmBn7HerheEZQgA9N2DarQ==", - "dev": true, - "requires": { - "q": "1.5.1" - } - }, - "csso": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/csso/-/csso-3.5.0.tgz", - "integrity": "sha512-WtJjFP3ZsSdWhiZr4/k1B9uHPgYjFYnDxfbaJxk1hz5PDLIJ5BCRWkJqaztZ0DbP8d2ZIVwUPIJb2YmCwkPaMw==", - "dev": true, - "requires": { - "css-tree": "1.0.0-alpha.27" - }, - "dependencies": { - "css-tree": { - "version": "1.0.0-alpha.27", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.27.tgz", - "integrity": "sha512-BAYp9FyN4jLXjfvRpTDchBllDptqlK9I7OsagXCG9Am5C+5jc8eRZHgqb9x500W2OKS14MMlpQc/nmh/aA7TEQ==", - "dev": true, - "requires": { - "mdn-data": "1.1.1", - "source-map": "0.5.7" - } - } - } - }, - "esprima": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", - "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", - "dev": true - }, - "js-yaml": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz", - "integrity": "sha512-O2v52ffjLa9VeM43J4XocZE//WT9N0IiwDa3KSHH7Tu8CtH+1qM8SIZvnsTh6v+4yFy5KUY3BHUVwjpfAWsjIA==", - "dev": true, - "requires": { - "argparse": "1.0.10", - "esprima": "4.0.0" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "svgo": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.0.5.tgz", - "integrity": "sha512-nYrifviB77aNKDNKKyuay3M9aYiK6Hv5gJVDdjj2ZXTQmI8WZc8+UPLR5IpVlktJfSu3co/4XcWgrgI6seGBPg==", - "dev": true, - "requires": { - "coa": "2.0.1", - "colors": "1.1.2", - "css-select": "1.3.0-rc0", - "css-select-base-adapter": "0.1.0", - "css-tree": "1.0.0-alpha25", - "css-url-regex": "1.1.0", - "csso": "3.5.0", - "js-yaml": "3.10.0", - "mkdirp": "0.5.1", - "object.values": "1.0.4", - "sax": "1.2.4", - "stable": "0.1.6", - "unquote": "1.1.1", - "util.promisify": "1.0.0" - } - }, - "uglify-js": { - "version": "3.3.21", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.3.21.tgz", - "integrity": "sha512-uy82472lH8tshK3jS3c5IFb5MmNKd/5qyBd0ih8sM42L3jWvxnE339U9gZU1zufnLVs98Stib9twq8dLm2XYCA==", - "dev": true, - "requires": { - "commander": "2.15.1", - "source-map": "0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - } - } - }, - "htmlparser2": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.9.2.tgz", - "integrity": "sha1-G9+HrMoPP55T+k/M6w9LTLsAszg=", - "dev": true, - "requires": { - "domelementtype": "1.3.0", - "domhandler": "2.4.1", - "domutils": "1.7.0", - "entities": "1.1.1", - "inherits": "2.0.3", - "readable-stream": "2.3.6" - } - }, - "http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", - "dev": true, - "requires": { - "depd": "1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": "1.4.0" - } - }, - "https-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", - "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", - "dev": true - }, - "ieee754": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.11.tgz", - "integrity": "sha512-VhDzCKN7K8ufStx/CLj5/PDTMgph+qwN5Pkd5i0sGnVwk56zJ0lkT8Qzi1xqWLS0Wp29DgDtNeS7v8/wMoZeHg==", - "dev": true - }, - "indexes-of": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", - "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=", - "dev": true - }, - "indexof": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", - "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, - "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", - "dev": true - }, - "invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dev": true, - "requires": { - "loose-envify": "1.3.1" - } - }, - "is-absolute-url": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz", - "integrity": "sha1-UFMN+4T8yap9vnhS6Do3uTufKqY=", - "dev": true - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", - "dev": true, - "requires": { - "binary-extensions": "1.11.0" - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" - }, - "is-callable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.3.tgz", - "integrity": "sha1-hut1OSgF3cM69xySoO7fdO52BLI=", - "dev": true - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "is-date-object": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", - "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", - "dev": true - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "0.1.6", - "is-data-descriptor": "0.1.4", - "kind-of": "5.1.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "is-directory": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", - "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", - "dev": true - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-finite": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", - "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", - "dev": true, - "requires": { - "number-is-nan": "1.0.1" - } - }, - "is-glob": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", - "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", - "dev": true, - "requires": { - "is-extglob": "2.1.1" - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "is-odd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-odd/-/is-odd-2.0.0.tgz", - "integrity": "sha512-OTiixgpZAT1M4NHgS5IguFp/Vz2VI3U7Goh4/HA1adtwyLtSBrxYlcSYkhpAE07s4fKEcjrFxyvtQBND4vFQyQ==", - "dev": true, - "requires": { - "is-number": "4.0.0" - }, - "dependencies": { - "is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true - } - } - }, - "is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", - "dev": true - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "3.0.1" - } - }, - "is-regex": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", - "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", - "dev": true, - "requires": { - "has": "1.0.1" - } - }, - "is-svg": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-2.1.0.tgz", - "integrity": "sha1-z2EJDaDZ77yrhyLeum8DIgjbsOk=", - "dev": true, - "requires": { - "html-comment-regex": "1.1.1" - } - }, - "is-symbol": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz", - "integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI=", - "dev": true - }, - "is-url": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", - "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", - "dev": true - }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true - }, - "is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", - "dev": true - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "js-base64": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.3.tgz", - "integrity": "sha512-H7ErYLM34CvDMto3GbD6xD0JLUGYXR3QTcH6B/tr4Hi/QpSThnCsIp+Sy5FRTw3B0d6py4HcNkW7nO/wdtGWEw==", - "dev": true - }, - "js-beautify": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.7.5.tgz", - "integrity": "sha512-9OhfAqGOrD7hoQBLJMTA+BKuKmoEtTJXzZ7WDF/9gvjtey1koVLuZqIY6c51aPDjbNdNtIXAkiWKVhziawE9Og==", - "dev": true, - "requires": { - "config-chain": "1.1.11", - "editorconfig": "0.13.3", - "mkdirp": "0.5.1", - "nopt": "3.0.6" - } - }, - "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", - "dev": true - }, - "js-yaml": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.11.0.tgz", - "integrity": "sha512-saJstZWv7oNeOyBh3+Dx1qWzhW0+e6/8eDzo7p5rDFqxntSztloLtuKu+Ejhtq82jsilwOIZYsCz+lIjthg1Hw==", - "dev": true, - "requires": { - "argparse": "1.0.10", - "esprima": "4.0.0" - }, - "dependencies": { - "esprima": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", - "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", - "dev": true - } - } - }, - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", - "dev": true - }, - "json5": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", - "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", - "dev": true - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, - "requires": { - "prelude-ls": "1.1.2", - "type-check": "0.3.2" - } - }, - "loader-utils": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz", - "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", - "dev": true, - "requires": { - "big.js": "3.2.0", - "emojis-list": "2.1.0", - "json5": "0.5.1" - } - }, - "lodash": { - "version": "4.17.5", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz", - "integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==", - "dev": true - }, - "lodash.clone": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-4.5.0.tgz", - "integrity": "sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y=", - "dev": true - }, - "lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", - "dev": true - }, - "lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=", - "dev": true - }, - "loose-envify": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", - "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=", - "dev": true, - "requires": { - "js-tokens": "3.0.2" - } - }, - "lru-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-3.2.0.tgz", - "integrity": "sha1-cXibO39Tmb7IVl3aOKow0qCX7+4=", - "dev": true, - "requires": { - "pseudomap": "1.0.2" - } - }, - "macaddress": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/macaddress/-/macaddress-0.2.8.tgz", - "integrity": "sha1-WQTcU3w57G2+/q6QIycTX6hRHxI=", - "dev": true - }, - "magic-string": { - "version": "0.22.5", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.22.5.tgz", - "integrity": "sha512-oreip9rJZkzvA8Qzk9HFs8fZGF/u7H/gtrE8EN6RjKJ9kh2HlC+yQ2QezifqTZfGyiuAV0dRv5a+y/8gBb1m9w==", - "dev": true, - "requires": { - "vlq": "0.2.3" - } - }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true - }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "dev": true, - "requires": { - "object-visit": "1.0.1" - } - }, - "math-expression-evaluator": { - "version": "1.2.17", - "resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz", - "integrity": "sha1-3oGf282E3M2PrlnGrreWFbnSZqw=", - "dev": true - }, - "md5.js": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz", - "integrity": "sha1-6b296UogpawYsENA/Fdk1bCdkB0=", - "dev": true, - "requires": { - "hash-base": "3.0.4", - "inherits": "2.0.3" - } - }, - "mdn-data": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-1.1.1.tgz", - "integrity": "sha512-2J5JENcb4yD5AzBI4ilTakiq2P9gHSsi4LOygnMu/bkchgTiA63AjsHAhDc+3U36AJHRfcz30Qv6Tb7i/Qsiew==", - "dev": true - }, - "merge-source-map": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.0.4.tgz", - "integrity": "sha1-pd5GU42uhNQRTMXqArR3KmNGcB8=", - "dev": true, - "requires": { - "source-map": "0.5.7" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "4.0.0", - "array-unique": "0.3.2", - "braces": "2.3.2", - "define-property": "2.0.2", - "extend-shallow": "3.0.2", - "extglob": "2.0.4", - "fragment-cache": "0.2.1", - "kind-of": "6.0.2", - "nanomatch": "1.2.9", - "object.pick": "1.3.0", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" - } - }, - "miller-rabin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", - "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", - "dev": true, - "requires": { - "bn.js": "4.11.8", - "brorand": "1.1.0" - } - }, - "mime": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", - "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", - "dev": true - }, - "minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true - }, - "minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", - "dev": true - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "1.1.11" - } - }, - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true - }, - "mixin-deep": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", - "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", - "dev": true, - "requires": { - "for-in": "1.0.2", - "is-extendable": "1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "2.0.4" - } - } - } - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "nan": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", - "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==", - "dev": true - }, - "nanomatch": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.9.tgz", - "integrity": "sha512-n8R9bS8yQ6eSXaV6jHUpKzD8gLsin02w1HSFiegwrs9E098Ylhw5jdyKPaYqvHknHaSCKTPp7C8dGCQ0q9koXA==", - "dev": true, - "requires": { - "arr-diff": "4.0.0", - "array-unique": "0.3.2", - "define-property": "2.0.2", - "extend-shallow": "3.0.2", - "fragment-cache": "0.2.1", - "is-odd": "2.0.0", - "is-windows": "1.0.2", - "kind-of": "6.0.2", - "object.pick": "1.3.0", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" - } - }, - "nice-try": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.4.tgz", - "integrity": "sha512-2NpiFHqC87y/zFke0fC0spBXL3bBsoh/p5H1EFhshxjCR5+0g2d6BiXbUFz9v1sAcxsk2htp2eQnNIci2dIYcA==", - "dev": true - }, - "node-forge": { - "version": "0.7.5", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.5.tgz", - "integrity": "sha512-MmbQJ2MTESTjt3Gi/3yG1wGpIMhUfcIypUCGtTizFR9IiccFwxSpfp0vtIZlkFclEqERemxfnSdZEMR9VqqEFQ==", - "dev": true - }, - "node-libs-browser": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.1.0.tgz", - "integrity": "sha512-5AzFzdoIMb89hBGMZglEegffzgRg+ZFoUmisQ8HI4j1KDdpx13J0taNp2y9xPbur6W61gepGDDotGBVQ7mfUCg==", - "dev": true, - "requires": { - "assert": "1.4.1", - "browserify-zlib": "0.2.0", - "buffer": "4.9.1", - "console-browserify": "1.1.0", - "constants-browserify": "1.0.0", - "crypto-browserify": "3.12.0", - "domain-browser": "1.2.0", - "events": "1.1.1", - "https-browserify": "1.0.0", - "os-browserify": "0.3.0", - "path-browserify": "0.0.0", - "process": "0.11.10", - "punycode": "1.4.1", - "querystring-es3": "0.2.1", - "readable-stream": "2.3.6", - "stream-browserify": "2.0.1", - "stream-http": "2.8.1", - "string_decoder": "1.1.1", - "timers-browserify": "2.0.6", - "tty-browserify": "0.0.0", - "url": "0.11.0", - "util": "0.10.3", - "vm-browserify": "0.0.4" - } - }, - "nopt": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", - "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", - "dev": true, - "requires": { - "abbrev": "1.1.1" - } - }, - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "1.1.0" - } - }, - "normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", - "dev": true - }, - "normalize-url": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz", - "integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=", - "dev": true, - "requires": { - "object-assign": "4.1.1", - "prepend-http": "1.0.4", - "query-string": "4.3.4", - "sort-keys": "1.1.2" - } - }, - "nth-check": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz", - "integrity": "sha1-mSms32KPwsQQmN6rgqxYDPFJquQ=", - "dev": true, - "requires": { - "boolbase": "1.0.0" - } - }, - "num2fraction": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", - "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=", - "dev": true - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true - }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "dev": true, - "requires": { - "copy-descriptor": "0.1.1", - "define-property": "0.2.5", - "kind-of": "3.2.2" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "0.1.6" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "object-inspect": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.4.1.tgz", - "integrity": "sha512-wqdhLpfCUbEsoEwl3FXwGyv8ief1k/1aUdIPCqVnupM6e8l63BEJdiF/0swtn04/8p05tG/T0FrpTlfwvljOdw==", - "dev": true - }, - "object-keys": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz", - "integrity": "sha1-xUYBd4rVYPEULODgG8yotW0TQm0=", - "dev": true - }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dev": true, - "requires": { - "isobject": "3.0.1" - } - }, - "object.getownpropertydescriptors": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", - "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", - "dev": true, - "requires": { - "define-properties": "1.1.2", - "es-abstract": "1.11.0" - } - }, - "object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "dev": true, - "requires": { - "isobject": "3.0.1" - } - }, - "object.values": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.0.4.tgz", - "integrity": "sha1-5STaCbT2b/Bd9FdUbscqyZ8TBpo=", - "dev": true, - "requires": { - "define-properties": "1.1.2", - "es-abstract": "1.11.0", - "function-bind": "1.1.1", - "has": "1.0.1" - } - }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "dev": true, - "requires": { - "ee-first": "1.1.1" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1.0.2" - } - }, - "opn": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/opn/-/opn-5.3.0.tgz", - "integrity": "sha512-bYJHo/LOmoTd+pfiYhfZDnf9zekVJrY+cnS2a5F2x+w5ppvTqObojTP7WiFG+kVZs9Inw+qQ/lw7TroWwhdd2g==", - "dev": true, - "requires": { - "is-wsl": "1.1.0" - } - }, - "optionator": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", - "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", - "dev": true, - "requires": { - "deep-is": "0.1.3", - "fast-levenshtein": "2.0.6", - "levn": "0.3.0", - "prelude-ls": "1.1.2", - "type-check": "0.3.2", - "wordwrap": "1.0.0" - } - }, - "os-browserify": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", - "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", - "dev": true - }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "dev": true - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true - }, - "pako": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", - "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=", - "dev": true - }, - "parcel-bundler": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/parcel-bundler/-/parcel-bundler-1.7.0.tgz", - "integrity": "sha512-U3AeOJWVZXhzTCceuCUfKpKX3hGDOAhYArdJLMrTZGFjKhg+hJboWqy6Tw6ye36WpqLWZrBLeGx7kBpLHJBr3A==", - "dev": true, - "requires": { - "babel-code-frame": "6.26.0", - "babel-core": "6.26.0", - "babel-generator": "6.26.1", - "babel-plugin-transform-es2015-modules-commonjs": "6.26.0", - "babel-plugin-transform-react-jsx": "6.24.1", - "babel-preset-env": "1.6.1", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0", - "babylon": "6.18.0", - "babylon-walk": "1.0.2", - "browserslist": "2.11.3", - "chalk": "2.3.2", - "chokidar": "2.0.3", - "command-exists": "1.2.4", - "commander": "2.15.1", - "cross-spawn": "6.0.5", - "cssnano": "3.10.0", - "deasync": "0.1.12", - "dotenv": "5.0.1", - "filesize": "3.6.1", - "get-port": "3.2.0", - "glob": "7.1.2", - "grapheme-breaker": "0.3.2", - "htmlnano": "0.1.7", - "is-url": "1.2.4", - "js-yaml": "3.11.0", - "json5": "0.5.1", - "micromatch": "3.1.10", - "mkdirp": "0.5.1", - "node-forge": "0.7.5", - "node-libs-browser": "2.1.0", - "opn": "5.3.0", - "physical-cpu-count": "2.0.0", - "postcss": "6.0.21", - "postcss-value-parser": "3.3.0", - "posthtml": "0.11.3", - "posthtml-parser": "0.4.1", - "posthtml-render": "1.1.3", - "resolve": "1.7.1", - "semver": "5.5.0", - "serialize-to-js": "1.2.0", - "serve-static": "1.13.2", - "source-map": "0.6.1", - "strip-ansi": "4.0.0", - "toml": "2.3.3", - "tomlify-j0.4": "3.0.0", - "uglify-es": "3.3.9", - "v8-compile-cache": "1.1.2", - "worker-farm": "1.6.0", - "ws": "4.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "1.9.1" - } - }, - "chalk": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", - "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", - "dev": true, - "requires": { - "ansi-styles": "3.2.1", - "escape-string-regexp": "1.0.5", - "supports-color": "5.3.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "3.0.0" - } - }, - "supports-color": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", - "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", - "dev": true, - "requires": { - "has-flag": "3.0.0" - } - } - } - }, - "parcel-plugin-vue": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/parcel-plugin-vue/-/parcel-plugin-vue-1.5.0.tgz", - "integrity": "sha512-f6f/o1eiOvoeFoYC4puZrzDbygwlja6rl+6ejp4KycHTbdW3HjhqPXeaFBmAEPvp7D1llTeCaT8ck9SLp8F2RA==", - "dev": true, - "requires": { - "debug": "3.1.0", - "vue-hot-reload-api": "2.3.0", - "vueify": "9.4.1", - "vueify-bolt": "1.0.2" - } - }, - "parse-asn1": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz", - "integrity": "sha512-KPx7flKXg775zZpnp9SxJlz00gTd4BmJ2yJufSc44gMCRrRQ7NSzAcSJQfifuOLgW6bEi+ftrALtsgALeB2Adw==", - "dev": true, - "requires": { - "asn1.js": "4.10.1", - "browserify-aes": "1.2.0", - "create-hash": "1.2.0", - "evp_bytestokey": "1.0.3", - "pbkdf2": "3.0.14" - } - }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "dev": true, - "requires": { - "error-ex": "1.3.1" - } - }, - "parseurl": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", - "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=", - "dev": true - }, - "pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true - }, - "path-browserify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz", - "integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=", - "dev": true - }, - "path-dirname": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true - }, - "path-parse": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", - "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", - "dev": true - }, - "pbkdf2": { - "version": "3.0.14", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.14.tgz", - "integrity": "sha512-gjsZW9O34fm0R7PaLHRJmLLVfSoesxztjPjE9o6R+qtVJij90ltg1joIovN9GKrRW3t1PzhDDG3UMEMFfZ+1wA==", - "dev": true, - "requires": { - "create-hash": "1.2.0", - "create-hmac": "1.1.7", - "ripemd160": "2.0.1", - "safe-buffer": "5.1.1", - "sha.js": "2.4.11" - } - }, - "physical-cpu-count": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/physical-cpu-count/-/physical-cpu-count-2.0.0.tgz", - "integrity": "sha1-GN4vl+S/epVRrXURlCtUlverpmA=", - "dev": true - }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true - }, - "postcss": { - "version": "6.0.21", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.21.tgz", - "integrity": "sha512-y/bKfbQz2Nn/QBC08bwvYUxEFOVGfPIUOTsJ2CK5inzlXW9SdYR1x4pEsG9blRAF/PX+wRNdOah+gx/hv4q7dw==", - "dev": true, - "requires": { - "chalk": "2.3.2", - "source-map": "0.6.1", - "supports-color": "5.3.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "1.9.1" - } - }, - "chalk": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", - "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", - "dev": true, - "requires": { - "ansi-styles": "3.2.1", - "escape-string-regexp": "1.0.5", - "supports-color": "5.3.0" - } - }, - "supports-color": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", - "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", - "dev": true, - "requires": { - "has-flag": "3.0.0" - } - } - } - }, - "postcss-calc": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-5.3.1.tgz", - "integrity": "sha1-d7rnypKK2FcW4v2kLyYb98HWW14=", - "dev": true, - "requires": { - "postcss": "5.2.18", - "postcss-message-helpers": "2.0.0", - "reduce-css-calc": "1.3.0" - }, - "dependencies": { - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "1.1.3", - "js-base64": "2.4.3", - "source-map": "0.5.7", - "supports-color": "3.2.3" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "1.0.0" - } - } - } - }, - "postcss-colormin": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-2.2.2.tgz", - "integrity": "sha1-ZjFBfV8OkJo9fsJrJMio0eT5bks=", - "dev": true, - "requires": { - "colormin": "1.1.2", - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0" - }, - "dependencies": { - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "1.1.3", - "js-base64": "2.4.3", - "source-map": "0.5.7", - "supports-color": "3.2.3" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "1.0.0" - } - } - } - }, - "postcss-convert-values": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-2.6.1.tgz", - "integrity": "sha1-u9hZPFwf0uPRwyK7kl3K6Nrk1i0=", - "dev": true, - "requires": { - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0" - }, - "dependencies": { - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "1.1.3", - "js-base64": "2.4.3", - "source-map": "0.5.7", - "supports-color": "3.2.3" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "1.0.0" - } - } - } - }, - "postcss-discard-comments": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-2.0.4.tgz", - "integrity": "sha1-vv6J+v1bPazlzM5Rt2uBUUvgDj0=", - "dev": true, - "requires": { - "postcss": "5.2.18" - }, - "dependencies": { - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "1.1.3", - "js-base64": "2.4.3", - "source-map": "0.5.7", - "supports-color": "3.2.3" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "1.0.0" - } - } - } - }, - "postcss-discard-duplicates": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-2.1.0.tgz", - "integrity": "sha1-uavye4isGIFYpesSq8riAmO5GTI=", - "dev": true, - "requires": { - "postcss": "5.2.18" - }, - "dependencies": { - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "1.1.3", - "js-base64": "2.4.3", - "source-map": "0.5.7", - "supports-color": "3.2.3" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "1.0.0" - } - } - } - }, - "postcss-discard-empty": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-2.1.0.tgz", - "integrity": "sha1-0rS9nVztXr2Nyt52QMfXzX9PkrU=", - "dev": true, - "requires": { - "postcss": "5.2.18" - }, - "dependencies": { - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "1.1.3", - "js-base64": "2.4.3", - "source-map": "0.5.7", - "supports-color": "3.2.3" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "1.0.0" - } - } - } - }, - "postcss-discard-overridden": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-0.1.1.tgz", - "integrity": "sha1-ix6vVU9ob7KIzYdMVWZ7CqNmjVg=", - "dev": true, - "requires": { - "postcss": "5.2.18" - }, - "dependencies": { - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "1.1.3", - "js-base64": "2.4.3", - "source-map": "0.5.7", - "supports-color": "3.2.3" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "1.0.0" - } - } - } - }, - "postcss-discard-unused": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-2.2.3.tgz", - "integrity": "sha1-vOMLLMWR/8Y0Mitfs0ZLbZNPRDM=", - "dev": true, - "requires": { - "postcss": "5.2.18", - "uniqs": "2.0.0" - }, - "dependencies": { - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "1.1.3", - "js-base64": "2.4.3", - "source-map": "0.5.7", - "supports-color": "3.2.3" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "1.0.0" - } - } - } - }, - "postcss-filter-plugins": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/postcss-filter-plugins/-/postcss-filter-plugins-2.0.2.tgz", - "integrity": "sha1-bYWGJTTXNaxCDkqFgG4fXUKG2Ew=", - "dev": true, - "requires": { - "postcss": "5.2.18", - "uniqid": "4.1.1" - }, - "dependencies": { - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "1.1.3", - "js-base64": "2.4.3", - "source-map": "0.5.7", - "supports-color": "3.2.3" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "1.0.0" - } - } - } - }, - "postcss-load-config": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-1.2.0.tgz", - "integrity": "sha1-U56a/J3chiASHr+djDZz4M5Q0oo=", - "dev": true, - "requires": { - "cosmiconfig": "2.2.2", - "object-assign": "4.1.1", - "postcss-load-options": "1.2.0", - "postcss-load-plugins": "2.3.0" - } - }, - "postcss-load-options": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postcss-load-options/-/postcss-load-options-1.2.0.tgz", - "integrity": "sha1-sJixVZ3awt8EvAuzdfmaXP4rbYw=", - "dev": true, - "requires": { - "cosmiconfig": "2.2.2", - "object-assign": "4.1.1" - } - }, - "postcss-load-plugins": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/postcss-load-plugins/-/postcss-load-plugins-2.3.0.tgz", - "integrity": "sha1-dFdoEWWZrKLwCfrUJrABdQSdjZI=", - "dev": true, - "requires": { - "cosmiconfig": "2.2.2", - "object-assign": "4.1.1" - } - }, - "postcss-merge-idents": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/postcss-merge-idents/-/postcss-merge-idents-2.1.7.tgz", - "integrity": "sha1-TFUwMTwI4dWzu/PSu8dH4njuonA=", - "dev": true, - "requires": { - "has": "1.0.1", - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0" - }, - "dependencies": { - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "1.1.3", - "js-base64": "2.4.3", - "source-map": "0.5.7", - "supports-color": "3.2.3" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "1.0.0" - } - } - } - }, - "postcss-merge-longhand": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-2.0.2.tgz", - "integrity": "sha1-I9kM0Sewp3mUkVMyc5A0oaTz1lg=", - "dev": true, - "requires": { - "postcss": "5.2.18" - }, - "dependencies": { - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "1.1.3", - "js-base64": "2.4.3", - "source-map": "0.5.7", - "supports-color": "3.2.3" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "1.0.0" - } - } - } - }, - "postcss-merge-rules": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-2.1.2.tgz", - "integrity": "sha1-0d9d+qexrMO+VT8OnhDofGG19yE=", - "dev": true, - "requires": { - "browserslist": "1.7.7", - "caniuse-api": "1.6.1", - "postcss": "5.2.18", - "postcss-selector-parser": "2.2.3", - "vendors": "1.0.1" - }, - "dependencies": { - "browserslist": { - "version": "1.7.7", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-1.7.7.tgz", - "integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=", - "dev": true, - "requires": { - "caniuse-db": "1.0.30000828", - "electron-to-chromium": "1.3.42" - } - }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "1.1.3", - "js-base64": "2.4.3", - "source-map": "0.5.7", - "supports-color": "3.2.3" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "1.0.0" - } - } - } - }, - "postcss-message-helpers": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-message-helpers/-/postcss-message-helpers-2.0.0.tgz", - "integrity": "sha1-pPL0+rbk/gAvCu0ABHjN9S+bpg4=", - "dev": true - }, - "postcss-minify-font-values": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-1.0.5.tgz", - "integrity": "sha1-S1jttWZB66fIR0qzUmyv17vey2k=", - "dev": true, - "requires": { - "object-assign": "4.1.1", - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0" - }, - "dependencies": { - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "1.1.3", - "js-base64": "2.4.3", - "source-map": "0.5.7", - "supports-color": "3.2.3" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "1.0.0" - } - } - } - }, - "postcss-minify-gradients": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-1.0.5.tgz", - "integrity": "sha1-Xb2hE3NwP4PPtKPqOIHY11/15uE=", - "dev": true, - "requires": { - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0" - }, - "dependencies": { - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "1.1.3", - "js-base64": "2.4.3", - "source-map": "0.5.7", - "supports-color": "3.2.3" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "1.0.0" - } - } - } - }, - "postcss-minify-params": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-1.2.2.tgz", - "integrity": "sha1-rSzgcTc7lDs9kwo/pZo1jCjW8fM=", - "dev": true, - "requires": { - "alphanum-sort": "1.0.2", - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0", - "uniqs": "2.0.0" - }, - "dependencies": { - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "1.1.3", - "js-base64": "2.4.3", - "source-map": "0.5.7", - "supports-color": "3.2.3" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "1.0.0" - } - } - } - }, - "postcss-minify-selectors": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-2.1.1.tgz", - "integrity": "sha1-ssapjAByz5G5MtGkllCBFDEXNb8=", - "dev": true, - "requires": { - "alphanum-sort": "1.0.2", - "has": "1.0.1", - "postcss": "5.2.18", - "postcss-selector-parser": "2.2.3" - }, - "dependencies": { - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "1.1.3", - "js-base64": "2.4.3", - "source-map": "0.5.7", - "supports-color": "3.2.3" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "1.0.0" - } - } - } - }, - "postcss-normalize-charset": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-1.1.1.tgz", - "integrity": "sha1-757nEhLX/nWceO0WL2HtYrXLk/E=", - "dev": true, - "requires": { - "postcss": "5.2.18" - }, - "dependencies": { - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "1.1.3", - "js-base64": "2.4.3", - "source-map": "0.5.7", - "supports-color": "3.2.3" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "1.0.0" - } - } - } - }, - "postcss-normalize-url": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-3.0.8.tgz", - "integrity": "sha1-EI90s/L82viRov+j6kWSJ5/HgiI=", - "dev": true, - "requires": { - "is-absolute-url": "2.1.0", - "normalize-url": "1.9.1", - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0" - }, - "dependencies": { - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "1.1.3", - "js-base64": "2.4.3", - "source-map": "0.5.7", - "supports-color": "3.2.3" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "1.0.0" - } - } - } - }, - "postcss-ordered-values": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-2.2.3.tgz", - "integrity": "sha1-7sbCpntsQSqNsgQud/6NpD+VwR0=", - "dev": true, - "requires": { - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0" - }, - "dependencies": { - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "1.1.3", - "js-base64": "2.4.3", - "source-map": "0.5.7", - "supports-color": "3.2.3" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "1.0.0" - } - } - } - }, - "postcss-reduce-idents": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-idents/-/postcss-reduce-idents-2.4.0.tgz", - "integrity": "sha1-wsbSDMlYKE9qv75j92Cb9AkFmtM=", - "dev": true, - "requires": { - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0" - }, - "dependencies": { - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "1.1.3", - "js-base64": "2.4.3", - "source-map": "0.5.7", - "supports-color": "3.2.3" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "1.0.0" - } - } - } - }, - "postcss-reduce-initial": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-1.0.1.tgz", - "integrity": "sha1-aPgGlfBF0IJjqHmtJA343WT2ROo=", - "dev": true, - "requires": { - "postcss": "5.2.18" - }, - "dependencies": { - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "1.1.3", - "js-base64": "2.4.3", - "source-map": "0.5.7", - "supports-color": "3.2.3" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "1.0.0" - } - } - } - }, - "postcss-reduce-transforms": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-1.0.4.tgz", - "integrity": "sha1-/3b02CEkN7McKYpC0uFEQCV3GuE=", - "dev": true, - "requires": { - "has": "1.0.1", - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0" - }, - "dependencies": { - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "1.1.3", - "js-base64": "2.4.3", - "source-map": "0.5.7", - "supports-color": "3.2.3" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "1.0.0" - } - } - } - }, - "postcss-selector-parser": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-2.2.3.tgz", - "integrity": "sha1-+UN3iGBsPJrO4W/+jYsWKX8nu5A=", - "dev": true, - "requires": { - "flatten": "1.0.2", - "indexes-of": "1.0.1", - "uniq": "1.0.1" - } - }, - "postcss-svgo": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-2.1.6.tgz", - "integrity": "sha1-tt8YqmE7Zm4TPwittSGcJoSsEI0=", - "dev": true, - "requires": { - "is-svg": "2.1.0", - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0", - "svgo": "0.7.2" - }, - "dependencies": { - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "1.1.3", - "js-base64": "2.4.3", - "source-map": "0.5.7", - "supports-color": "3.2.3" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "1.0.0" - } - } - } - }, - "postcss-unique-selectors": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-2.0.2.tgz", - "integrity": "sha1-mB1X0p3csz57Hf4f1DuGSfkzyh0=", - "dev": true, - "requires": { - "alphanum-sort": "1.0.2", - "postcss": "5.2.18", - "uniqs": "2.0.0" - }, - "dependencies": { - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "1.1.3", - "js-base64": "2.4.3", - "source-map": "0.5.7", - "supports-color": "3.2.3" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "1.0.0" - } - } - } - }, - "postcss-value-parser": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz", - "integrity": "sha1-h/OPnxj3dKSrTIojL1xc6IcqnRU=", - "dev": true - }, - "postcss-zindex": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/postcss-zindex/-/postcss-zindex-2.2.0.tgz", - "integrity": "sha1-0hCd3AVbka9n/EyzsCWUZjnSryI=", - "dev": true, - "requires": { - "has": "1.0.1", - "postcss": "5.2.18", - "uniqs": "2.0.0" - }, - "dependencies": { - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "1.1.3", - "js-base64": "2.4.3", - "source-map": "0.5.7", - "supports-color": "3.2.3" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "1.0.0" - } - } - } - }, - "posthtml": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/posthtml/-/posthtml-0.11.3.tgz", - "integrity": "sha512-quMHnDckt2DQ9lRi6bYLnuyBDnVzK+McHa8+ar4kTdYbWEo/92hREOu3h70ZirudOOp/my2b3r0m5YtxY52yrA==", - "dev": true, - "requires": { - "object-assign": "4.1.1", - "posthtml-parser": "0.3.3", - "posthtml-render": "1.1.3" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - }, - "posthtml-parser": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/posthtml-parser/-/posthtml-parser-0.3.3.tgz", - "integrity": "sha512-H/Z/yXGwl49A7hYQLV1iQ3h87NE0aZ/PMZhFwhw3lKeCAN+Ti4idrHvVvh4/GX10I7u77aQw+QB4vV5/Lzvv5A==", - "dev": true, - "requires": { - "htmlparser2": "3.9.2", - "isobject": "2.1.0", - "object-assign": "4.1.1" - } - } - } - }, - "posthtml-parser": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/posthtml-parser/-/posthtml-parser-0.4.1.tgz", - "integrity": "sha512-h7vXIQ21Ikz2w5wPClPakNP6mJeJCK6BT0GpqnQrNNABdR7/TchNlFyryL1Bz6Ww53YWCKkr6tdZuHlxY1AVdQ==", - "dev": true, - "requires": { - "htmlparser2": "3.9.2", - "object-assign": "4.1.1" - } - }, - "posthtml-render": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/posthtml-render/-/posthtml-render-1.1.3.tgz", - "integrity": "sha512-aYvEMUxvKAv7D+ob2qlosyEL5qzsxCvauN/gjkRxr7fsW3+R2CJ1L3YHxx9kAjBJehmSwbNkSKzREdVfz1NWew==", - "dev": true - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true - }, - "prepend-http": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", - "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", - "dev": true - }, - "prettier": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.12.0.tgz", - "integrity": "sha512-Wz0SMncgaglBzDcohH3ZIAi4nVpzOIEweFzCOmgVEoRSeO72b4dcKGfgxoRGVMaFlh1r7dlVaJ+f3CIHfeH6xg==", - "dev": true - }, - "private": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", - "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", - "dev": true - }, - "process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", - "dev": true - }, - "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", - "dev": true - }, - "proto-list": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", - "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=", - "dev": true - }, - "prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", - "dev": true - }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true - }, - "public-encrypt": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.2.tgz", - "integrity": "sha512-4kJ5Esocg8X3h8YgJsKAuoesBgB7mqH3eowiDzMUPKiRDDE7E/BqqZD1hnTByIaAFiwAw246YEltSq7tdrOH0Q==", - "dev": true, - "requires": { - "bn.js": "4.11.8", - "browserify-rsa": "4.0.1", - "create-hash": "1.2.0", - "parse-asn1": "5.1.1", - "randombytes": "2.0.6" - } - }, - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true - }, - "q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", - "dev": true - }, - "query-string": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", - "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=", - "dev": true, - "requires": { - "object-assign": "4.1.1", - "strict-uri-encode": "1.1.0" - } - }, - "querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", - "dev": true - }, - "querystring-es3": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", - "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", - "dev": true - }, - "quote-stream": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/quote-stream/-/quote-stream-1.0.2.tgz", - "integrity": "sha1-hJY/jJwmuULhU/7rU6rnRlK34LI=", - "dev": true, - "requires": { - "buffer-equal": "0.0.1", - "minimist": "1.2.0", - "through2": "2.0.3" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - } - } - }, - "randombytes": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.6.tgz", - "integrity": "sha512-CIQ5OFxf4Jou6uOKe9t1AOgqpeU5fd70A8NPdHSGeYXqXsPe6peOwI0cUl88RWZ6sP1vPMV3avd/R6cZ5/sP1A==", - "dev": true, - "requires": { - "safe-buffer": "5.1.1" - } - }, - "randomfill": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", - "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", - "dev": true, - "requires": { - "randombytes": "2.0.6", - "safe-buffer": "5.1.1" - } - }, - "range-parser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", - "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=", - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "2.0.0", - "safe-buffer": "5.1.1", - "string_decoder": "1.1.1", - "util-deprecate": "1.0.2" - } - }, - "readdirp": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", - "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "minimatch": "3.0.4", - "readable-stream": "2.3.6", - "set-immediate-shim": "1.0.1" - } - }, - "reduce-css-calc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz", - "integrity": "sha1-dHyRTgSWFKTJz7umKYca0dKSdxY=", - "dev": true, - "requires": { - "balanced-match": "0.4.2", - "math-expression-evaluator": "1.2.17", - "reduce-function-call": "1.0.2" - }, - "dependencies": { - "balanced-match": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", - "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", - "dev": true - } - } - }, - "reduce-function-call": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/reduce-function-call/-/reduce-function-call-1.0.2.tgz", - "integrity": "sha1-WiAL+S4ON3UXUv5FsKszD9S2vpk=", - "dev": true, - "requires": { - "balanced-match": "0.4.2" - }, - "dependencies": { - "balanced-match": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", - "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", - "dev": true - } - } - }, - "regenerate": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.3.3.tgz", - "integrity": "sha512-jVpo1GadrDAK59t/0jRx5VxYWQEDkkEKi6+HjE3joFVLfDOh9Xrdh0dF1eSq+BI/SwvTQ44gSscJ8N5zYL61sg==", - "dev": true - }, - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "dev": true - }, - "regenerator-transform": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.10.1.tgz", - "integrity": "sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q==", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "private": "0.1.8" - } - }, - "regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, - "requires": { - "extend-shallow": "3.0.2", - "safe-regex": "1.1.0" - } - }, - "regexpu-core": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-2.0.0.tgz", - "integrity": "sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA=", - "dev": true, - "requires": { - "regenerate": "1.3.3", - "regjsgen": "0.2.0", - "regjsparser": "0.1.5" - } - }, - "regjsgen": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", - "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=", - "dev": true - }, - "regjsparser": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", - "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", - "dev": true, - "requires": { - "jsesc": "0.5.0" - } - }, - "remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", - "dev": true - }, - "repeat-element": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", - "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=", - "dev": true - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true - }, - "repeating": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", - "dev": true, - "requires": { - "is-finite": "1.0.2" - } - }, - "require-from-string": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-1.2.1.tgz", - "integrity": "sha1-UpyczvJzgK3+yaL5ZbZJu+5jZBg=", - "dev": true - }, - "resolve": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.7.1.tgz", - "integrity": "sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw==", - "dev": true, - "requires": { - "path-parse": "1.0.5" - } - }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "dev": true - }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true - }, - "ripemd160": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.1.tgz", - "integrity": "sha1-D0WEKVxTo2KK9+bXmsohzlfRxuc=", - "dev": true, - "requires": { - "hash-base": "2.0.2", - "inherits": "2.0.3" - }, - "dependencies": { - "hash-base": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-2.0.2.tgz", - "integrity": "sha1-ZuodhW206KVHDK32/OI65SRO8uE=", - "dev": true, - "requires": { - "inherits": "2.0.3" - } - } - } - }, - "safe-buffer": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", - "dev": true - }, - "safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "dev": true, - "requires": { - "ret": "0.1.15" - } - }, - "safer-eval": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/safer-eval/-/safer-eval-1.2.3.tgz", - "integrity": "sha512-nDwXOhiheoaBT6op02n8wzsshjLXHhh4YAeqsDEoVmy1k2+lGv/ENLsGaWqkaKArUkUx48VO12/ZPa3sI/OEqQ==", - "dev": true, - "requires": { - "clones": "1.1.0" - } - }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "dev": true - }, - "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", - "dev": true - }, - "send": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", - "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", - "dev": true, - "requires": { - "debug": "2.6.9", - "depd": "1.1.2", - "destroy": "1.0.4", - "encodeurl": "1.0.2", - "escape-html": "1.0.3", - "etag": "1.8.1", - "fresh": "0.5.2", - "http-errors": "1.6.3", - "mime": "1.4.1", - "ms": "2.0.0", - "on-finished": "2.3.0", - "range-parser": "1.2.0", - "statuses": "1.4.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } - } - }, - "serialize-to-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/serialize-to-js/-/serialize-to-js-1.2.0.tgz", - "integrity": "sha512-BjvRI+63PY/I3OVBe4rCI0ENW+odR2V0PIyPndz22PbesNZNc2axReOPA4NrO0C7gEljUUvg0E/yZOJ1Ifs+5g==", - "dev": true, - "requires": { - "js-beautify": "1.7.5", - "safer-eval": "1.2.3" - } - }, - "serve-static": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", - "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", - "dev": true, - "requires": { - "encodeurl": "1.0.2", - "escape-html": "1.0.3", - "parseurl": "1.3.2", - "send": "0.16.2" - } - }, - "set-immediate-shim": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", - "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", - "dev": true - }, - "set-value": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", - "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", - "dev": true, - "requires": { - "extend-shallow": "2.0.1", - "is-extendable": "0.1.1", - "is-plain-object": "2.0.4", - "split-string": "3.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "0.1.1" - } - } - } - }, - "setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", - "dev": true - }, - "setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", - "dev": true - }, - "sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dev": true, - "requires": { - "inherits": "2.0.3", - "safe-buffer": "5.1.1" - } - }, - "shallow-copy": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/shallow-copy/-/shallow-copy-0.0.1.tgz", - "integrity": "sha1-QV9CcC1z2BAzApLMXuhurhoRoXA=", - "dev": true - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, - "requires": { - "shebang-regex": "1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true - }, - "sigmund": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", - "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", - "dev": true - }, - "slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", - "dev": true - }, - "snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, - "requires": { - "base": "0.11.2", - "debug": "2.6.9", - "define-property": "0.2.5", - "extend-shallow": "2.0.1", - "map-cache": "0.2.2", - "source-map": "0.5.7", - "source-map-resolve": "0.5.1", - "use": "3.1.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "0.1.6" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "0.1.1" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, - "snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, - "requires": { - "define-property": "1.0.0", - "isobject": "3.0.1", - "snapdragon-util": "3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "1.0.2" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "6.0.2" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "6.0.2" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "1.0.0", - "is-data-descriptor": "1.0.0", - "kind-of": "6.0.2" - } - } - } - }, - "snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, - "requires": { - "kind-of": "3.2.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "sort-keys": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", - "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", - "dev": true, - "requires": { - "is-plain-obj": "1.1.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-resolve": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.1.tgz", - "integrity": "sha512-0KW2wvzfxm8NCTb30z0LMNyPqWCdDGE2viwzUaucqJdkTRXtZiSY3I+2A6nVAjmdOy0I4gU8DwnVVGsk9jvP2A==", - "dev": true, - "requires": { - "atob": "2.1.0", - "decode-uri-component": "0.2.0", - "resolve-url": "0.2.1", - "source-map-url": "0.4.0", - "urix": "0.1.0" - } - }, - "source-map-support": { - "version": "0.4.18", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", - "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", - "dev": true, - "requires": { - "source-map": "0.5.7" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, - "source-map-url": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", - "dev": true - }, - "split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, - "requires": { - "extend-shallow": "3.0.2" - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "stable": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.6.tgz", - "integrity": "sha1-kQ9dKu17Ugxud3SZwfMuE5/eyxA=", - "dev": true - }, - "static-eval": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.0.0.tgz", - "integrity": "sha512-6flshd3F1Gwm+Ksxq463LtFd1liC77N/PX1FVVc3OzL3hAmo2fwHFbuArkcfi7s9rTNsLEhcRmXGFZhlgy40uw==", - "dev": true, - "requires": { - "escodegen": "1.9.1" - } - }, - "static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "dev": true, - "requires": { - "define-property": "0.2.5", - "object-copy": "0.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "0.1.6" - } - } - } - }, - "static-module": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/static-module/-/static-module-2.2.4.tgz", - "integrity": "sha512-qlzhn8tYcfLsXK2RTWtkx1v/cqiPtS9eFy+UmQ9UnpEDYcwtgbceOybnKp5JncsOnLI/pyGeyzI9Bej9tv0xiA==", - "dev": true, - "requires": { - "concat-stream": "1.6.2", - "convert-source-map": "1.5.1", - "duplexer2": "0.1.4", - "escodegen": "1.9.1", - "falafel": "2.1.0", - "has": "1.0.1", - "magic-string": "0.22.5", - "merge-source-map": "1.0.4", - "object-inspect": "1.4.1", - "quote-stream": "1.0.2", - "readable-stream": "2.3.6", - "shallow-copy": "0.0.1", - "static-eval": "2.0.0", - "through2": "2.0.3" - } - }, - "statuses": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", - "dev": true - }, - "stream-browserify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", - "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=", - "dev": true, - "requires": { - "inherits": "2.0.3", - "readable-stream": "2.3.6" - } - }, - "stream-http": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.1.tgz", - "integrity": "sha512-cQ0jo17BLca2r0GfRdZKYAGLU6JRoIWxqSOakUMuKOT6MOK7AAlE856L33QuDmAy/eeOrhLee3dZKX0Uadu93A==", - "dev": true, - "requires": { - "builtin-status-codes": "3.0.0", - "inherits": "2.0.3", - "readable-stream": "2.3.6", - "to-arraybuffer": "1.0.1", - "xtend": "4.0.1" - } - }, - "strict-uri-encode": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", - "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "5.1.1" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "2.1.1" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - }, - "svgo": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-0.7.2.tgz", - "integrity": "sha1-n1dyQTlSE1xv779Ar+ak+qiLS7U=", - "dev": true, - "requires": { - "coa": "1.0.4", - "colors": "1.1.2", - "csso": "2.3.2", - "js-yaml": "3.7.0", - "mkdirp": "0.5.1", - "sax": "1.2.4", - "whet.extend": "0.9.9" - }, - "dependencies": { - "js-yaml": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.7.0.tgz", - "integrity": "sha1-XJZ93YN6m/3KXy3oQlOr6KHAO4A=", - "dev": true, - "requires": { - "argparse": "1.0.10", - "esprima": "2.7.3" - } - } - } - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, - "through2": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", - "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", - "dev": true, - "requires": { - "readable-stream": "2.3.6", - "xtend": "4.0.1" - } - }, - "timers-browserify": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.6.tgz", - "integrity": "sha512-HQ3nbYRAowdVd0ckGFvmJPPCOH/CHleFN/Y0YQCX1DVaB7t+KFvisuyN09fuP8Jtp1CpfSh8O8bMkHbdbPe6Pw==", - "dev": true, - "requires": { - "setimmediate": "1.0.5" - } - }, - "tiny-inflate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.2.tgz", - "integrity": "sha1-k9nez/yIBb1X6uQxDwt0Xptvs6c=", - "dev": true - }, - "to-arraybuffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", - "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", - "dev": true - }, - "to-fast-properties": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", - "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", - "dev": true - }, - "to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, - "requires": { - "define-property": "2.0.2", - "extend-shallow": "3.0.2", - "regex-not": "1.0.2", - "safe-regex": "1.1.0" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "requires": { - "is-number": "3.0.0", - "repeat-string": "1.6.1" - } - }, - "toml": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/toml/-/toml-2.3.3.tgz", - "integrity": "sha512-O7L5hhSQHxuufWUdcTRPfuTh3phKfAZ/dqfxZFoxPCj2RYmpaSGLEIs016FCXItQwNr08yefUB5TSjzRYnajTA==", - "dev": true - }, - "tomlify-j0.4": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/tomlify-j0.4/-/tomlify-j0.4-3.0.0.tgz", - "integrity": "sha512-2Ulkc8T7mXJ2l0W476YC/A209PR38Nw8PuaCNtk9uI3t1zzFdGQeWYGQvmj2PZkVvRC/Yoi4xQKMRnWc/N29tQ==", - "dev": true - }, - "trim-right": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", - "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", - "dev": true - }, - "tty-browserify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", - "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", - "dev": true - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, - "requires": { - "prelude-ls": "1.1.2" - } - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true - }, - "uglify-es": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.3.9.tgz", - "integrity": "sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ==", - "dev": true, - "requires": { - "commander": "2.13.0", - "source-map": "0.6.1" - }, - "dependencies": { - "commander": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz", - "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==", - "dev": true - } - } - }, - "unicode-trie": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-0.3.1.tgz", - "integrity": "sha1-1nHd3YkQGgi6w3tqUWEBBgIFIIU=", - "dev": true, - "requires": { - "pako": "0.2.9", - "tiny-inflate": "1.0.2" - } - }, - "union-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", - "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", - "dev": true, - "requires": { - "arr-union": "3.1.0", - "get-value": "2.0.6", - "is-extendable": "0.1.1", - "set-value": "0.4.3" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "0.1.1" - } - }, - "set-value": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", - "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", - "dev": true, - "requires": { - "extend-shallow": "2.0.1", - "is-extendable": "0.1.1", - "is-plain-object": "2.0.4", - "to-object-path": "0.3.0" - } - } - } - }, - "uniq": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", - "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=", - "dev": true - }, - "uniqid": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/uniqid/-/uniqid-4.1.1.tgz", - "integrity": "sha1-iSIN32t1GuUrX3JISGNShZa7hME=", - "dev": true, - "requires": { - "macaddress": "0.2.8" - } - }, - "uniqs": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz", - "integrity": "sha1-/+3ks2slKQaW5uFl1KWe25mOawI=", - "dev": true - }, - "unquote": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", - "integrity": "sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ=", - "dev": true - }, - "unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dev": true, - "requires": { - "has-value": "0.3.1", - "isobject": "3.0.1" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dev": true, - "requires": { - "get-value": "2.0.6", - "has-values": "0.1.4", - "isobject": "2.1.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true - } - } - }, - "upath": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.0.4.tgz", - "integrity": "sha512-d4SJySNBXDaQp+DPrziv3xGS6w3d2Xt69FijJr86zMPBy23JEloMCEOUBBzuN7xCtjLCnmB9tI/z7SBCahHBOw==", - "dev": true - }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "dev": true - }, - "url": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", - "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", - "dev": true, - "requires": { - "punycode": "1.3.2", - "querystring": "0.2.0" - }, - "dependencies": { - "punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", - "dev": true - } - } - }, - "use": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.0.tgz", - "integrity": "sha512-6UJEQM/L+mzC3ZJNM56Q4DFGLX/evKGRg15UJHGB9X5j5Z3AFbgZvjUh2yq/UJUY4U5dh7Fal++XbNg1uzpRAw==", - "dev": true, - "requires": { - "kind-of": "6.0.2" - } - }, - "util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", - "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", - "dev": true, - "requires": { - "inherits": "2.0.1" - }, - "dependencies": { - "inherits": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", - "dev": true - } - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, - "util.promisify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", - "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", - "dev": true, - "requires": { - "define-properties": "1.1.2", - "object.getownpropertydescriptors": "2.0.3" - } - }, - "v8-compile-cache": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-1.1.2.tgz", - "integrity": "sha512-ejdrifsIydN1XDH7EuR2hn8ZrkRKUYF7tUcBjBy/lhrCvs2K+zRlbW9UHc0IQ9RsYFZJFqJrieoIHfkCa0DBRA==", - "dev": true - }, - "vendors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.1.tgz", - "integrity": "sha1-N61zyO5Bf7PVgOeFMSMH0nSEfyI=", - "dev": true - }, - "vlq": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/vlq/-/vlq-0.2.3.tgz", - "integrity": "sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow==", - "dev": true - }, - "vm-browserify": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", - "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=", - "dev": true, - "requires": { - "indexof": "0.0.1" - } - }, - "vue": { - "version": "2.5.16", - "resolved": "https://registry.npmjs.org/vue/-/vue-2.5.16.tgz", - "integrity": "sha512-/ffmsiVuPC8PsWcFkZngdpas19ABm5mh2wA7iDqcltyCTwlgZjHGeJYOXkBMo422iPwIcviOtrTCUpSfXmToLQ==" - }, - "vue-hot-reload-api": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/vue-hot-reload-api/-/vue-hot-reload-api-2.3.0.tgz", - "integrity": "sha512-2j/t+wIbyVMP5NvctQoSUvLkYKoWAAk2QlQiilrM2a6/ulzFgdcLUJfTvs4XQ/3eZhHiBmmEojbjmM4AzZj8JA==", - "dev": true - }, - "vue-loader": { - "version": "13.7.1", - "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-13.7.1.tgz", - "integrity": "sha512-v6PbKMGl/hWHGPxB2uGHsA66vusrXF66J/h1QiFXtU6z5zVSK8jq5xl95M1p3QNXmuEJKNP3nxoXfbgQNs7hJg==", - "dev": true, - "requires": { - "consolidate": "0.14.5", - "hash-sum": "1.0.2", - "loader-utils": "1.1.0", - "lru-cache": "4.1.2", - "postcss": "6.0.21", - "postcss-load-config": "1.2.0", - "postcss-selector-parser": "2.2.3", - "prettier": "1.12.0", - "resolve": "1.7.1", - "source-map": "0.6.1", - "vue-hot-reload-api": "2.3.0", - "vue-style-loader": "3.1.2", - "vue-template-es2015-compiler": "1.6.0" - }, - "dependencies": { - "lru-cache": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.2.tgz", - "integrity": "sha512-wgeVXhrDwAWnIF/yZARsFnMBtdFXOg1b8RIrhilp+0iDYN4mdQcNZElDZ0e4B64BhaxeQ5zN7PMyvu7we1kPeQ==", - "dev": true, - "requires": { - "pseudomap": "1.0.2", - "yallist": "2.1.2" - } - } - } - }, - "vue-router": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.0.1.tgz", - "integrity": "sha512-vLLoY452L+JBpALMP5UHum9+7nzR9PeIBCghU9ZtJ1eWm6ieUI8Zb/DI3MYxH32bxkjzYV1LRjNv4qr8d+uX/w==" - }, - "vue-style-loader": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-3.1.2.tgz", - "integrity": "sha512-ICtVdK/p+qXWpdSs2alWtsXt9YnDoYjQe0w5616j9+/EhjoxZkbun34uWgsMFnC1MhrMMwaWiImz3K2jK1Yp2Q==", - "dev": true, - "requires": { - "hash-sum": "1.0.2", - "loader-utils": "1.1.0" - } - }, - "vue-template-compiler": { - "version": "2.5.16", - "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.5.16.tgz", - "integrity": "sha512-ZbuhCcF/hTYmldoUOVcu2fcbeSAZnfzwDskGduOrnjBiIWHgELAd+R8nAtX80aZkceWDKGQ6N9/0/EUpt+l22A==", - "dev": true, - "requires": { - "de-indent": "1.0.2", - "he": "1.1.1" - } - }, - "vue-template-es2015-compiler": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.6.0.tgz", - "integrity": "sha512-x3LV3wdmmERhVCYy3quqA57NJW7F3i6faas++pJQWtknWT+n7k30F4TVdHvCLn48peTJFRvCpxs3UuFPqgeELg==", - "dev": true - }, - "vueify": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/vueify/-/vueify-9.4.1.tgz", - "integrity": "sha1-0pqXdaM8S4qGAeGGqF2iq4AMoNY=", - "dev": true, - "requires": { - "chalk": "1.1.3", - "convert-source-map": "1.5.1", - "cssnano": "3.10.0", - "hash-sum": "1.0.2", - "json5": "0.5.1", - "lru-cache": "4.1.2", - "object-assign": "4.1.1", - "postcss": "5.2.18", - "postcss-selector-parser": "2.2.3", - "source-map": "0.5.7", - "through": "2.3.8", - "vue-hot-reload-api": "2.3.0", - "vue-template-compiler": "2.5.16", - "vue-template-es2015-compiler": "1.6.0" - }, - "dependencies": { - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "lru-cache": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.2.tgz", - "integrity": "sha512-wgeVXhrDwAWnIF/yZARsFnMBtdFXOg1b8RIrhilp+0iDYN4mdQcNZElDZ0e4B64BhaxeQ5zN7PMyvu7we1kPeQ==", - "dev": true, - "requires": { - "pseudomap": "1.0.2", - "yallist": "2.1.2" - } - }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "1.1.3", - "js-base64": "2.4.3", - "source-map": "0.5.7", - "supports-color": "3.2.3" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "1.0.0" - } - } - } - }, - "vueify-bolt": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/vueify-bolt/-/vueify-bolt-1.0.2.tgz", - "integrity": "sha1-t40ZZkG5q/aRRQ9J7xSQzyBvwhQ=", - "dev": true, - "requires": { - "chalk": "1.1.3", - "convert-source-map": "1.5.1", - "cssnano": "3.10.0", - "hash-sum": "1.0.2", - "json5": "0.5.1", - "lru-cache": "4.1.2", - "object-assign": "4.1.1", - "postcss": "5.2.18", - "postcss-selector-parser": "2.2.3", - "source-map": "0.5.7", - "through": "2.3.8", - "vue-hot-reload-api": "2.3.0", - "vue-loader": "13.7.1", - "vue-template-es2015-compiler": "1.6.0" - }, - "dependencies": { - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "lru-cache": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.2.tgz", - "integrity": "sha512-wgeVXhrDwAWnIF/yZARsFnMBtdFXOg1b8RIrhilp+0iDYN4mdQcNZElDZ0e4B64BhaxeQ5zN7PMyvu7we1kPeQ==", - "dev": true, - "requires": { - "pseudomap": "1.0.2", - "yallist": "2.1.2" - } - }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "1.1.3", - "js-base64": "2.4.3", - "source-map": "0.5.7", - "supports-color": "3.2.3" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "1.0.0" - } - } - } - }, - "whet.extend": { - "version": "0.9.9", - "resolved": "https://registry.npmjs.org/whet.extend/-/whet.extend-0.9.9.tgz", - "integrity": "sha1-+HfVv2SMl+WqVC+twW1qJZucEaE=", - "dev": true - }, - "which": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", - "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", - "dev": true, - "requires": { - "isexe": "2.0.0" - } - }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", - "dev": true - }, - "worker-farm": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.6.0.tgz", - "integrity": "sha512-6w+3tHbM87WnSWnENBUvA2pxJPLhQUg5LKwUQHq3r+XPhIM+Gh2R5ycbwPCyuGbNg+lPgdcnQUhuC02kJCvffQ==", - "dev": true, - "requires": { - "errno": "0.1.7" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "ws": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-4.1.0.tgz", - "integrity": "sha512-ZGh/8kF9rrRNffkLFV4AzhvooEclrOH0xaugmqGsIfFgOE/pIz4fMc4Ef+5HSQqTEug2S9JZIWDR47duDSLfaA==", - "dev": true, - "requires": { - "async-limiter": "1.0.0", - "safe-buffer": "5.1.1" - } - }, - "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", - "dev": true - }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true - } - } -} diff --git a/web-cors/frontend/package.json b/web-cors/frontend/package.json index 7ce2f641..6a5b84d5 100644 --- a/web-cors/frontend/package.json +++ b/web-cors/frontend/package.json @@ -1,22 +1,16 @@ { - "name": "actix-web-cors", + "name": "frontend-vue", "version": "0.1.0", "description": "webapp", - "main": "main.js", "scripts": { - "dev": "rm -rf dist/ && NODE_ENV=development parcel index.html", - "build": "NODE_ENV=production parcel build index.html", - "test": "echo \"Error: no test specified\" && exit 1" + "serve": "vue-cli-service serve", + "build": "vue-cli-service build" }, - "license": "ISC", "dependencies": { - "vue": "^2.5.13", - "vue-router": "^3.0.1", - "axios": "^0.17.1" + "vue": "2.6.10" }, "devDependencies": { - "babel-preset-env": "^1.6.1", - "parcel-bundler": "^1.4.1", - "parcel-plugin-vue": "^1.5.0" + "@vue/cli-service": "^3.0.0", + "vue-template-compiler": "^2.5.21" } } diff --git a/web-cors/frontend/src/app.vue b/web-cors/frontend/src/app.vue index 0c054c20..fa89f3f4 100644 --- a/web-cors/frontend/src/app.vue +++ b/web-cors/frontend/src/app.vue @@ -22,45 +22,50 @@ @@ -95,7 +100,6 @@ input[type="password"] { border-radius: 2px; font-family: 'Roboto', sans-serif; font-weight: bold; - text-transform: uppercase; transition: 0.1s ease; cursor: pointer; } @@ -142,4 +146,4 @@ cursor: pointer; padding-top: 100px; } } - \ No newline at end of file + diff --git a/web-cors/frontend/src/main.js b/web-cors/frontend/src/main.js index df1e4b7c..8b86123b 100644 --- a/web-cors/frontend/src/main.js +++ b/web-cors/frontend/src/main.js @@ -2,10 +2,5 @@ import Vue from 'vue' import App from './app' new Vue({ - el: '#app', render: h => h(App) -}) - -if (module.hot) { - module.hot.accept(); -} \ No newline at end of file +}).$mount('#app') diff --git a/websocket-chat-broker/Cargo.toml b/websocket-chat-broker/Cargo.toml index 3b61d476..7f1ecab8 100644 --- a/websocket-chat-broker/Cargo.toml +++ b/websocket-chat-broker/Cargo.toml @@ -2,17 +2,20 @@ name = "websocket-broker-example" version = "0.1.0" authors = ["Chris Ricketts "] -workspace = "../" +edition = "2018" +workspace = ".." [[bin]] name = "server" path = "src/main.rs" [dependencies] -rand = "*" +rand = "0.6" futures = "0.1.24" -actix = "0.7" -actix-web = "0.7" -actix-broker = "0.1.4" +actix = "0.8.2" +actix-web = "1.0" +actix-files = "0.1" +actix-web-actors = "1.0" +actix-broker = "0.2.0" log = "0.4.5" simple_logger = "0.5.0" diff --git a/websocket-chat-broker/src/main.rs b/websocket-chat-broker/src/main.rs index 24ae4929..4509badf 100644 --- a/websocket-chat-broker/src/main.rs +++ b/websocket-chat-broker/src/main.rs @@ -1,24 +1,18 @@ #[macro_use] -extern crate actix; -extern crate actix_broker; -extern crate actix_web; -extern crate futures; -extern crate rand; -#[macro_use] extern crate log; -extern crate simple_logger; use actix::fut; use actix::prelude::*; use actix_broker::BrokerIssue; -use actix_web::server::HttpServer; -use actix_web::{fs, ws, App, Error, HttpRequest, HttpResponse}; +use actix_files::Files; +use actix_web::{web, App, Error, HttpRequest, HttpResponse, HttpServer}; +use actix_web_actors::ws; mod server; use server::*; -fn chat_route(req: &HttpRequest<()>) -> Result { - ws::start(req, WsChatSession::default()) +fn chat_route(req: HttpRequest, stream: web::Payload) -> Result { + ws::start(WsChatSession::default(), &req, stream) } #[derive(Default)] @@ -34,7 +28,7 @@ impl WsChatSession { // First send a leave message for the current room let leave_msg = LeaveRoom(self.room.clone(), self.id); // issue_sync comes from having the `BrokerIssue` trait in scope. - self.issue_sync(leave_msg, ctx); + self.issue_system_sync(leave_msg, ctx); // Then send a join message for the new room let join_msg = JoinRoom( room_name.to_owned(), @@ -52,7 +46,8 @@ impl WsChatSession { } fut::ok(()) - }).spawn(ctx); + }) + .spawn(ctx); } fn list_rooms(&mut self, ctx: &mut ws::WebsocketContext) { @@ -66,18 +61,19 @@ impl WsChatSession { } } fut::ok(()) - }).spawn(ctx); + }) + .spawn(ctx); } fn send_msg(&self, msg: &str) { let content = format!( "{}: {}", - self.name.clone().unwrap_or("anon".to_string()), + self.name.clone().unwrap_or_else(|| "anon".to_string()), msg ); let msg = SendMessage(self.room.clone(), self.id, content); // issue_async comes from having the `BrokerIssue` trait in scope. - self.issue_async(msg); + self.issue_system_async(msg); } } @@ -91,7 +87,7 @@ impl Actor for WsChatSession { fn stopped(&mut self, _ctx: &mut Self::Context) { info!( "WsChatSession closed for {}({}) in room {}", - self.name.clone().unwrap_or("anon".to_string()), + self.name.clone().unwrap_or_else(|| "anon".to_string()), self.id, self.room ); @@ -145,23 +141,19 @@ impl StreamHandler for WsChatSession { } } -fn main() { +fn main() -> std::io::Result<()> { let sys = actix::System::new("websocket-broker-example"); simple_logger::init_with_level(log::Level::Info).unwrap(); HttpServer::new(move || { App::new() - .resource("/ws/", |r| r.route().f(chat_route)) - .handler( - "/", - fs::StaticFiles::new("./static/") - .unwrap() - .index_file("index.html"), - ) - }).bind("127.0.0.1:8080") + .service(web::resource("/ws/").to(chat_route)) + .service(Files::new("/", "./static/").index_file("index.html")) + }) + .bind("127.0.0.1:8080") .unwrap() .start(); info!("Started http server: 127.0.0.1:8080"); - let _ = sys.run(); + sys.run() } diff --git a/websocket-chat-broker/src/server.rs b/websocket-chat-broker/src/server.rs index 87a6052b..946f670a 100644 --- a/websocket-chat-broker/src/server.rs +++ b/websocket-chat-broker/src/server.rs @@ -42,7 +42,7 @@ impl WsChatServer { id: Option, client: Client, ) -> usize { - let mut id = id.unwrap_or_else(|| rand::random::()); + let mut id = id.unwrap_or_else(rand::random::); if let Some(room) = self.rooms.get_mut(room_name) { loop { if room.contains_key(&id) { @@ -81,8 +81,8 @@ impl Actor for WsChatServer { type Context = Context; fn started(&mut self, ctx: &mut Self::Context) { - self.subscribe_async::(ctx); - self.subscribe_async::(ctx); + self.subscribe_system_async::(ctx); + self.subscribe_system_async::(ctx); } } @@ -94,7 +94,7 @@ impl Handler for WsChatServer { let id = self.add_client_to_room(&room_name, None, client); let join_msg = format!( "{} joined {}", - client_name.unwrap_or("anon".to_string()), + client_name.unwrap_or_else(|| "anon".to_string()), room_name ); self.send_chat_message(&room_name, &join_msg, id); diff --git a/websocket-chat/Cargo.toml b/websocket-chat/Cargo.toml index b6d281ff..fb5817f5 100644 --- a/websocket-chat/Cargo.toml +++ b/websocket-chat/Cargo.toml @@ -2,23 +2,24 @@ name = "websocket-example" version = "0.1.0" authors = ["Nikolay Kim "] -workspace = "../" +workspace = ".." +edition = "2018" [[bin]] name = "websocket-chat-server" path = "src/main.rs" [dependencies] -rand = "*" +actix = "0.8.2" +actix-web = "1.0.0" +actix-web-actors = "1.0.0" +actix-files = "0.1.1" + +rand = "0.6" bytes = "0.4" byteorder = "1.1" -futures = "0.1" +futures = "0.1.25" tokio-io = "0.1" -tokio-core = "0.1" -env_logger = "*" - +env_logger = "0.6" serde = "1.0" serde_json = "1.0" - -actix = "0.7" -actix-web = "0.7" diff --git a/websocket-chat/src/main.rs b/websocket-chat/src/main.rs index 477b8e40..f8eee4c3 100644 --- a/websocket-chat/src/main.rs +++ b/websocket-chat/src/main.rs @@ -1,22 +1,9 @@ -#![allow(unused_variables)] -extern crate byteorder; -extern crate bytes; -extern crate env_logger; -extern crate futures; -extern crate rand; -extern crate serde; -extern crate serde_json; -extern crate tokio_core; -extern crate tokio_io; - -extern crate actix; -extern crate actix_web; - -use std::time::{Instant, Duration}; +use std::time::{Duration, Instant}; use actix::*; -use actix_web::server::HttpServer; -use actix_web::{fs, http, ws, App, Error, HttpRequest, HttpResponse}; +use actix_files as fs; +use actix_web::{web, App, Error, HttpRequest, HttpResponse, HttpServer}; +use actix_web_actors::ws; mod server; @@ -25,22 +12,22 @@ const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5); /// How long before lack of client response causes a timeout const CLIENT_TIMEOUT: Duration = Duration::from_secs(10); -/// This is our websocket route state, this state is shared with all route -/// instances via `HttpContext::state()` -struct WsChatSessionState { - addr: Addr, -} - /// Entry point for our route -fn chat_route(req: &HttpRequest) -> Result { +fn chat_route( + req: HttpRequest, + stream: web::Payload, + srv: web::Data>, +) -> Result { ws::start( - req, WsChatSession { id: 0, hb: Instant::now(), room: "Main".to_owned(), name: None, + addr: srv.get_ref().clone(), }, + &req, + stream, ) } @@ -54,10 +41,12 @@ struct WsChatSession { room: String, /// peer name name: Option, + /// Chat server + addr: Addr, } impl Actor for WsChatSession { - type Context = ws::WebsocketContext; + type Context = ws::WebsocketContext; /// Method is called on actor start. /// We register ws session with ChatServer @@ -71,8 +60,7 @@ impl Actor for WsChatSession { // HttpContext::state() is instance of WsChatSessionState, state is shared // across all routes within application let addr = ctx.address(); - ctx.state() - .addr + self.addr .send(server::Connect { addr: addr.recipient(), }) @@ -88,9 +76,9 @@ impl Actor for WsChatSession { .wait(ctx); } - fn stopping(&mut self, ctx: &mut Self::Context) -> Running { + fn stopping(&mut self, _: &mut Self::Context) -> Running { // notify chat server - ctx.state().addr.do_send(server::Disconnect { id: self.id }); + self.addr.do_send(server::Disconnect { id: self.id }); Running::Stop } } @@ -126,8 +114,7 @@ impl StreamHandler for WsChatSession { // Send ListRooms message to chat server and wait for // response println!("List rooms"); - ctx.state() - .addr + self.addr .send(server::ListRooms) .into_actor(self) .then(|res, _, ctx| { @@ -149,7 +136,7 @@ impl StreamHandler for WsChatSession { "/join" => { if v.len() == 2 { self.room = v[1].to_owned(); - ctx.state().addr.do_send(server::Join { + self.addr.do_send(server::Join { id: self.id, name: self.room.clone(), }); @@ -175,17 +162,18 @@ impl StreamHandler for WsChatSession { m.to_owned() }; // send message to chat server - ctx.state().addr.do_send(server::ClientMessage { + self.addr.do_send(server::ClientMessage { id: self.id, - msg: msg, + msg, room: self.room.clone(), }) } } - ws::Message::Binary(bin) => println!("Unexpected binary"), + ws::Message::Binary(_) => println!("Unexpected binary"), ws::Message::Close(_) => { ctx.stop(); - }, + } + ws::Message::Nop => (), } } } @@ -194,7 +182,7 @@ impl WsChatSession { /// helper method that sends ping to client every second. /// /// also this method checks heartbeats from client - fn hb(&self, ctx: &mut ws::WebsocketContext) { + fn hb(&self, ctx: &mut ws::WebsocketContext) { ctx.run_interval(HEARTBEAT_INTERVAL, |act, ctx| { // check client heartbeats if Instant::now().duration_since(act.hb) > CLIENT_TIMEOUT { @@ -202,9 +190,7 @@ impl WsChatSession { println!("Websocket Client heartbeat failed, disconnecting!"); // notify chat server - ctx.state() - .addr - .do_send(server::Disconnect { id: act.id }); + act.addr.do_send(server::Disconnect { id: act.id }); // stop actor ctx.stop(); @@ -218,35 +204,30 @@ impl WsChatSession { } } -fn main() { - let _ = env_logger::init(); - let sys = actix::System::new("websocket-example"); +fn main() -> std::io::Result<()> { + env_logger::init(); + let sys = System::new("ws-example"); - // Start chat server actor in separate thread - let server = Arbiter::start(|_| server::ChatServer::default()); + // Start chat server actor + let server = server::ChatServer::default().start(); // Create Http server with websocket support HttpServer::new(move || { - // Websocket sessions state - let state = WsChatSessionState { - addr: server.clone(), - }; - - App::with_state(state) - // redirect to websocket.html - .resource("/", |r| r.method(http::Method::GET).f(|_| { + App::new() + .data(server.clone()) + // redirect to websocket.html + .service(web::resource("/").route(web::get().to(|| { HttpResponse::Found() .header("LOCATION", "/static/websocket.html") .finish() - })) - // websocket - .resource("/ws/", |r| r.route().f(chat_route)) - // static resources - .handler("/static/", fs::StaticFiles::new("static/").unwrap()) - }).bind("127.0.0.1:8080") - .unwrap() - .start(); + }))) + // websocket + .service(web::resource("/ws/").to(chat_route)) + // static resources + .service(fs::Files::new("/static/", "static/")) + }) + .bind("127.0.0.1:8080")? + .start(); - println!("Started http server: 127.0.0.1:8080"); - let _ = sys.run(); + sys.run() } diff --git a/websocket-chat/src/server.rs b/websocket-chat/src/server.rs index 80d62b6d..f8acc451 100644 --- a/websocket-chat/src/server.rs +++ b/websocket-chat/src/server.rs @@ -68,7 +68,7 @@ impl Default for ChatServer { ChatServer { sessions: HashMap::new(), - rooms: rooms, + rooms, rng: rand::thread_rng(), } } diff --git a/websocket-tcp-chat/Cargo.toml b/websocket-tcp-chat/Cargo.toml index c73d5678..dd100b89 100644 --- a/websocket-tcp-chat/Cargo.toml +++ b/websocket-tcp-chat/Cargo.toml @@ -2,7 +2,8 @@ name = "websocket-tcp-example" version = "0.1.0" authors = ["Nikolay Kim "] -workspace = "../" +workspace = ".." +edition = "2018" [[bin]] name = "websocket-tcp-server" @@ -13,18 +14,19 @@ name = "websocket-tcp-client" path = "src/client.rs" [dependencies] -rand = "*" +actix = "0.8.2" +actix-web = "1.0.0" +actix-web-actors = "1.0.0" +actix-files = "0.1.1" + +rand = "0.6" bytes = "0.4" byteorder = "1.1" futures = "0.1" tokio-io = "0.1" tokio-tcp = "0.1" tokio-codec = "0.1" -env_logger = "*" - +env_logger = "0.6" serde = "1.0" serde_json = "1.0" serde_derive = "1.0" - -actix = "0.7" -actix-web = "0.7" diff --git a/websocket-tcp-chat/src/client.rs b/websocket-tcp-chat/src/client.rs index 28313e29..1694c870 100644 --- a/websocket-tcp-chat/src/client.rs +++ b/websocket-tcp-chat/src/client.rs @@ -154,7 +154,7 @@ impl StreamHandler for ChatClient { for room in rooms { println!("{}", room); } - println!(""); + println!(); } _ => (), } diff --git a/websocket-tcp-chat/src/codec.rs b/websocket-tcp-chat/src/codec.rs index 55cf0db3..8d362630 100644 --- a/websocket-tcp-chat/src/codec.rs +++ b/websocket-tcp-chat/src/codec.rs @@ -65,7 +65,9 @@ impl Encoder for ChatCodec { type Error = io::Error; fn encode( - &mut self, msg: ChatResponse, dst: &mut BytesMut, + &mut self, + msg: ChatResponse, + dst: &mut BytesMut, ) -> Result<(), Self::Error> { let msg = json::to_string(&msg).unwrap(); let msg_ref: &[u8] = msg.as_ref(); @@ -108,7 +110,9 @@ impl Encoder for ClientChatCodec { type Error = io::Error; fn encode( - &mut self, msg: ChatRequest, dst: &mut BytesMut, + &mut self, + msg: ChatRequest, + dst: &mut BytesMut, ) -> Result<(), Self::Error> { let msg = json::to_string(&msg).unwrap(); let msg_ref: &[u8] = msg.as_ref(); diff --git a/websocket-tcp-chat/src/main.rs b/websocket-tcp-chat/src/main.rs index 099ba8d4..7aeaec8e 100644 --- a/websocket-tcp-chat/src/main.rs +++ b/websocket-tcp-chat/src/main.rs @@ -1,25 +1,14 @@ -#![allow(unused_variables)] -extern crate byteorder; -extern crate bytes; -extern crate env_logger; -extern crate futures; -extern crate rand; -extern crate serde; -extern crate serde_json; -extern crate tokio_codec; -extern crate tokio_io; -extern crate tokio_tcp; #[macro_use] extern crate serde_derive; - #[macro_use] extern crate actix; -extern crate actix_web; + +use std::time::{Duration, Instant}; use actix::*; -use actix_web::server::HttpServer; -use actix_web::{fs, http, ws, App, Error, HttpRequest, HttpResponse}; -use std::time::{Instant, Duration}; +use actix_files as fs; +use actix_web::{web, App, Error, HttpRequest, HttpResponse, HttpServer}; +use actix_web_actors::ws; mod codec; mod server; @@ -30,22 +19,22 @@ const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5); /// How long before lack of client response causes a timeout const CLIENT_TIMEOUT: Duration = Duration::from_secs(10); -/// This is our websocket route state, this state is shared with all route -/// instances via `HttpContext::state()` -struct WsChatSessionState { - addr: Addr, -} - /// Entry point for our route -fn chat_route(req: &HttpRequest) -> Result { +fn chat_route( + req: HttpRequest, + stream: web::Payload, + srv: web::Data>, +) -> Result { ws::start( - req, WsChatSession { id: 0, hb: Instant::now(), room: "Main".to_owned(), name: None, + addr: srv.get_ref().clone(), }, + &req, + stream, ) } @@ -59,26 +48,26 @@ struct WsChatSession { room: String, /// peer name name: Option, + /// Chat server + addr: Addr, } impl Actor for WsChatSession { - type Context = ws::WebsocketContext; + type Context = ws::WebsocketContext; /// Method is called on actor start. /// We register ws session with ChatServer fn started(&mut self, ctx: &mut Self::Context) { + // we'll start heartbeat process on session start. + self.hb(ctx); + // register self in chat server. `AsyncContext::wait` register // future within context, but context waits until this future resolves // before processing any other events. // HttpContext::state() is instance of WsChatSessionState, state is shared // across all routes within application - - // we'll start heartbeat process on session start. - self.hb(ctx); - let addr = ctx.address(); - ctx.state() - .addr + self.addr .send(server::Connect { addr: addr.recipient(), }) @@ -94,9 +83,9 @@ impl Actor for WsChatSession { .wait(ctx); } - fn stopping(&mut self, ctx: &mut Self::Context) -> Running { + fn stopping(&mut self, _: &mut Self::Context) -> Running { // notify chat server - ctx.state().addr.do_send(server::Disconnect { id: self.id }); + self.addr.do_send(server::Disconnect { id: self.id }); Running::Stop } } @@ -132,8 +121,7 @@ impl StreamHandler for WsChatSession { // Send ListRooms message to chat server and wait for // response println!("List rooms"); - ctx.state() - .addr + self.addr .send(server::ListRooms) .into_actor(self) .then(|res, _, ctx| { @@ -155,7 +143,7 @@ impl StreamHandler for WsChatSession { "/join" => { if v.len() == 2 { self.room = v[1].to_owned(); - ctx.state().addr.do_send(server::Join { + self.addr.do_send(server::Join { id: self.id, name: self.room.clone(), }); @@ -181,17 +169,18 @@ impl StreamHandler for WsChatSession { m.to_owned() }; // send message to chat server - ctx.state().addr.do_send(server::Message { + self.addr.do_send(server::Message { id: self.id, - msg: msg, + msg, room: self.room.clone(), }) } } - ws::Message::Binary(bin) => println!("Unexpected binary"), + ws::Message::Binary(_) => println!("Unexpected binary"), ws::Message::Close(_) => { ctx.stop(); - }, + } + ws::Message::Nop => (), } } } @@ -200,7 +189,7 @@ impl WsChatSession { /// helper method that sends ping to client every second. /// /// also this method checks heartbeats from client - fn hb(&self, ctx: &mut ws::WebsocketContext) { + fn hb(&self, ctx: &mut ws::WebsocketContext) { ctx.run_interval(HEARTBEAT_INTERVAL, |act, ctx| { // check client heartbeats if Instant::now().duration_since(act.hb) > CLIENT_TIMEOUT { @@ -208,9 +197,7 @@ impl WsChatSession { println!("Websocket Client heartbeat failed, disconnecting!"); // notify chat server - ctx.state() - .addr - .do_send(server::Disconnect { id: act.id }); + act.addr.do_send(server::Disconnect { id: act.id }); // stop actor ctx.stop(); @@ -224,42 +211,38 @@ impl WsChatSession { } } -fn main() { - let _ = env_logger::init(); +fn main() -> std::io::Result<()> { + env_logger::init(); let sys = actix::System::new("websocket-example"); - // Start chat server actor in separate thread - let server = Arbiter::start(|_| server::ChatServer::default()); + // Start chat server actor + let server = server::ChatServer::default().start(); // Start tcp server in separate thread let srv = server.clone(); - Arbiter::new("tcp-server").do_send::(msgs::Execute::new(move || { + Arbiter::new().exec(move || { session::TcpServer::new("127.0.0.1:12345", srv); - Ok(()) - })); + Ok::<_, ()>(()) + }); // Create Http server with websocket support HttpServer::new(move || { - // Websocket sessions state - let state = WsChatSessionState { - addr: server.clone(), - }; - - App::with_state(state) + App::new() + .data(server.clone()) // redirect to websocket.html - .resource("/", |r| r.method(http::Method::GET).f(|_| { + .service(web::resource("/").route(web::get().to(|| { HttpResponse::Found() .header("LOCATION", "/static/websocket.html") .finish() - })) + }))) // websocket - .resource("/ws/", |r| r.route().f(chat_route)) + .service(web::resource("/ws/").to(chat_route)) // static resources - .handler("/static/", fs::StaticFiles::new("static/").unwrap()) - }).bind("127.0.0.1:8080") - .unwrap() - .start(); + .service(fs::Files::new("/static/", "static/")) + }) + .bind("127.0.0.1:8080")? + .start(); println!("Started http server: 127.0.0.1:8080"); - let _ = sys.run(); + sys.run() } diff --git a/websocket-tcp-chat/src/server.rs b/websocket-tcp-chat/src/server.rs index efc946a8..b7a1a3db 100644 --- a/websocket-tcp-chat/src/server.rs +++ b/websocket-tcp-chat/src/server.rs @@ -6,7 +6,7 @@ use actix::prelude::*; use rand::{self, rngs::ThreadRng, Rng}; use std::collections::{HashMap, HashSet}; -use session; +use crate::session; /// Message for chat server communications @@ -66,7 +66,7 @@ impl Default for ChatServer { ChatServer { sessions: HashMap::new(), - rooms: rooms, + rooms, rng: rand::thread_rng(), } } diff --git a/websocket-tcp-chat/src/session.rs b/websocket-tcp-chat/src/session.rs index 37bd33a9..711b3287 100644 --- a/websocket-tcp-chat/src/session.rs +++ b/websocket-tcp-chat/src/session.rs @@ -11,8 +11,8 @@ use tokio_tcp::{TcpListener, TcpStream}; use actix::prelude::*; -use codec::{ChatCodec, ChatRequest, ChatResponse}; -use server::{self, ChatServer}; +use crate::codec::{ChatCodec, ChatRequest, ChatResponse}; +use crate::server::{self, ChatServer}; /// Chat server sends this messages to session #[derive(Message)] @@ -62,7 +62,7 @@ impl Actor for ChatSession { .wait(ctx); } - fn stopping(&mut self, ctx: &mut Self::Context) -> Running { + fn stopping(&mut self, _: &mut Self::Context) -> Running { // notify chat server self.addr.do_send(server::Disconnect { id: self.id }); Running::Stop @@ -82,7 +82,7 @@ impl StreamHandler for ChatSession { self.addr .send(server::ListRooms) .into_actor(self) - .then(|res, act, ctx| { + .then(|res, act, _| { match res { Ok(rooms) => { act.framed.write(ChatResponse::Rooms(rooms)); @@ -124,7 +124,7 @@ impl StreamHandler for ChatSession { impl Handler for ChatSession { type Result = (); - fn handle(&mut self, msg: Message, ctx: &mut Context) { + fn handle(&mut self, msg: Message, _: &mut Context) { // send message to peer self.framed.write(ChatResponse::Message(msg.0)); } @@ -138,10 +138,10 @@ impl ChatSession { ) -> ChatSession { ChatSession { id: 0, - addr: addr, + addr, hb: Instant::now(), room: "Main".to_owned(), - framed: framed, + framed, } } @@ -175,7 +175,7 @@ pub struct TcpServer { } impl TcpServer { - pub fn new(s: &str, chat: Addr) { + pub fn new(_s: &str, chat: Addr) { // Create server listener let addr = net::SocketAddr::from_str("127.0.0.1:12345").unwrap(); let listener = TcpListener::bind(&addr).unwrap(); @@ -187,10 +187,8 @@ impl TcpServer { // implement stream handler `StreamHandler<(TcpStream, // net::SocketAddr), io::Error>` TcpServer::create(|ctx| { - ctx.add_message_stream( - listener.incoming().map_err(|_| ()).map(|s| TcpConnect(s)), - ); - TcpServer { chat: chat } + ctx.add_message_stream(listener.incoming().map_err(|_| ()).map(TcpConnect)); + TcpServer { chat } }); } } diff --git a/websocket/Cargo.toml b/websocket/Cargo.toml index 4ed656ec..891ef14e 100644 --- a/websocket/Cargo.toml +++ b/websocket/Cargo.toml @@ -2,7 +2,8 @@ name = "websocket" version = "0.1.0" authors = ["Nikolay Kim "] -workspace = "../" +edition = "2018" +workspace = ".." [[bin]] name = "websocket-server" @@ -13,8 +14,12 @@ name = "websocket-client" path = "src/client.rs" [dependencies] -env_logger = "*" +actix = "0.8.2" +actix-codec = "0.1.2" +actix-web = "1.0.0" +actix-web-actors = "1.0.0" +actix-files = "0.1.1" +awc = "0.2.1" +env_logger = "0.6" futures = "0.1" - -actix = "0.7" -actix-web = "0.7" +bytes = "0.4" \ No newline at end of file diff --git a/websocket/src/client.rs b/websocket/src/client.rs index 7cb5fa3b..0a9eee35 100644 --- a/websocket/src/client.rs +++ b/websocket/src/client.rs @@ -1,34 +1,39 @@ //! Simple websocket client. - -#![allow(unused_variables)] -extern crate actix; -extern crate actix_web; -extern crate env_logger; -extern crate futures; - use std::time::Duration; use std::{io, thread}; +use actix::io::SinkWrite; use actix::*; -use actix_web::ws::{Client, ClientWriter, Message, ProtocolError}; -use futures::Future; +use actix_codec::{AsyncRead, AsyncWrite, Framed}; +use awc::{ + error::WsProtocolError, + ws::{Codec, Frame, Message}, + Client, +}; +use futures::{ + lazy, + stream::{SplitSink, Stream}, + Future, +}; fn main() { ::std::env::set_var("RUST_LOG", "actix_web=info"); - let _ = env_logger::init(); + env_logger::init(); let sys = actix::System::new("ws-example"); - Arbiter::spawn( - Client::new("http://127.0.0.1:8080/ws/") + Arbiter::spawn(lazy(|| { + Client::new() + .ws("http://127.0.0.1:8080/ws/") .connect() .map_err(|e| { println!("Error: {}", e); - () }) - .map(|(reader, writer)| { + .map(|(response, framed)| { + println!("{:?}", response); + let (sink, stream) = framed.split(); let addr = ChatClient::create(|ctx| { - ChatClient::add_stream(reader, ctx); - ChatClient(writer) + ChatClient::add_stream(stream, ctx); + ChatClient(SinkWrite::new(sink, ctx)) }); // start console loop @@ -40,20 +45,23 @@ fn main() { } addr.do_send(ClientCommand(cmd)); }); - - () - }), - ); + }) + })); let _ = sys.run(); } -struct ChatClient(ClientWriter); +struct ChatClient(SinkWrite>>) +where + T: AsyncRead + AsyncWrite; #[derive(Message)] struct ClientCommand(String); -impl Actor for ChatClient { +impl Actor for ChatClient +where + T: AsyncRead + AsyncWrite, +{ type Context = Context; fn started(&mut self, ctx: &mut Context) { @@ -69,10 +77,13 @@ impl Actor for ChatClient { } } -impl ChatClient { +impl ChatClient +where + T: AsyncRead + AsyncWrite, +{ fn hb(&self, ctx: &mut Context) { ctx.run_later(Duration::new(1, 0), |act, ctx| { - act.0.ping(""); + act.0.write(Message::Ping(String::new())).unwrap(); act.hb(ctx); // client should also check for a timeout here, similar to the @@ -82,24 +93,29 @@ impl ChatClient { } /// Handle stdin commands -impl Handler for ChatClient { +impl Handler for ChatClient +where + T: AsyncRead + AsyncWrite, +{ type Result = (); - fn handle(&mut self, msg: ClientCommand, ctx: &mut Context) { - self.0.text(msg.0) + fn handle(&mut self, msg: ClientCommand, _ctx: &mut Context) { + self.0.write(Message::Text(msg.0)).unwrap(); } } /// Handle server websocket messages -impl StreamHandler for ChatClient { - fn handle(&mut self, msg: Message, ctx: &mut Context) { - match msg { - Message::Text(txt) => println!("Server: {:?}", txt), - _ => (), +impl StreamHandler for ChatClient +where + T: AsyncRead + AsyncWrite, +{ + fn handle(&mut self, msg: Frame, _ctx: &mut Context) { + if let Frame::Text(txt) = msg { + println!("Server: {:?}", txt) } } - fn started(&mut self, ctx: &mut Context) { + fn started(&mut self, _ctx: &mut Context) { println!("Connected"); } @@ -108,3 +124,8 @@ impl StreamHandler for ChatClient { ctx.stop() } } + +impl actix::io::WriteHandler for ChatClient where + T: AsyncRead + AsyncWrite +{ +} diff --git a/websocket/src/main.rs b/websocket/src/main.rs index 84aa4646..b16e3fa3 100644 --- a/websocket/src/main.rs +++ b/websocket/src/main.rs @@ -3,17 +3,12 @@ //! or [python console client](https://github.com/actix/examples/blob/master/websocket/websocket-client.py) //! could be used for testing. -#![allow(unused_variables)] -extern crate actix; -extern crate actix_web; -extern crate env_logger; - -use std::time::{Instant, Duration}; +use std::time::{Duration, Instant}; use actix::prelude::*; -use actix_web::{ - fs, http, middleware, server, ws, App, Error, HttpRequest, HttpResponse, -}; +use actix_files as fs; +use actix_web::{middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer}; +use actix_web_actors::ws; /// How often heartbeat pings are sent const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5); @@ -21,8 +16,11 @@ const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5); const CLIENT_TIMEOUT: Duration = Duration::from_secs(10); /// do websocket handshake and start `MyWebSocket` actor -fn ws_index(r: &HttpRequest) -> Result { - ws::start(r, MyWebSocket::new()) +fn ws_index(r: HttpRequest, stream: web::Payload) -> Result { + println!("{:?}", r); + let res = ws::start(MyWebSocket::new(), &r, stream); + println!("{:?}", res.as_ref().unwrap()); + res } /// websocket connection is long running connection, it easier @@ -60,6 +58,7 @@ impl StreamHandler for MyWebSocket { ws::Message::Close(_) => { ctx.stop(); } + ws::Message::Nop => (), } } } @@ -91,25 +90,20 @@ impl MyWebSocket { } } -fn main() { - ::std::env::set_var("RUST_LOG", "actix_web=info"); +fn main() -> std::io::Result<()> { + std::env::set_var("RUST_LOG", "actix_server=info,actix_web=info"); env_logger::init(); - let sys = actix::System::new("ws-example"); - server::new( - || App::new() + HttpServer::new(|| { + App::new() // enable logger - .middleware(middleware::Logger::default()) + .wrap(middleware::Logger::default()) // websocket route - .resource("/ws/", |r| r.method(http::Method::GET).f(ws_index)) + .service(web::resource("/ws/").route(web::get().to(ws_index))) // static files - .handler("/", fs::StaticFiles::new("static/") - .unwrap() - .index_file("index.html"))) - // start http server on 127.0.0.1:8080 - .bind("127.0.0.1:8080").unwrap() - .start(); - - println!("Started http server: 127.0.0.1:8080"); - let _ = sys.run(); + .service(fs::Files::new("/", "static/").index_file("index.html")) + }) + // start http server on 127.0.0.1:8080 + .bind("127.0.0.1:8080")? + .run() }