Bridge init
This commit is contained in:
parent
305a58e8b5
commit
3bf66a32c1
6 changed files with 352 additions and 1 deletions
|
@ -14,6 +14,7 @@ license = "MIT OR Apache-2.0"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
|
thiserror = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
envy = { workspace = true }
|
envy = { workspace = true }
|
||||||
dotenvy = { workspace = true }
|
dotenvy = { workspace = true }
|
||||||
|
@ -44,8 +45,24 @@ tracing-subscriber = { workspace = true, optional = true }
|
||||||
|
|
||||||
## Needed for webhooks
|
## Needed for webhooks
|
||||||
|
|
||||||
|
## 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 }
|
||||||
|
|
||||||
|
[dependencies.web-sys]
|
||||||
|
workspace = true
|
||||||
|
features = [
|
||||||
|
"Window",
|
||||||
|
"Document",
|
||||||
|
"Url",
|
||||||
|
"UrlSearchParams",
|
||||||
|
"EventListener",
|
||||||
|
"EventTarget",
|
||||||
|
"console",
|
||||||
|
]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
# default = []
|
|
||||||
default = ["middleware", "redis_apl", "webhook_utils", "tracing"]
|
default = ["middleware", "redis_apl", "webhook_utils", "tracing"]
|
||||||
middleware = [
|
middleware = [
|
||||||
"dep:axum",
|
"dep:axum",
|
||||||
|
@ -57,3 +74,4 @@ middleware = [
|
||||||
redis_apl = ["dep:redis"]
|
redis_apl = ["dep:redis"]
|
||||||
webhook_utils = ["dep:http"]
|
webhook_utils = ["dep:http"]
|
||||||
tracing = ["dep:tracing", "dep:tracing-subscriber"]
|
tracing = ["dep:tracing", "dep:tracing-subscriber"]
|
||||||
|
bridge = ["dep:wasm-bindgen", "dep:bus", "dep:serde-wasm-bindgen"]
|
||||||
|
|
5
sdk/rust-toolchain.toml
Normal file
5
sdk/rust-toolchain.toml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
[toolchain]
|
||||||
|
# channel = "nightly-2024-04-25"
|
||||||
|
## Toggle to this one for sdk releases
|
||||||
|
channel = "stable"
|
||||||
|
targets = ["x86_64-unknown-linux-gnu", "wasm32-unknown-unknown"]
|
113
sdk/src/bridge/event.rs
Normal file
113
sdk/src/bridge/event.rs
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
use crate::locales::LocaleCode;
|
||||||
|
|
||||||
|
use super::ThemeType;
|
||||||
|
use bus::{Bus, BusReader};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use strum_macros::EnumIter;
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(EnumIter, Debug)]
|
||||||
|
pub enum EventType {
|
||||||
|
Handshake,
|
||||||
|
Response,
|
||||||
|
Redirect,
|
||||||
|
Theme,
|
||||||
|
LocaleChanged,
|
||||||
|
TokenRefreshed,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
#[serde(tag = "type", content = "payload")]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub enum Event {
|
||||||
|
Handshake(PayloadHanshake),
|
||||||
|
Response(PayloadResponse),
|
||||||
|
Redirect(PayloadRedirect),
|
||||||
|
Theme(PayloadTheme),
|
||||||
|
LocaleChanged(PayloadLocaleChanged),
|
||||||
|
TokenRefreshed(PayloadTokenRefreshed),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct PayloadHanshake {
|
||||||
|
pub token: String,
|
||||||
|
pub version: String,
|
||||||
|
pub saleor_version: Option<String>,
|
||||||
|
pub dashboard_version: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct PayloadResponse {
|
||||||
|
pub action_id: String,
|
||||||
|
pub ok: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct PayloadRedirect {
|
||||||
|
pub path: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct PayloadTheme {
|
||||||
|
pub theme: ThemeType,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct PayloadLocaleChanged {
|
||||||
|
pub locale: LocaleCode,
|
||||||
|
}
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct PayloadTokenRefreshed {
|
||||||
|
pub token: String,
|
||||||
|
}
|
207
sdk/src/bridge/mod.rs
Normal file
207
sdk/src/bridge/mod.rs
Normal file
|
@ -0,0 +1,207 @@
|
||||||
|
pub mod event;
|
||||||
|
|
||||||
|
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 web_sys::console;
|
||||||
|
|
||||||
|
pub struct AppBridge {
|
||||||
|
pub state: AppBridgeState,
|
||||||
|
pub referer_origin: Option<String>,
|
||||||
|
pub event_channels: EventChannels,
|
||||||
|
/**
|
||||||
|
* Should automatically emit Actions.NotifyReady.
|
||||||
|
* If app loading time is longer, this can be disabled and sent manually.
|
||||||
|
*/
|
||||||
|
auto_notify_ready: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
pub struct AppBridgeState {
|
||||||
|
pub token: Option<String>,
|
||||||
|
pub id: String,
|
||||||
|
pub ready: bool,
|
||||||
|
pub domain: String,
|
||||||
|
pub path: String,
|
||||||
|
pub theme: ThemeType,
|
||||||
|
pub locale: LocaleCode,
|
||||||
|
pub saleor_api_url: String,
|
||||||
|
/**pub
|
||||||
|
* Versions of Saleor that app is mounted. Passed from the Dashboard.pub
|
||||||
|
* Works form Saleor 3.15pub
|
||||||
|
*/
|
||||||
|
pub saleor_version: Option<String>,
|
||||||
|
pub dashboard_version: Option<String>,
|
||||||
|
pub user: Option<AppBridgeUser>,
|
||||||
|
pub app_permissions: Option<Vec<AppPermission>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppBridgeState {
|
||||||
|
pub fn from_window() -> Result<Self, JsValue> {
|
||||||
|
let mut state = AppBridgeState::default();
|
||||||
|
let window = web_sys::window().ok_or(JsValue::from_str("Missing window"))?;
|
||||||
|
let href = window.location().href()?;
|
||||||
|
let url = web_sys::Url::new(&href)?;
|
||||||
|
|
||||||
|
let saleor_api_url = url
|
||||||
|
.search_params()
|
||||||
|
.get(AppIframeParams::SaleorApiUrl.into());
|
||||||
|
let id = url.search_params().get(AppIframeParams::Id.into());
|
||||||
|
let theme = url.search_params().get(AppIframeParams::Theme.into());
|
||||||
|
let domain = url.search_params().get(AppIframeParams::Domain.into());
|
||||||
|
let locale = url.search_params().get(AppIframeParams::Locale.into());
|
||||||
|
|
||||||
|
if let Some(id) = id {
|
||||||
|
state.id = id
|
||||||
|
}
|
||||||
|
if let Some(saleor_api_url) = saleor_api_url {
|
||||||
|
state.saleor_api_url = saleor_api_url
|
||||||
|
}
|
||||||
|
if let Some(theme) = theme {
|
||||||
|
if let Ok(theme_type) = ThemeType::from_str(&theme) {
|
||||||
|
state.theme = theme_type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(domain) = domain {
|
||||||
|
state.domain = domain
|
||||||
|
}
|
||||||
|
if let Some(locale) = locale {
|
||||||
|
if let Ok(loc) = LocaleCode::from_str(&locale) {
|
||||||
|
state.locale = loc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
debug!("state from window: {:?}", &state);
|
||||||
|
console::log_1(&format!("state from window: {:?}", &state).into());
|
||||||
|
Ok(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
pub struct AppBridgeUser {
|
||||||
|
/**
|
||||||
|
* Original permissions of the user that is using the app.
|
||||||
|
* *Not* the same permissions as the app itself.
|
||||||
|
*
|
||||||
|
* Can be used by app to check if user is authorized to perform
|
||||||
|
* domain specific actions
|
||||||
|
*/
|
||||||
|
pub permissions: Vec<AppPermission>,
|
||||||
|
pub email: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, EnumString, IntoStaticStr)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[strum(serialize_all = "camelCase")]
|
||||||
|
pub enum AppIframeParams {
|
||||||
|
Id,
|
||||||
|
Theme,
|
||||||
|
Domain,
|
||||||
|
SaleorApiUrl,
|
||||||
|
Locale,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, EnumString)]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
|
pub enum ThemeType {
|
||||||
|
Light,
|
||||||
|
Dark,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ThemeType {
|
||||||
|
fn default() -> Self {
|
||||||
|
ThemeType::Light
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppBridge {
|
||||||
|
pub fn new(auto_notify_ready: Option<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")) {
|
||||||
|
error!("Window is undefined");
|
||||||
|
console::log_1(&"Window is undefined".into());
|
||||||
|
return Err(AppBridgeError::WindowIsUndefined);
|
||||||
|
}
|
||||||
|
let referrer = web_sys::window().and_then(|w| {
|
||||||
|
w.document().and_then(|d| {
|
||||||
|
web_sys::Url::new(&d.referrer())
|
||||||
|
.ok()
|
||||||
|
.and_then(|u| Some(u.origin()))
|
||||||
|
})
|
||||||
|
});
|
||||||
|
if referrer.is_none() {
|
||||||
|
warn!("Referrer origin is none");
|
||||||
|
console::log_1(&"Referrer origin is none".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut bridge = Self {
|
||||||
|
auto_notify_ready,
|
||||||
|
state: match AppBridgeState::from_window() {
|
||||||
|
Ok(s) => s,
|
||||||
|
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()?;
|
||||||
|
}
|
||||||
|
Ok(bridge)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn listen_to_events(&mut self) -> Result<&mut Self, 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(_)>);
|
||||||
|
window
|
||||||
|
.add_event_listener_with_callback("message", &cb.as_ref().unchecked_ref())
|
||||||
|
.map_err(|e| AppBridgeError::JsValue(e))?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dispatch_event(&mut self, event: Event) -> Result<Event, 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)?);
|
||||||
|
parent
|
||||||
|
.post_message(&message, "*")
|
||||||
|
.map_err(|e| AppBridgeError::JsValue(e))?;
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
pub fn notify_ready(&mut self) -> Result<&mut Self, AppBridgeError> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
pub enum AppBridgeError {
|
||||||
|
#[error("failed serializing event from window")]
|
||||||
|
SerdeError(#[from] serde_wasm_bindgen::Error),
|
||||||
|
#[error("Something went wrong with serde_json::to_string(&event)")]
|
||||||
|
FailedPayloadToJsonStringification(#[from] serde_json::Error),
|
||||||
|
#[error("Windows parent is missing, meaning the app is probably not embedded in Iframe")]
|
||||||
|
WindowParentIsUndefined,
|
||||||
|
#[error("Window is typeof undefined. Probably means AppBridge::new() is being called outside of a browser")]
|
||||||
|
WindowIsUndefined,
|
||||||
|
#[error("JS error")]
|
||||||
|
JsValue(JsValue),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct SaleorIframeEvent {
|
||||||
|
origin: String,
|
||||||
|
data: Event,
|
||||||
|
}
|
|
@ -1,4 +1,6 @@
|
||||||
pub mod apl;
|
pub mod apl;
|
||||||
|
#[cfg(feature = "bridge")]
|
||||||
|
pub mod bridge;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod headers;
|
pub mod headers;
|
||||||
pub mod locales;
|
pub mod locales;
|
||||||
|
|
|
@ -46,3 +46,9 @@ pub enum LocaleCode {
|
||||||
ZhHans,
|
ZhHans,
|
||||||
ZhHant,
|
ZhHant,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for LocaleCode {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::En
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue