initial impl for Standard Notes Actions

This commit is contained in:
Peter Cai 2020-04-07 16:03:09 +08:00
parent 9e4bf24377
commit 2a8ed95488
No known key found for this signature in database
GPG key ID: 71F5FB4E4F3FD54F
4 changed files with 201 additions and 4 deletions

View file

@ -17,7 +17,14 @@ serde = { version = "1.0", features = [ "derive" ] }
serde_json = "1.0"
wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4"
web-sys = { version = "0.3", features = [ "Request", "Response", "ResponseInit", "Url" ] }
web-sys = { version = "0.3", features = [
"Headers",
"Request",
"Response",
"ResponseInit",
"Url",
"UrlSearchParams"
] }
# The `console_error_panic_hook` crate provides better debugging of panics by
# logging them with `console.error`. This is great for development, but requires

View file

@ -1,8 +1,10 @@
#[macro_use]
extern crate lazy_static;
#[macro_use]
mod utils;
mod router;
mod sn;
use cfg_if::cfg_if;
use utils::{Error, MyResult};
@ -28,6 +30,7 @@ lazy_static! {
fn build_routes() -> router::Router {
let mut router = router::Router::new(&default_route);
router.add_route("/hello", &hello_world);
sn::build_routes(&mut router);
return router;
}
@ -45,6 +48,19 @@ async fn hello_world(_req: Request, _url: Url) -> MyResult<Response> {
#[wasm_bindgen]
pub async fn handle_request_rs(req: Request) -> Response {
let url = Url::new(&req.url()).unwrap();
if req.method() == "OPTIONS" {
return Response::new_with_opt_str_and_init(
None, ResponseInit::new()
.status(200)
.headers({
let headers = Headers::new().unwrap();
cors!(headers);
headers
}.as_ref())
).unwrap();
}
let result = ROUTER.execute(req, url).await;
match result {

147
src/sn.rs Normal file
View file

@ -0,0 +1,147 @@
// Interface for Standard Notes (Actions)
use crate::router::Router;
use crate::utils::{self, Error, MyResult};
use serde::{Serialize, Serializer};
use std::vec::Vec;
use web_sys::*;
pub fn build_routes(router: &mut Router) {
router.add_route("/actions", &get_actions);
}
macro_rules! verify_secret {
($url:expr, $params:ident, $config:ident) => {
let $config = utils::get_config();
let $params = UrlSearchParams::new_with_str(&$url.search())
.map_err(|_| Error::BadRequest("Failed to parse query string".into()))?;
if !$params.has("secret") {
return Err(Error::BadRequest("Secret needed".into()));
} else if $params.get("secret").unwrap() != $config.secret {
return Err(Error::Unauthorized("Secret mismatch".into()));
}
};
}
async fn get_actions(_req: Request, url: Url) -> MyResult<Response> {
verify_secret!(url, params, config);
let origin = url.origin();
let mut actions = vec![];
actions.push(Action {
label: "Publish".into(),
url: format!("{}/post?secret={}", origin, config.secret.clone()),
verb: Verb::Post,
context: Context::Item,
content_types: vec![ContentType::Note],
access_type: Some(AccessType::Decrypted)
});
let info = ActionsExtension {
identifier: config.plugin_identifier.clone(),
name: config.title.clone(),
description: format!("Standard Notes plugin for {}", config.title.clone()),
url: format!("{}/actions?secret={}", origin, config.secret.clone()),
content_type: ContentType::Extension,
supported_types: vec![ContentType::Note],
actions
};
Ok(Response::new_with_opt_str_and_init(
Some(&serde_json::to_string(&info)
.map_err(|_| Error::InternalError())?),
ResponseInit::new()
.status(200)
.headers({
let headers = Headers::new().unwrap();
headers.set("Content-Type", "application/json").unwrap();
cors!(headers);
headers
}.as_ref())
).unwrap())
}
pub enum Verb {
Show,
Post,
Render
}
impl Serialize for Verb {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: Serializer
{
serializer.serialize_str(match *self {
Verb::Show => "show",
Verb::Post => "post",
Verb::Render => "render"
})
}
}
pub enum Context {
Item
}
impl Serialize for Context {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: Serializer
{
serializer.serialize_str(match *self {
Context::Item => "Item"
})
}
}
pub enum ContentType {
Note,
Extension
}
impl Serialize for ContentType {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: Serializer
{
serializer.serialize_str(match *self {
ContentType::Note => "Note",
ContentType::Extension => "Extension"
})
}
}
pub enum AccessType {
Decrypted,
Encrypted
}
impl Serialize for AccessType {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: Serializer
{
serializer.serialize_str(match *self {
AccessType::Decrypted => "decrypted",
AccessType::Encrypted => "encrypted"
})
}
}
#[derive(Serialize)]
pub struct Action {
label: String,
url: String,
verb: Verb,
context: Context,
content_types: Vec<ContentType>,
access_type: Option<AccessType>
}
#[derive(Serialize)]
pub struct ActionsExtension {
identifier: String,
name: String,
description: String,
url: String,
content_type: ContentType,
supported_types: Vec<ContentType>,
actions: Vec<Action>
}

View file

@ -17,16 +17,30 @@ cfg_if! {
}
}
#[macro_export]
macro_rules! cors {
($headers:ident) => {
$headers.set("Access-Control-Allow-Origin", "*").unwrap();
$headers.set("Access-Control-Allow-Headers", "*").unwrap();
};
}
pub type MyResult<T> = Result<T, Error>;
pub enum Error {
NotFound(String)
NotFound(String),
BadRequest(String),
Unauthorized(String),
InternalError()
}
impl Error {
pub fn status_code(&self) -> u16 {
match self {
Error::NotFound(_) => 404
Error::NotFound(_) => 404,
Error::BadRequest(_) => 400,
Error::Unauthorized(_) => 401,
Error::InternalError() => 500
}
}
}
@ -36,6 +50,15 @@ impl Into<String> for Error {
match self {
Error::NotFound(reason) => {
format!("Not Found, Reason: {}", reason)
},
Error::BadRequest(reason) => {
format!("Bad Request, Reason: {}", reason)
},
Error::Unauthorized(reason) => {
format!("Unauthorized, Reason: {}", reason)
},
Error::InternalError() => {
format!("Internal Errror")
}
}
}
@ -44,7 +67,11 @@ impl Into<String> for Error {
#[derive(Deserialize)]
pub struct Config {
// The secret value used to authenticate the Standard Notes plugin link
secret: String
pub secret: String,
// Title of the blog
pub title: String,
// Plugin identifier used for Standard Notes
pub plugin_identifier: String
}
pub fn get_config() -> Config {