Bridge progress
This commit is contained in:
parent
59c1ec058c
commit
b21d49a125
17 changed files with 1054 additions and 1011 deletions
1728
Cargo.lock
generated
1728
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -35,7 +35,7 @@ iso_currency = { version = "0.4.4", features = ["with-serde", "iterator"] }
|
|||
pulldown-cmark = "0.11.0"
|
||||
http = "1"
|
||||
thiserror = "1.0.61"
|
||||
wasm-bindgen = "=0.2.92"
|
||||
wasm-bindgen = "=0.2.93"
|
||||
console_error_panic_hook = "0.1"
|
||||
leptos = { version = "0.6", features = ["nightly"] }
|
||||
leptos_axum = { version = "0.6" }
|
||||
|
|
|
@ -36,17 +36,16 @@ http = { workspace = true }
|
|||
pulldown-cmark = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
saleor-app-sdk = { workspace = true, features = ["bridge"], optional = true }
|
||||
dotenvy = { workspace = true }
|
||||
envy = { workspace = true }
|
||||
cynic = { workspace = true, features = ["http-surf"], optional = true }
|
||||
surf = { workspace = true, optional = true }
|
||||
saleor-app-sdk = { workspace = true, optional = true }
|
||||
|
||||
[build-dependencies]
|
||||
cynic-codegen = { workspace = true, optional = true }
|
||||
|
||||
[features]
|
||||
hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"]
|
||||
ssr = [
|
||||
"dep:axum",
|
||||
"dep:tokio",
|
||||
|
@ -55,6 +54,11 @@ ssr = [
|
|||
"dep:leptos_axum",
|
||||
"dep:tracing",
|
||||
"dep:saleor-app-sdk",
|
||||
"saleor-app-sdk/file_apl",
|
||||
"saleor-app-sdk/redis_apl",
|
||||
"saleor-app-sdk/tracing",
|
||||
"saleor-app-sdk/middleware",
|
||||
"saleor-app-sdk/webhook_utils",
|
||||
"dep:tracing-subscriber",
|
||||
"dep:anyhow",
|
||||
"dep:cynic",
|
||||
|
@ -64,6 +68,13 @@ ssr = [
|
|||
"leptos_meta/ssr",
|
||||
"leptos_router/ssr",
|
||||
]
|
||||
hydrate = [
|
||||
"leptos/hydrate",
|
||||
"leptos_meta/hydrate",
|
||||
"leptos_router/hydrate",
|
||||
"dep:saleor-app-sdk",
|
||||
"saleor-app-sdk/bridge",
|
||||
]
|
||||
|
||||
|
||||
# Defines a size-optimized profile for the WASM bundle in release mode
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
use crate::error_template::{AppError, ErrorTemplate};
|
||||
use crate::routes::extensions::order_to_pdf::Pdf;
|
||||
use crate::routes::home::Home;
|
||||
use leptos::*;
|
||||
use leptos_meta::*;
|
||||
use leptos_router::*;
|
||||
use saleor_app_sdk::bridge::AppBridge;
|
||||
|
||||
#[derive(Params, PartialEq)]
|
||||
pub struct UrlAppParams {
|
||||
|
@ -13,22 +15,23 @@ pub struct UrlAppParams {
|
|||
pub fn App() -> impl IntoView {
|
||||
// Provides context that manages stylesheets, titles, meta tags, etc.
|
||||
provide_meta_context();
|
||||
|
||||
let app_bridge = AppBridge::new(Some(true)).unwrap();
|
||||
view! {
|
||||
<Stylesheet id="leptos" href="/pkg/saleor-app-template-ui.css"/>
|
||||
<Stylesheet id="leptos" href="/pkg/saleor-app-template-ui.css" />
|
||||
|
||||
// sets the document title
|
||||
<Title text="Example UI App template in Rust"/>
|
||||
<Title text="Example UI App template in Rust" />
|
||||
|
||||
// content for this welcome page
|
||||
<Router fallback=|| {
|
||||
let mut outside_errors = Errors::default();
|
||||
outside_errors.insert_with_default_key(AppError::NotFound);
|
||||
view! { <ErrorTemplate outside_errors/> }.into_view()
|
||||
view! { <ErrorTemplate outside_errors /> }.into_view()
|
||||
}>
|
||||
<main class="p-4 md:p-8 md:px-16">
|
||||
<Routes>
|
||||
<Route path="/" view=Home/>
|
||||
<Route path="/" view=Home />
|
||||
<Route path="/extensions/order_to_pdf" view=Pdf />
|
||||
</Routes>
|
||||
</main>
|
||||
</Router>
|
||||
|
|
|
@ -26,7 +26,7 @@ async fn main() -> Result<(), std::io::Error> {
|
|||
use leptos_axum::{generate_route_list, LeptosRoutes };
|
||||
use app::*;
|
||||
use fileserv::file_and_error_handler;
|
||||
use saleor_app_sdk::middleware::verify_webhook_signature::webhook_signature_verifier;
|
||||
use saleor_app_sdk::{manifest::{extension::AppExtensionBuilder, AppExtensionMount, AppExtensionTarget}, middleware::verify_webhook_signature::webhook_signature_verifier};
|
||||
use tokio::sync::Mutex;
|
||||
use saleor_app_sdk::{
|
||||
cargo_info,
|
||||
|
@ -49,48 +49,16 @@ async fn main() -> Result<(), std::io::Error> {
|
|||
let saleor_app = SaleorApp::new(&config).unwrap();
|
||||
|
||||
let app_manifest = AppManifestBuilder::new(&config, cargo_info!())
|
||||
.add_webhook(
|
||||
WebhookManifestBuilder::new(&config)
|
||||
.set_query(
|
||||
r#"
|
||||
subscription QueryProductsChanged {
|
||||
event {
|
||||
... on ProductUpdated {
|
||||
product {
|
||||
... BaseProduct
|
||||
}
|
||||
}
|
||||
... on ProductCreated {
|
||||
product {
|
||||
... BaseProduct
|
||||
}
|
||||
}
|
||||
... on ProductDeleted {
|
||||
product {
|
||||
... BaseProduct
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fragment BaseProduct on Product {
|
||||
id
|
||||
slug
|
||||
name
|
||||
category {
|
||||
slug
|
||||
}
|
||||
}
|
||||
"#,
|
||||
)
|
||||
.add_async_events(vec![
|
||||
AsyncWebhookEventType::ProductCreated,
|
||||
AsyncWebhookEventType::ProductUpdated,
|
||||
AsyncWebhookEventType::ProductDeleted,
|
||||
])
|
||||
.build(),
|
||||
)
|
||||
.add_permission(AppPermission::ManageProducts)
|
||||
.add_extension(
|
||||
AppExtensionBuilder::new()
|
||||
.set_url("/extensions/order_to_pdf")
|
||||
.set_label("Order to PDF")
|
||||
.add_permissions(vec![AppPermission::ManageOrders, AppPermission::ManageProducts, AppPermission::ManageProductTypesAndAttributes])
|
||||
.set_mount(AppExtensionMount::OrderDetailsMoreActions)
|
||||
.set_target(AppExtensionTarget::Popup)
|
||||
.build()
|
||||
)
|
||||
.build();
|
||||
|
||||
let app_state = AppState{
|
||||
|
|
1
app-template-ui/src/routes/extensions/mod.rs
Normal file
1
app-template-ui/src/routes/extensions/mod.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub mod order_to_pdf;
|
9
app-template-ui/src/routes/extensions/order_to_pdf.rs
Normal file
9
app-template-ui/src/routes/extensions/order_to_pdf.rs
Normal file
|
@ -0,0 +1,9 @@
|
|||
use leptos::*;
|
||||
|
||||
#[component]
|
||||
pub fn Pdf() -> impl IntoView {
|
||||
view! {
|
||||
<h1>Yello!</h1>
|
||||
<p class="italic text-lg">r#"Loading AppBridge, please wait..."#</p>
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
#[cfg(feature = "ssr")]
|
||||
pub mod api;
|
||||
pub mod extensions;
|
||||
pub mod home;
|
||||
|
|
15
sdk/.neoconf.json
Normal file
15
sdk/.neoconf.json
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"lspconfig": {
|
||||
"rust_analyzer": {
|
||||
"rust-analyzer.cargo.features": "all"
|
||||
/*
|
||||
use this only when working with leptos
|
||||
*/
|
||||
// "rust-analyzer.rustfmt.overrideCommand": [
|
||||
// "leptosfmt",
|
||||
// "--stdin",
|
||||
// "--rustfmt"
|
||||
// ]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -48,7 +48,7 @@ tracing-subscriber = { workspace = true, optional = true }
|
|||
## Needed for bridge
|
||||
wasm-bindgen = { workspace = true, optional = true }
|
||||
serde-wasm-bindgen = { version = "0.6.5", optional = true }
|
||||
bus = { version = "2.4.1", optional = true }
|
||||
# bus = { version = "2.4.1", optional = true }
|
||||
|
||||
[dependencies.web-sys]
|
||||
optional = true
|
||||
|
@ -66,7 +66,7 @@ features = [
|
|||
[dev-dependencies]
|
||||
|
||||
[features]
|
||||
default = ["middleware", "redis_apl", "webhook_utils", "tracing"]
|
||||
default = []
|
||||
middleware = [
|
||||
"dep:axum",
|
||||
"dep:jsonwebtoken",
|
||||
|
@ -80,7 +80,7 @@ webhook_utils = ["dep:http"]
|
|||
tracing = ["dep:tracing", "dep:tracing-subscriber"]
|
||||
bridge = [
|
||||
"dep:wasm-bindgen",
|
||||
"dep:bus",
|
||||
# "dep:bus",
|
||||
"dep:serde-wasm-bindgen",
|
||||
"dep:web-sys",
|
||||
]
|
||||
|
|
|
@ -1,65 +1,66 @@
|
|||
use crate::locales::LocaleCode;
|
||||
|
||||
use super::ThemeType;
|
||||
use bus::{Bus, BusReader};
|
||||
// use bus::{Bus, BusReader};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum_macros::EnumIter;
|
||||
// use strum_macros::EnumIter;
|
||||
// use web_sys::js_sys::Object;
|
||||
|
||||
pub struct EventChannels {
|
||||
pub handshake: Bus<PayloadHanshake>,
|
||||
pub response: Bus<PayloadResponse>,
|
||||
pub redirect: Bus<PayloadRedirect>,
|
||||
pub theme: Bus<PayloadTheme>,
|
||||
pub locale_changed: Bus<PayloadLocaleChanged>,
|
||||
pub token_refreshed: Bus<PayloadTokenRefreshed>,
|
||||
}
|
||||
// pub struct EventChannels {
|
||||
// pub handshake: Bus<PayloadHanshake>,
|
||||
// pub response: Bus<PayloadResponse>,
|
||||
// pub redirect: Bus<PayloadRedirect>,
|
||||
// pub theme: Bus<PayloadTheme>,
|
||||
// pub locale_changed: Bus<PayloadLocaleChanged>,
|
||||
// pub token_refreshed: Bus<PayloadTokenRefreshed>,
|
||||
// }
|
||||
//
|
||||
// impl EventChannels {
|
||||
// pub fn new() -> Self {
|
||||
// Self {
|
||||
// handshake: Bus::new(10),
|
||||
// response: Bus::new(10),
|
||||
// redirect: Bus::new(10),
|
||||
// theme: Bus::new(10),
|
||||
// locale_changed: Bus::new(10),
|
||||
// token_refreshed: Bus::new(10),
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// pub fn subscribe_handshake(&mut self) -> BusReader<PayloadHanshake> {
|
||||
// self.handshake.add_rx()
|
||||
// }
|
||||
//
|
||||
// pub fn subscribe_response(&mut self) -> BusReader<PayloadResponse> {
|
||||
// self.response.add_rx()
|
||||
// }
|
||||
//
|
||||
// pub fn subscribe_redirect(&mut self) -> BusReader<PayloadRedirect> {
|
||||
// self.redirect.add_rx()
|
||||
// }
|
||||
//
|
||||
// pub fn subscribe_theme(&mut self) -> BusReader<PayloadTheme> {
|
||||
// self.theme.add_rx()
|
||||
// }
|
||||
//
|
||||
// pub fn subscribe_locale_changed(&mut self) -> BusReader<PayloadLocaleChanged> {
|
||||
// self.locale_changed.add_rx()
|
||||
// }
|
||||
//
|
||||
// pub fn subscribe_token_refreshed(&mut self) -> BusReader<PayloadTokenRefreshed> {
|
||||
// self.token_refreshed.add_rx()
|
||||
// }
|
||||
// }
|
||||
|
||||
impl EventChannels {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
handshake: Bus::new(10),
|
||||
response: Bus::new(10),
|
||||
redirect: Bus::new(10),
|
||||
theme: Bus::new(10),
|
||||
locale_changed: Bus::new(10),
|
||||
token_refreshed: Bus::new(10),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn subscribe_handshake(&mut self) -> BusReader<PayloadHanshake> {
|
||||
self.handshake.add_rx()
|
||||
}
|
||||
|
||||
pub fn subscribe_response(&mut self) -> BusReader<PayloadResponse> {
|
||||
self.response.add_rx()
|
||||
}
|
||||
|
||||
pub fn subscribe_redirect(&mut self) -> BusReader<PayloadRedirect> {
|
||||
self.redirect.add_rx()
|
||||
}
|
||||
|
||||
pub fn subscribe_theme(&mut self) -> BusReader<PayloadTheme> {
|
||||
self.theme.add_rx()
|
||||
}
|
||||
|
||||
pub fn subscribe_locale_changed(&mut self) -> BusReader<PayloadLocaleChanged> {
|
||||
self.locale_changed.add_rx()
|
||||
}
|
||||
|
||||
pub fn subscribe_token_refreshed(&mut self) -> BusReader<PayloadTokenRefreshed> {
|
||||
self.token_refreshed.add_rx()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(EnumIter, Debug)]
|
||||
pub enum EventType {
|
||||
Handshake,
|
||||
Response,
|
||||
Redirect,
|
||||
Theme,
|
||||
LocaleChanged,
|
||||
TokenRefreshed,
|
||||
}
|
||||
// #[derive(EnumIter, Debug)]
|
||||
// pub enum EventType {
|
||||
// Handshake,
|
||||
// Response,
|
||||
// Redirect,
|
||||
// Theme,
|
||||
// LocaleChanged,
|
||||
// TokenRefreshed,
|
||||
// }
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
#[serde(tag = "type", content = "payload")]
|
||||
|
@ -71,6 +72,7 @@ pub enum Event {
|
|||
Theme(PayloadTheme),
|
||||
LocaleChanged(PayloadLocaleChanged),
|
||||
TokenRefreshed(PayloadTokenRefreshed),
|
||||
NotifyReady(String),
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
|
|
|
@ -4,18 +4,17 @@ use std::str::FromStr;
|
|||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum_macros::{EnumString, IntoStaticStr};
|
||||
use tracing::{debug, error, warn};
|
||||
use wasm_bindgen::{closure::Closure, JsCast, JsValue};
|
||||
|
||||
use crate::{locales::LocaleCode, manifest::AppPermission};
|
||||
|
||||
use self::event::{Event, EventChannels};
|
||||
use self::event::Event;
|
||||
use web_sys::console;
|
||||
|
||||
pub struct AppBridge {
|
||||
pub state: AppBridgeState,
|
||||
pub referer_origin: Option<String>,
|
||||
pub event_channels: EventChannels,
|
||||
// pub event_channels: EventChannels,
|
||||
/**
|
||||
* Should automatically emit Actions.NotifyReady.
|
||||
* If app loading time is longer, this can be disabled and sent manually.
|
||||
|
@ -77,7 +76,7 @@ impl AppBridgeState {
|
|||
state.locale = loc
|
||||
}
|
||||
}
|
||||
debug!("state from window: {:?}", &state);
|
||||
// debug!("state from window: {:?}", &state);
|
||||
console::log_1(&format!("state from window: {:?}", &state).into());
|
||||
Ok(state)
|
||||
}
|
||||
|
@ -122,10 +121,10 @@ impl Default for ThemeType {
|
|||
|
||||
impl AppBridge {
|
||||
pub fn new(auto_notify_ready: Option<bool>) -> Result<Self, AppBridgeError> {
|
||||
debug!("creating app bridge");
|
||||
// debug!("creating app bridge");
|
||||
console::log_1(&"creating app bridge".into());
|
||||
if web_sys::Window::is_type_of(&JsValue::from_str("undefined")) {
|
||||
error!("Window is undefined");
|
||||
// error!("Window is undefined");
|
||||
console::log_1(&"Window is undefined".into());
|
||||
return Err(AppBridgeError::WindowIsUndefined);
|
||||
}
|
||||
|
@ -137,7 +136,7 @@ impl AppBridge {
|
|||
})
|
||||
});
|
||||
if referrer.is_none() {
|
||||
warn!("Referrer origin is none");
|
||||
// warn!("Referrer origin is none");
|
||||
console::log_1(&"Referrer origin is none".into());
|
||||
}
|
||||
|
||||
|
@ -148,7 +147,6 @@ impl AppBridge {
|
|||
Err(e) => return Err(AppBridgeError::JsValue(e)),
|
||||
},
|
||||
referer_origin: referrer,
|
||||
event_channels: EventChannels::new(),
|
||||
};
|
||||
if bridge.auto_notify_ready.unwrap_or(false) {
|
||||
bridge.notify_ready()?;
|
||||
|
@ -161,7 +159,7 @@ impl AppBridge {
|
|||
let cb = Closure::wrap(Box::new(|e: JsValue| {
|
||||
let event_data: Result<SaleorIframeEvent, _> = serde_wasm_bindgen::from_value(e);
|
||||
web_sys::console::log_1(&format!("{:?}", &event_data).into());
|
||||
debug!("{:?}", &event_data);
|
||||
// debug!("{:?}", &event_data);
|
||||
}) as Box<dyn FnMut(_)>);
|
||||
window
|
||||
.add_event_listener_with_callback("message", &cb.as_ref().unchecked_ref())
|
||||
|
@ -182,7 +180,8 @@ impl AppBridge {
|
|||
todo!()
|
||||
}
|
||||
pub fn notify_ready(&mut self) -> Result<&mut Self, AppBridgeError> {
|
||||
todo!()
|
||||
self.dispatch_event(Event::NotifyReady("{}".to_owned()))?;
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
|
||||
|
|
@ -9,6 +9,8 @@ pub mod manifest;
|
|||
pub mod middleware;
|
||||
pub mod webhooks;
|
||||
|
||||
use anyhow::bail;
|
||||
|
||||
use apl::{AplType, APL};
|
||||
use config::Config;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
|
58
sdk/src/manifest/extension.rs
Normal file
58
sdk/src/manifest/extension.rs
Normal file
|
@ -0,0 +1,58 @@
|
|||
use super::{AppExtension, AppExtensionMount, AppExtensionTarget, AppPermission};
|
||||
|
||||
pub struct AppExtensionBuilder {
|
||||
pub extension: AppExtension,
|
||||
}
|
||||
|
||||
impl AppExtensionBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
extension: AppExtension::default(),
|
||||
}
|
||||
}
|
||||
/** Name which will be displayed in the dashboard */
|
||||
pub fn set_label(mut self, label: &str) -> Self {
|
||||
label.clone_into(&mut self.extension.label);
|
||||
self
|
||||
}
|
||||
|
||||
/** the place where the extension will be mounted */
|
||||
pub fn set_mount(mut self, mount: AppExtensionMount) -> Self {
|
||||
self.extension.mount = mount;
|
||||
self
|
||||
}
|
||||
|
||||
/** Method of presenting the interface
|
||||
`POPUP` will present the interface in a modal overlay
|
||||
`APP_PAGE` will navigate to the application page
|
||||
@default `POPUP`
|
||||
*/
|
||||
pub fn set_target(mut self, target: AppExtensionTarget) -> Self {
|
||||
self.extension.target = target;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_permission(mut self, permission: AppPermission) -> Self {
|
||||
self.extension.permissions.push(permission);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_permissions(mut self, mut permission: Vec<AppPermission>) -> Self {
|
||||
self.extension.permissions.append(&mut permission);
|
||||
self
|
||||
}
|
||||
|
||||
/** URL of the view to display,
|
||||
you can skip the domain and protocol when target is set to `APP_PAGE`, or when your manifest defines an `appUrl`.
|
||||
|
||||
When target is set to `POPUP`, the url will be used to render an `<iframe>`.
|
||||
*/
|
||||
pub fn set_url(mut self, url: &str) -> Self {
|
||||
url.clone_into(&mut self.extension.url);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> AppExtension {
|
||||
self.extension
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
pub mod extension;
|
||||
|
||||
use crate::{config::Config, webhooks::WebhookManifest};
|
||||
|
||||
|
@ -47,6 +48,11 @@ pub enum AppExtensionMount {
|
|||
OrderOverviewCreate,
|
||||
OrderOverviewMoreActions,
|
||||
}
|
||||
impl Default for AppExtensionMount {
|
||||
fn default() -> Self {
|
||||
Self::ProductOverviewMoreActions
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
|
@ -54,8 +60,13 @@ pub enum AppExtensionTarget {
|
|||
Popup,
|
||||
AppPage,
|
||||
}
|
||||
impl Default for AppExtensionTarget {
|
||||
fn default() -> Self {
|
||||
Self::Popup
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AppExtension {
|
||||
/** Name which will be displayed in the dashboard */
|
||||
|
@ -188,6 +199,20 @@ impl AppManifestBuilder {
|
|||
self.manifest.permissions.append(&mut permissions);
|
||||
self
|
||||
}
|
||||
pub fn add_extension(mut self, extension: AppExtension) -> Self {
|
||||
match &mut self.manifest.extensions {
|
||||
Some(e) => e.push(extension),
|
||||
None => self.manifest.extensions = Some(vec![extension]),
|
||||
}
|
||||
self
|
||||
}
|
||||
pub fn add_extensions(mut self, mut extensions: Vec<AppExtension>) -> Self {
|
||||
match &mut self.manifest.extensions {
|
||||
Some(e) => e.append(&mut extensions),
|
||||
None => self.manifest.extensions = Some(extensions),
|
||||
}
|
||||
self
|
||||
}
|
||||
pub fn build(self) -> AppManifest {
|
||||
self.manifest
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
|
Loading…
Reference in a new issue