1pub mod delegate;
17pub mod error;
18pub mod grant_store;
19pub mod manifest;
20pub mod mime;
21pub mod permissions;
22pub mod preflight;
23pub mod security;
24pub mod serving;
25pub mod state;
26
27pub use delegate::{ChainRpcRequest, ProductViewDelegate};
28pub use error::ProductError;
29pub use grant_store::{InMemorySandboxGrantStore, SandboxGrantStore, SandboxGrantStoreError};
30pub use manifest::{
31 ConsentChainTarget, ConsentSandboxRequest, HostChainRequirements, HostChainTarget,
32 HostManifest, HostSandboxRequest, HostSandboxRequirements, ManifestError,
33 PreparedProductBundle, PresentationConsentSummary, ProductBundleError, ProductConsentSummary,
34 ProductManifest, ProductMetadata, ProductModality, ProductSource, PublisherMetadata,
35 SandboxRequestKind, VerifiedConsentSummary,
36};
37pub use permissions::{
38 EffectiveSandboxPermissions, GrantedSandboxRequest, SandboxGrant, SandboxPermissionResolution,
39};
40pub use preflight::{
41 ConsentDenyBehavior, ProductConsentDecision, ProductPreflightContext,
42 ProductPreflightDenyReason, ProductPreflightError, ProductPreflightOutcome,
43 ProductPreflightPrompt,
44};
45pub use security::{csp_for_scheme, inject_into_head};
46pub use serving::serve_asset;
47pub use state::{ProductAssets, ProductLoadState};
48
49#[cfg(test)]
50mod tests {
51 use super::*;
52
53 struct MockDelegate {
55 last_product_id: std::cell::RefCell<String>,
56 }
57
58 impl MockDelegate {
59 fn new() -> Self {
60 Self {
61 last_product_id: std::cell::RefCell::new(String::new()),
62 }
63 }
64 }
65
66 impl ProductViewDelegate for MockDelegate {
67 fn send_response(&self, product_id: &str, _bytes: Vec<u8>) -> Result<(), ProductError> {
68 *self.last_product_id.borrow_mut() = product_id.to_string();
69 Ok(())
70 }
71
72 fn needs_sign(
73 &self,
74 product_id: &str,
75 _request_id: &str,
76 _request_tag: u8,
77 _public_key: Vec<u8>,
78 _payload: Vec<u8>,
79 ) -> Result<(), ProductError> {
80 *self.last_product_id.borrow_mut() = product_id.to_string();
81 Ok(())
82 }
83
84 fn needs_chain_query(
85 &self,
86 product_id: &str,
87 _request_id: &str,
88 _method: &str,
89 _params_json: &str,
90 ) -> Result<(), ProductError> {
91 *self.last_product_id.borrow_mut() = product_id.to_string();
92 Ok(())
93 }
94
95 fn needs_chain_subscription(
96 &self,
97 product_id: &str,
98 _request_id: &str,
99 _method: &str,
100 _params_json: &str,
101 ) -> Result<(), ProductError> {
102 *self.last_product_id.borrow_mut() = product_id.to_string();
103 Ok(())
104 }
105
106 fn needs_chain_follow(
107 &self,
108 product_id: &str,
109 _request_id: &str,
110 _genesis_hash: Vec<u8>,
111 _with_runtime: bool,
112 ) -> Result<(), ProductError> {
113 *self.last_product_id.borrow_mut() = product_id.to_string();
114 Ok(())
115 }
116
117 fn needs_chain_rpc(
118 &self,
119 product_id: &str,
120 _request_id: &str,
121 _req: ChainRpcRequest,
122 ) -> Result<(), ProductError> {
123 *self.last_product_id.borrow_mut() = product_id.to_string();
124 Ok(())
125 }
126
127 fn needs_navigate(
128 &self,
129 product_id: &str,
130 _request_id: &str,
131 _url: &str,
132 ) -> Result<(), ProductError> {
133 *self.last_product_id.borrow_mut() = product_id.to_string();
134 Ok(())
135 }
136 }
137
138 #[test]
139 fn test_mock_delegate_send_response() {
140 let d = MockDelegate::new();
141 d.send_response("app.dot", vec![1, 2, 3]).unwrap();
142 assert_eq!(*d.last_product_id.borrow(), "app.dot");
143 }
144
145 #[test]
146 fn test_mock_delegate_needs_sign() {
147 let d = MockDelegate::new();
148 d.needs_sign("app.dot", "req-1", 42, vec![0; 32], vec![0; 64])
149 .unwrap();
150 assert_eq!(*d.last_product_id.borrow(), "app.dot");
151 }
152
153 #[test]
154 fn test_mock_delegate_needs_chain_rpc() {
155 let d = MockDelegate::new();
156 let req = ChainRpcRequest {
157 request_tag: 1,
158 genesis_hash: vec![0; 32],
159 method: "chainHead_v1_header".into(),
160 params_json: "[]".into(),
161 follow_sub_id: Some("sub-1".into()),
162 };
163 d.needs_chain_rpc("app.dot", "req-2", req.clone()).unwrap();
164 assert_eq!(*d.last_product_id.borrow(), "app.dot");
165 assert_eq!(req.method, "chainHead_v1_header");
167 }
168
169 #[test]
170 fn test_mock_delegate_needs_navigate() {
171 let d = MockDelegate::new();
172 d.needs_navigate("app.dot", "req-3", "https://polkadot.network")
173 .unwrap();
174 assert_eq!(*d.last_product_id.borrow(), "app.dot");
175 }
176
177 #[test]
178 fn test_delegate_returns_error() {
179 struct FailingDelegate;
180 impl ProductViewDelegate for FailingDelegate {
181 fn send_response(&self, _: &str, _: Vec<u8>) -> Result<(), ProductError> {
182 Err(ProductError::UnknownProduct("nope".into()))
183 }
184 fn needs_sign(
185 &self,
186 _: &str,
187 _: &str,
188 _: u8,
189 _: Vec<u8>,
190 _: Vec<u8>,
191 ) -> Result<(), ProductError> {
192 Err(ProductError::SignFailed("locked".into()))
193 }
194 fn needs_chain_query(
195 &self,
196 _: &str,
197 _: &str,
198 _: &str,
199 _: &str,
200 ) -> Result<(), ProductError> {
201 Err(ProductError::ChainRpcFailed("offline".into()))
202 }
203 fn needs_chain_subscription(
204 &self,
205 _: &str,
206 _: &str,
207 _: &str,
208 _: &str,
209 ) -> Result<(), ProductError> {
210 Err(ProductError::ChainSubscriptionFailed("no chain".into()))
211 }
212 fn needs_chain_follow(
213 &self,
214 _: &str,
215 _: &str,
216 _: Vec<u8>,
217 _: bool,
218 ) -> Result<(), ProductError> {
219 Err(ProductError::ChainFollowFailed("gone".into()))
220 }
221 fn needs_chain_rpc(
222 &self,
223 _: &str,
224 _: &str,
225 _: ChainRpcRequest,
226 ) -> Result<(), ProductError> {
227 Err(ProductError::ChainRpcFailed("timeout".into()))
228 }
229 fn needs_navigate(&self, _: &str, _: &str, _: &str) -> Result<(), ProductError> {
230 Err(ProductError::NavigateRejected("blocked".into()))
231 }
232 }
233
234 let d = FailingDelegate;
235 assert!(d.send_response("x", vec![]).is_err());
236 assert!(d.needs_sign("x", "r", 0, vec![], vec![]).is_err());
237 assert!(d.needs_chain_query("x", "r", "m", "[]").is_err());
238 assert!(d.needs_chain_subscription("x", "r", "m", "[]").is_err());
239 assert!(d.needs_chain_follow("x", "r", vec![], false).is_err());
240 assert!(d
241 .needs_chain_rpc(
242 "x",
243 "r",
244 ChainRpcRequest {
245 request_tag: 0,
246 genesis_hash: vec![],
247 method: String::new(),
248 params_json: String::new(),
249 follow_sub_id: None,
250 }
251 )
252 .is_err());
253 assert!(d.needs_navigate("x", "r", "url").is_err());
254 }
255}