Fix all AppBridge payloads, split to actions and events etc
Some checks failed
clippy / clippy (push) Has been cancelled

This commit is contained in:
djkato 2024-11-08 01:10:59 +01:00
parent 3d916f1ff4
commit 27c039278c
13 changed files with 216 additions and 52 deletions

6
.env
View file

@ -1,10 +1,10 @@
## COMMON VARIABLES FOR ALL APPS
REQUIRED_SALEOR_VERSION="^3.13"
# only sets port, the host is always 0.0.0.0 (listens to everything). Set this to docker-compose service name
APP_API_BASE_URL="http://10.100.110.29:3000"
APP_IFRAME_BASE_URL="http://10.100.110.29:3000"
APP_API_BASE_URL="http://10.100.110.60:3000"
APP_IFRAME_BASE_URL="http://10.100.110.60:3000"
APL="File"
APL_URL="./temp/apl.json"
APL_URL="apl.json"
LOG_LEVEL="DEBUG"
## THESE VARIABLES ARE FOR SITEMAP-GENERATOR APP

9
apl.json Normal file
View file

@ -0,0 +1,9 @@
{
"http://localhost:8000/graphql/": {
"domain": "http://10.100.110.60:3000",
"token": "NhJa5QG3J4UGbXQSiYF9L7HySMCKqA",
"saleorApiUrl": "http://localhost:8000/graphql/",
"appId": "saleor-app-template-ui",
"jwks": null
}
}

View file

@ -41,17 +41,7 @@ envy = { workspace = true }
cynic = { workspace = true, features = ["http-surf"], optional = true }
surf = { workspace = true, optional = true }
[target.'cfg(target_family = "unix")'.dependencies]
saleor-app-sdk = { workspace = true, optional = true, features = [
"file_apl",
"redis_apl",
"tracing",
"middleware",
"webhook_utils",
] }
[target.'cfg(target_family = "wasm")'.dependencies]
saleor-app-sdk = { workspace = true, optional = true, features = ["bridge"] }
saleor-app-sdk = { workspace = true, optional = true }
[build-dependencies]
cynic-codegen = { workspace = true, optional = true }
@ -79,6 +69,7 @@ ssr = [
"leptos/ssr",
"leptos_meta/ssr",
"leptos_router/ssr",
"saleor-app-sdk/bridge",
]
hydrate = [
"leptos/hydrate",

View file

@ -55,7 +55,11 @@ async fn main() -> Result<(), std::io::Error> {
let saleor_app = SaleorApp::new(&config).unwrap();
let app_manifest = AppManifestBuilder::new(&config, cargo_info!())
.add_permission(AppPermission::ManageProducts)
.add_permissions(vec![
AppPermission::ManageProducts,
AppPermission::ManageOrders,
AppPermission::ManageProductTypesAndAttributes,
])
.add_extension(
AppExtensionBuilder::new()
.set_url("/extensions/order_to_pdf")
@ -69,7 +73,8 @@ async fn main() -> Result<(), std::io::Error> {
.set_target(AppExtensionTarget::Popup)
.build(),
)
.build();
.build()
.expect("Manifest has invalid parameters");
let app_state = AppState {
manifest: app_manifest,
@ -89,11 +94,11 @@ async fn main() -> Result<(), std::io::Error> {
.fallback(file_and_error_handler)
.route(
"/api/webhooks",
post(webhooks).route_layer(middleware::from_fn(webhook_signature_verifier)),
post(webhooks)//.route_layer(middleware::from_fn(webhook_signature_verifier)),
)
.route(
"/api/register",
post(register).route_layer(middleware::from_fn(webhook_signature_verifier)),
post(register)//.route_layer(middleware::from_fn(webhook_signature_verifier)),
)
.route("/api/manifest", get(manifest))
.with_state(app_state.clone());

View file

@ -5,9 +5,9 @@ use axum::{
http::{HeaderMap, StatusCode},
};
use saleor_app_sdk::{AuthData, AuthToken};
use tracing::{debug, info};
use tracing::{debug, error, info};
use crate::{app::AppState, error_template::AxumError};
use crate::{app::AppState, error_template::{self, AxumError}};
pub async fn register(
@ -34,7 +34,10 @@ pub async fn register(
app_id: state.manifest.id,
saleor_api_url: saleor_api_url.clone(),
};
app.apl.set(auth_data).await?;
if let Err(e) = app.apl.set(auth_data).await{
error!("{:?}",e);
return Err(error_template::AxumError::Anyhow(e))
};
info!("registered app for{:?}", &saleor_api_url);
Ok(StatusCode::OK)

View file

@ -1,7 +1,46 @@
use leptos::*;
use leptos_dom::logging::{console_error, console_log};
#[component]
pub fn Pdf() -> impl IntoView {
let bridge = create_effect(|_| {
use saleor_app_sdk::bridge::AppBridge;
use saleor_app_sdk::{
bridge::action::{Action, PayloadRequestPermissions},
manifest::AppPermission,
};
match AppBridge::new(true) {
Ok(mut app_bridge) => {
console_log("App Bridge connected");
let cb_handle = app_bridge
.listen_to_events(|event| match event {
Ok(event) => console_log(&format!("order_to_pdf: {:?}", event)),
Err(e) => console_error(&format!("order_to_pdf: {:?}", e)),
})
.unwrap();
//TODO: imagine leaking memory on purpose xd
cb_handle.forget();
_ = app_bridge.dispatch_event(Action::RequestPermissions(
PayloadRequestPermissions {
permissions: vec![AppPermission::ManageOrders],
redirect_path: "".to_owned(),
},
));
}
Err(e) => console_error(&format!("{:?}", e)),
}; // let mut bridge = bridge.unwrap();
// match bridge.dispatch_event(Event::Handshake(PayloadHanshake::default())) {
// Ok(ev) => {
// console_log(&format! {"{:?}",ev});
// }
// Err(e) => {
// console_log(&format! {"{:?}",e});
// }
// };
// async fn temp(){}
// temp()
});
view! {
<h1>Yello!</h1>
<p class="italic text-lg">r#"Loading AppBridge, please wait..."#</p>

View file

@ -76,7 +76,9 @@ async fn main() -> anyhow::Result<()> {
.build(),
)
.add_permission(AppPermission::ManageProducts)
.build();
.build()
.expect("Manifest has invalid parameters");
let app_state = AppState {
manifest: app_manifest,
config: config.clone(),

45
sdk/src/bridge/action.rs Normal file
View file

@ -0,0 +1,45 @@
use serde::{Deserialize, Serialize};
use crate::manifest::AppPermission;
#[derive(Deserialize, Serialize, Debug)]
#[serde(tag = "type", content = "payload")]
#[serde(rename_all = "camelCase")]
pub enum Action {
Redirect(PayloadRedirect),
RequestPermissions(PayloadRequestPermissions),
NotifyReady(String),
Notification(PayloadNotification),
}
#[derive(Deserialize, Serialize, Debug, Default)]
#[serde(rename_all = "camelCase")]
pub struct PayloadRequestPermissions {
pub permissions: Vec<AppPermission>,
pub redirect_path: String,
}
#[derive(Deserialize, Serialize, Debug, Default)]
#[serde(rename_all = "camelCase")]
pub struct PayloadNotification {
pub status: Option<NotificationStatus>,
pub title: Option<String>,
pub text: Option<String>,
pub api_message: Option<String>,
}
#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "lowercase")]
pub enum NotificationStatus {
Info,
Success,
Warning,
Error,
}
#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct PayloadRedirect {
pub to: String,
pub new_context: Option<bool>,
}

View file

@ -1,4 +1,4 @@
use crate::locales::LocaleCode;
use crate::{locales::LocaleCode, manifest::AppPermission};
use super::ThemeType;
// use bus::{Bus, BusReader};
@ -72,14 +72,38 @@ pub enum Event {
Theme(PayloadTheme),
LocaleChanged(PayloadLocaleChanged),
TokenRefreshed(PayloadTokenRefreshed),
NotifyReady(String),
}
#[derive(Deserialize, Serialize, Debug, Default)]
#[serde(rename_all = "camelCase")]
pub struct PayloadRequestPermissions {
pub permissions: Vec<AppPermission>,
pub redirect_path: String,
}
#[derive(Deserialize, Serialize, Debug, Default)]
#[serde(rename_all = "camelCase")]
pub struct PayloadNotification {
pub status: Option<NotificationStatus>,
pub title: Option<String>,
pub text: Option<String>,
pub api_message: Option<String>,
}
#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "lowercase")]
pub enum NotificationStatus {
Info,
Success,
Warning,
Error,
}
#[derive(Deserialize, Serialize, Debug, Default)]
#[serde(rename_all = "camelCase")]
pub struct PayloadHanshake {
pub token: String,
pub version: String,
pub version: f32,
pub saleor_version: Option<String>,
pub dashboard_version: Option<String>,
}
@ -87,23 +111,24 @@ pub struct PayloadHanshake {
#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct PayloadResponse {
pub action_id: String,
pub action_id: Option<String>,
pub ok: bool,
}
#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct PayloadRedirect {
pub path: String,
pub to: String,
pub new_context: Option<bool>,
}
#[derive(Deserialize, Serialize, Debug)]
#[derive(Deserialize, Serialize, Debug, Default)]
#[serde(rename_all = "camelCase")]
pub struct PayloadTheme {
pub theme: ThemeType,
}
#[derive(Deserialize, Serialize, Debug)]
#[derive(Deserialize, Serialize, Debug, Default)]
#[serde(rename_all = "camelCase")]
pub struct PayloadLocaleChanged {
pub locale: LocaleCode,

View file

@ -1,7 +1,9 @@
pub mod action;
pub mod event;
use std::str::FromStr;
use action::Action;
use serde::{Deserialize, Serialize};
use strum_macros::{EnumString, IntoStaticStr};
use wasm_bindgen::{closure::Closure, JsCast, JsValue};
@ -9,7 +11,7 @@ use wasm_bindgen::{closure::Closure, JsCast, JsValue};
use crate::{locales::LocaleCode, manifest::AppPermission};
use self::event::Event;
use web_sys::console;
use web_sys::{console, js_sys::JSON};
pub struct AppBridge {
pub state: AppBridgeState,
@ -19,7 +21,7 @@ pub struct AppBridge {
* Should automatically emit Actions.NotifyReady.
* If app loading time is longer, this can be disabled and sent manually.
*/
auto_notify_ready: Option<bool>,
auto_notify_ready: bool,
}
#[derive(Default, Debug)]
@ -120,7 +122,7 @@ impl Default for ThemeType {
}
impl AppBridge {
pub fn new(auto_notify_ready: Option<bool>) -> Result<Self, AppBridgeError> {
pub fn new(auto_notify_ready: bool) -> Result<Self, AppBridgeError> {
// debug!("creating app bridge");
console::log_1(&"creating app bridge".into());
if web_sys::Window::is_type_of(&JsValue::from_str("undefined")) {
@ -148,39 +150,61 @@ impl AppBridge {
},
referer_origin: referrer,
};
if bridge.auto_notify_ready.unwrap_or(false) {
if bridge.auto_notify_ready {
bridge.notify_ready()?;
}
Ok(bridge)
}
pub fn listen_to_events(&mut self) -> Result<&mut Self, AppBridgeError> {
/*
* make sure to keep the returned closure handle safe, once it deallocs events will no longer
* trigger
*/
pub fn listen_to_events(
&mut self,
mut on_event: impl FnMut(Result<Event, serde_wasm_bindgen::Error>) + 'static,
) -> Result<Closure<dyn FnMut(JsValue)>, AppBridgeError> {
let window = web_sys::window().ok_or(AppBridgeError::WindowIsUndefined)?;
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);
}) as Box<dyn FnMut(_)>);
let cb = Closure::wrap(Box::new(move |e: JsValue| {
web_sys::console::log_1(
&format!(
"sdk::bridge::listen_to_events: {:?}",
&JSON::stringify(&web_sys::js_sys::Reflect::get(&e, &"data".into()).unwrap())
.unwrap()
)
.into(),
);
let event_data: Result<Event, _> = serde_wasm_bindgen::from_value(
web_sys::js_sys::Reflect::get(&e, &"data".into())
.expect("Closure should've received object with .data property, but didn't, saleor plz fix?"),
);
// web_sys::console::log_1(&format!("{:?}", &event_data).into());
on_event(event_data);
}) as Box<dyn FnMut(JsValue)>);
window
.add_event_listener_with_callback("message", &cb.as_ref().unchecked_ref())
.map_err(|e| AppBridgeError::JsValue(e))?;
Ok(self)
Ok(cb)
}
pub fn dispatch_event(&mut self, event: Event) -> Result<Event, AppBridgeError> {
pub fn dispatch_event(&mut self, action: Action) -> Result<(), AppBridgeError> {
let window = web_sys::window().ok_or(AppBridgeError::WindowIsUndefined)?;
let parent = match window.parent() {
Ok(p) => p.ok_or(AppBridgeError::WindowParentIsUndefined)?,
Err(e) => return Err(AppBridgeError::JsValue(e)),
};
let message = JsValue::from_str(&serde_json::to_string(&event)?);
// let message = JsValue::from(&event);
let message = serde_wasm_bindgen::to_value(&action)?;
web_sys::console::log_1(&message);
parent
.post_message(&message, "*")
.map_err(|e| AppBridgeError::JsValue(e))?;
todo!()
Ok(())
}
pub fn notify_ready(&mut self) -> Result<&mut Self, AppBridgeError> {
self.dispatch_event(Event::NotifyReady("{}".to_owned()))?;
self.dispatch_event(Action::NotifyReady("{}".to_owned()))?;
Ok(self)
}
}
@ -201,6 +225,6 @@ pub enum AppBridgeError {
#[derive(Serialize, Deserialize, Debug)]
pub struct SaleorIframeEvent {
origin: String,
data: Event,
pub origin: String,
pub data: Event,
}

View file

@ -1,9 +1,10 @@
use serde::{Deserialize, Serialize};
pub mod extension;
use thiserror::Error;
use crate::{config::Config, webhooks::WebhookManifest};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum AppPermission {
ManageUsers,
@ -213,10 +214,27 @@ impl AppManifestBuilder {
}
self
}
pub fn build(self) -> AppManifest {
self.manifest
pub fn build(self) -> Result<AppManifest, AppManifestBuilderError> {
if let Some(ext) = self
.manifest
.extensions
.as_ref()
.map(|exts| exts.iter().flat_map(|e| &e.permissions).collect::<Vec<_>>())
{
match ext.iter().all(|e| self.manifest.permissions.contains(e)) {
true => Ok(self.manifest),
false => Err(AppManifestBuilderError::MismatchedPermissions),
}
} else {
Ok(self.manifest)
}
}
}
#[derive(Error, Debug)]
pub enum AppManifestBuilderError {
#[error("manifest.permissions doesn't list all permissions needed in manifest.extensions.permissions")]
MismatchedPermissions,
}
pub struct CargoInfo {
pub name: String,
pub description: String,

View file

@ -86,14 +86,15 @@ async fn main() -> anyhow::Result<()> {
AppPermission::ManageCheckouts,
AppPermission::HandleCheckouts,
])
.build();
.build()
.expect("Manifest has invalid parameters");
let app_state = AppState {
active_payment_methods: get_active_payment_methods_from_env()?,
manifest: app_manifest,
config: config.clone(),
saleor_app: Arc::new(Mutex::new(saleor_app)),
cod_extra_price_as_product_slug: std::env::var("COD_EXTRA_PRICE_AS_PRODUCT_SLUG").ok()
cod_extra_price_as_product_slug: std::env::var("COD_EXTRA_PRICE_AS_PRODUCT_SLUG").ok(),
};
let app = create_routes(app_state);

View file

@ -84,7 +84,9 @@ async fn create_app(config: &Config, sitemap_config: SitemapConfig) -> Router {
])
.build(),
)
.build();
.build()
.expect("Manifest has invalid parameters");
debug!("Created AppManifest...");
let (sender, receiver) = tokio::sync::mpsc::channel(100);