///|
pub struct RuntimeLaunchManifest {
backend : String
platform : String
asset_protocol : String
devtools : Bool
bridge_url : String
webviews : Array[RuntimeWebViewBoot]
protocol_mappings : Array[RuntimeProtocolBinding]
virtual_files : Array[RuntimeVirtualAsset]
local_services : Array[LocalService]
filesystem_scopes : Array[FileSystemScope]
capabilities : Array[RuntimeCapabilityGrant]
startup_actions : Array[RuntimeAction]
lifecycle_hooks : Array[RuntimeLifecycleHook]
command_manifest : CommandManifest
permission_manifest : PermissionManifest
declared_routes : Array[String]
command_routes : Array[String]
registered_routes : Array[String]
} derive(Debug, Eq)
///|
pub struct RuntimeWebViewBoot {
label : String
title : String
url : String
width : Int
height : Int
resizable : Bool
title_bar : TitleBarStyle
devtools : Bool
asset_protocol : String
bridge_global_name : String
native_hook : String
event_dispatch_hook : String
allowed_routes : Array[String]
initialization_scripts : Array[String]
} derive(Debug, Eq)
///|
pub struct RuntimeProtocolBinding {
window_label : String
scheme : String
root : String
} derive(Debug, Eq)
///|
pub struct RuntimeVirtualAsset {
window_label : String
path : String
mime_type : String
content : String
} derive(Debug, Eq)
///|
pub struct RuntimeCapabilityGrant {
name : String
windows : Array[String]
origins : Array[String]
platforms : Array[String]
permissions : Array[String]
operation_scopes : Array[OperationScope]
} derive(Debug, Eq)
///|
pub(all) enum RuntimeLaunchAssetBody {
LaunchVirtualContent(String)
LaunchLocalFile(String)
LaunchPackagedFile(String)
} derive(Debug, Eq)
///|
pub struct RuntimeLaunchAsset {
url : String
mime_type : String
body : RuntimeLaunchAssetBody
} derive(Debug, Eq)
///|
struct ParsedLaunchAssetUrl {
scheme : String
authority : String
path : String
} derive(Debug, Eq)
///|
pub fn RuntimePlan::launch_manifest(
self : RuntimePlan,
registered_routes? : Array[String] = [],
restrict_registered? : Bool = false,
) -> Result[RuntimeLaunchManifest, Array[String]] {
let webviews : Array[RuntimeWebViewBoot] = []
let protocol_mappings : Array[RuntimeProtocolBinding] = []
let virtual_files : Array[RuntimeVirtualAsset] = []
let local_services : Array[LocalService] = []
let startup_actions : Array[RuntimeAction] = []
let lifecycle_hooks : Array[RuntimeLifecycleHook] = []
let problems : Array[String] = []
for window in self.windows() {
match
self.bridge_script_with_registered_routes(
config=BridgeConfig::new(window_label=window.label()),
registered_routes~,
restrict_registered~,
) {
Ok(bridge) =>
webviews.push(RuntimeWebViewBoot::from_window(self, window, bridge))
Err(bridge_problems) =>
for problem in bridge_problems {
problems.push(problem)
}
}
for mapping in window.source().protocol_mappings() {
protocol_mappings.push(
RuntimeProtocolBinding::from_mapping(window.label(), mapping),
)
}
for file in window.source().virtual_files() {
virtual_files.push(RuntimeVirtualAsset::from_file(window.label(), file))
}
for service in window.source().local_services() {
local_services.push(service)
}
}
match self.startup_actions() {
Ok(actions) =>
for action in actions {
startup_actions.push(action)
}
Err(action_problems) =>
for problem in action_problems {
problems.push(problem)
}
}
match self.lifecycle_hooks() {
Ok(hooks) =>
for hook in hooks {
lifecycle_hooks.push(hook)
}
Err(hook_problems) =>
for problem in hook_problems {
problems.push(problem)
}
}
if problems.is_empty() {
let declared_routes = self.command_manifest().routes()
let command_manifest = if restrict_registered {
registered_command_manifest(self.command_manifest(), registered_routes)
} else {
self.command_manifest()
}
let permission_manifest = PermissionManifest::from_command_manifest(
command_manifest,
capabilities=self.capabilities(),
)
Ok({
backend: self.backend().name(),
platform: self.platform(),
asset_protocol: self.asset_protocol(),
devtools: self.devtools(),
bridge_url: "\{self.asset_protocol()}://runtime/bridge.js",
webviews,
protocol_mappings,
virtual_files,
local_services,
filesystem_scopes: self.file_system_scopes(),
capabilities: self
.capabilities()
.map(RuntimeCapabilityGrant::from_capability),
startup_actions,
lifecycle_hooks,
command_manifest,
permission_manifest,
declared_routes,
command_routes: command_manifest.routes(),
registered_routes: registered_routes.copy(),
})
} else {
Err(problems)
}
}
///|
fn registered_command_manifest(
manifest : CommandManifest,
registered_routes : Array[String],
) -> CommandManifest {
let entries : Array[CommandManifestEntry] = []
for entry in manifest.entries() {
if entry.mode().requires_launch_route() &&
registered_routes.contains(entry.route()) {
entries.push(entry)
}
}
CommandManifest::new(entries~)
}
///|
fn CommandMode::requires_launch_route(self : CommandMode) -> Bool {
match self {
Sync | Async | Stream => true
Event => false
}
}
///|
fn RuntimeWebViewBoot::from_window(
runtime : RuntimePlan,
window : ResolvedWindow,
bridge : BridgeScript,
) -> RuntimeWebViewBoot {
{
label: window.label(),
title: window.title(),
url: window.url(),
width: window.width(),
height: window.height(),
resizable: window.resizable(),
title_bar: window.title_bar(),
devtools: runtime.devtools(),
asset_protocol: runtime.asset_protocol(),
bridge_global_name: bridge.global_name(),
native_hook: bridge.native_hook(),
event_dispatch_hook: bridge.event_dispatch_hook(),
allowed_routes: bridge.routes(),
initialization_scripts: [bridge.source()],
}
}
///|
fn RuntimeProtocolBinding::from_mapping(
window_label : String,
mapping : ProtocolMapping,
) -> RuntimeProtocolBinding {
{ window_label, scheme: mapping.scheme(), root: mapping.root() }
}
///|
fn RuntimeVirtualAsset::from_file(
window_label : String,
file : VirtualFile,
) -> RuntimeVirtualAsset {
{
window_label,
path: file.path(),
mime_type: mime_type_for_path(file.path()),
content: file.content(),
}
}
///|
pub fn RuntimeLaunchManifest::backend(self : RuntimeLaunchManifest) -> String {
self.backend
}
///|
pub fn RuntimeLaunchManifest::platform(self : RuntimeLaunchManifest) -> String {
self.platform
}
///|
pub fn RuntimeLaunchManifest::asset_protocol(
self : RuntimeLaunchManifest,
) -> String {
self.asset_protocol
}
///|
pub fn RuntimeLaunchManifest::devtools(self : RuntimeLaunchManifest) -> Bool {
self.devtools
}
///|
pub fn RuntimeLaunchManifest::bridge_url(
self : RuntimeLaunchManifest,
) -> String {
self.bridge_url
}
///|
pub fn RuntimeLaunchManifest::webviews(
self : RuntimeLaunchManifest,
) -> Array[RuntimeWebViewBoot] {
self.webviews.copy()
}
///|
pub fn RuntimeLaunchManifest::protocol_mappings(
self : RuntimeLaunchManifest,
) -> Array[RuntimeProtocolBinding] {
self.protocol_mappings.copy()
}
///|
pub fn RuntimeLaunchManifest::virtual_files(
self : RuntimeLaunchManifest,
) -> Array[RuntimeVirtualAsset] {
self.virtual_files.copy()
}
///|
pub fn RuntimeLaunchManifest::local_services(
self : RuntimeLaunchManifest,
) -> Array[LocalService] {
self.local_services.copy()
}
///|
pub fn RuntimeLaunchManifest::file_system_scopes(
self : RuntimeLaunchManifest,
) -> Array[FileSystemScope] {
self.filesystem_scopes.copy()
}
///|
pub fn RuntimeLaunchManifest::capabilities(
self : RuntimeLaunchManifest,
) -> Array[RuntimeCapabilityGrant] {
self.capabilities.copy()
}
///|
pub fn RuntimeLaunchManifest::startup_actions(
self : RuntimeLaunchManifest,
) -> Array[RuntimeAction] {
self.startup_actions.copy()
}
///|
pub fn RuntimeLaunchManifest::lifecycle_hooks(
self : RuntimeLaunchManifest,
) -> Array[RuntimeLifecycleHook] {
self.lifecycle_hooks.copy()
}
///|
pub fn RuntimeLaunchManifest::command_routes(
self : RuntimeLaunchManifest,
) -> Array[String] {
self.command_routes.copy()
}
///|
pub fn RuntimeLaunchManifest::declared_routes(
self : RuntimeLaunchManifest,
) -> Array[String] {
self.declared_routes.copy()
}
///|
pub fn RuntimeLaunchManifest::command_manifest(
self : RuntimeLaunchManifest,
) -> CommandManifest {
self.command_manifest
}
///|
pub fn RuntimeLaunchManifest::permission_manifest(
self : RuntimeLaunchManifest,
) -> PermissionManifest {
self.permission_manifest
}
///|
pub fn RuntimeLaunchManifest::registered_routes(
self : RuntimeLaunchManifest,
) -> Array[String] {
self.registered_routes.copy()
}
///|
pub fn RuntimeLaunchManifest::to_json(self : RuntimeLaunchManifest) -> String {
[
"{",
"\"backend\":\{self.backend.json_string()},",
"\"platform\":\{self.platform.json_string()},",
"\"assetProtocol\":\{self.asset_protocol.json_string()},",
"\"devtools\":\{self.devtools.json_bool()},",
"\"bridgeUrl\":\{self.bridge_url.json_string()},",
"\"webviews\":[\{self.webviews.map(runtime_webview_boot_json).join(",")}],",
"\"protocolMappings\":[\{self.protocol_mappings.map(runtime_protocol_binding_json).join(",")}],",
"\"virtualFiles\":[\{self.virtual_files.map(runtime_virtual_asset_json).join(",")}],",
"\"localServices\":[\{self.local_services.map(fn(service) { service.to_json() }).join(",")}],",
"\"filesystemScopes\":[\{self.filesystem_scopes.map(fn(scope) { scope.to_json() }).join(",")}],",
"\"capabilities\":[\{self.capabilities.map(runtime_capability_grant_json).join(",")}],",
"\"startupActions\":[\{self.startup_actions.map(fn(action) { action.to_json() }).join(",")}],",
"\"lifecycleHooks\":[\{self.lifecycle_hooks.map(fn(hook) { hook.to_json() }).join(",")}],",
"\"commandManifest\":\{self.command_manifest.to_json()},",
"\"permissionManifest\":\{self.permission_manifest.to_json()},",
"\"declaredRoutes\":[\{self.declared_routes.map(fn(route) { route.json_string() }).join(",")}],",
"\"commandRoutes\":[\{self.command_routes.map(fn(route) { route.json_string() }).join(",")}],",
"\"registeredRoutes\":[\{self.registered_routes.map(fn(route) { route.json_string() }).join(",")}]",
"}",
].join("")
}
///|
pub fn RuntimeLaunchManifest::resolve_asset(
self : RuntimeLaunchManifest,
url : String,
) -> Result[RuntimeLaunchAsset, String] {
match parse_launch_asset_url(url) {
Err(problem) => Err(problem)
Ok(parsed) =>
if parsed.scheme != self.asset_protocol {
Err("asset scheme mismatch: \{parsed.scheme}")
} else if parsed.authority == "inline" || parsed.authority == "rabbita" {
self.resolve_virtual_asset(url, parsed)
} else if parsed.authority == "local" {
self.resolve_local_asset(url, parsed)
} else if parsed.authority == "packaged" {
self.resolve_packaged_asset(url, parsed)
} else if parsed.authority == "runtime" {
Err(
"runtime assets are injected through WebView initialization scripts",
)
} else {
Err("unknown asset authority: \{parsed.authority}")
}
}
}
///|
pub fn RuntimeWebViewBoot::label(self : RuntimeWebViewBoot) -> String {
self.label
}
///|
pub fn RuntimeWebViewBoot::title(self : RuntimeWebViewBoot) -> String {
self.title
}
///|
pub fn RuntimeWebViewBoot::url(self : RuntimeWebViewBoot) -> String {
self.url
}
///|
pub fn RuntimeWebViewBoot::width(self : RuntimeWebViewBoot) -> Int {
self.width
}
///|
pub fn RuntimeWebViewBoot::height(self : RuntimeWebViewBoot) -> Int {
self.height
}
///|
pub fn RuntimeWebViewBoot::resizable(self : RuntimeWebViewBoot) -> Bool {
self.resizable
}
///|
pub fn RuntimeWebViewBoot::title_bar(
self : RuntimeWebViewBoot,
) -> TitleBarStyle {
self.title_bar
}
///|
pub fn RuntimeWebViewBoot::devtools(self : RuntimeWebViewBoot) -> Bool {
self.devtools
}
///|
pub fn RuntimeWebViewBoot::asset_protocol(self : RuntimeWebViewBoot) -> String {
self.asset_protocol
}
///|
pub fn RuntimeWebViewBoot::bridge_global_name(
self : RuntimeWebViewBoot,
) -> String {
self.bridge_global_name
}
///|
pub fn RuntimeWebViewBoot::native_hook(self : RuntimeWebViewBoot) -> String {
self.native_hook
}
///|
pub fn RuntimeWebViewBoot::event_dispatch_hook(
self : RuntimeWebViewBoot,
) -> String {
self.event_dispatch_hook
}
///|
pub fn RuntimeWebViewBoot::allowed_routes(
self : RuntimeWebViewBoot,
) -> Array[String] {
self.allowed_routes.copy()
}
///|
pub fn RuntimeWebViewBoot::initialization_scripts(
self : RuntimeWebViewBoot,
) -> Array[String] {
self.initialization_scripts.copy()
}
///|
pub fn RuntimeCapabilityGrant::name(self : RuntimeCapabilityGrant) -> String {
self.name
}
///|
pub fn RuntimeCapabilityGrant::windows(
self : RuntimeCapabilityGrant,
) -> Array[String] {
self.windows.copy()
}
///|
pub fn RuntimeCapabilityGrant::origins(
self : RuntimeCapabilityGrant,
) -> Array[String] {
self.origins.copy()
}
///|
pub fn RuntimeCapabilityGrant::platforms(
self : RuntimeCapabilityGrant,
) -> Array[String] {
self.platforms.copy()
}
///|
pub fn RuntimeCapabilityGrant::permissions(
self : RuntimeCapabilityGrant,
) -> Array[String] {
self.permissions.copy()
}
///|
pub fn RuntimeCapabilityGrant::operation_scopes(
self : RuntimeCapabilityGrant,
) -> Array[OperationScope] {
self.operation_scopes.copy()
}
///|
fn RuntimeCapabilityGrant::from_capability(
capability : Capability,
) -> RuntimeCapabilityGrant {
{
name: capability.name(),
windows: capability.windows(),
origins: capability.origins(),
platforms: capability.platforms(),
permissions: capability
.permissions()
.map(fn(permission) { permission.name() }),
operation_scopes: capability.operation_scopes(),
}
}
///|
pub fn RuntimeProtocolBinding::window_label(
self : RuntimeProtocolBinding,
) -> String {
self.window_label
}
///|
pub fn RuntimeProtocolBinding::scheme(self : RuntimeProtocolBinding) -> String {
self.scheme
}
///|
pub fn RuntimeProtocolBinding::root(self : RuntimeProtocolBinding) -> String {
self.root
}
///|
pub fn RuntimeVirtualAsset::window_label(self : RuntimeVirtualAsset) -> String {
self.window_label
}
///|
pub fn RuntimeVirtualAsset::path(self : RuntimeVirtualAsset) -> String {
self.path
}
///|
pub fn RuntimeVirtualAsset::mime_type(self : RuntimeVirtualAsset) -> String {
self.mime_type
}
///|
pub fn RuntimeVirtualAsset::content(self : RuntimeVirtualAsset) -> String {
self.content
}
///|
pub fn RuntimeLaunchAsset::url(self : RuntimeLaunchAsset) -> String {
self.url
}
///|
pub fn RuntimeLaunchAsset::mime_type(self : RuntimeLaunchAsset) -> String {
self.mime_type
}
///|
pub fn RuntimeLaunchAsset::body(
self : RuntimeLaunchAsset,
) -> RuntimeLaunchAssetBody {
self.body
}
///|
fn RuntimeLaunchManifest::resolve_virtual_asset(
self : RuntimeLaunchManifest,
url : String,
parsed : ParsedLaunchAssetUrl,
) -> Result[RuntimeLaunchAsset, String] {
match parse_manifest_asset_path(parsed.path) {
Err(problem) => Err(problem)
Ok((window_label, path)) => {
for file in self.virtual_files {
if file.window_label() == window_label && file.path() == path {
return Ok({
url,
mime_type: file.mime_type(),
body: LaunchVirtualContent(file.content()),
})
}
}
Err("virtual asset not found: \{window_label}/\{path}")
}
}
}
///|
fn RuntimeLaunchManifest::resolve_local_asset(
self : RuntimeLaunchManifest,
url : String,
parsed : ParsedLaunchAssetUrl,
) -> Result[RuntimeLaunchAsset, String] {
match parse_manifest_asset_path(parsed.path) {
Err(problem) => Err(problem)
Ok((window_label, path)) =>
match self.local_root_for(window_label) {
None => Err("local asset root not found: \{window_label}")
Some(root) =>
Ok({
url,
mime_type: mime_type_for_path(path),
body: LaunchLocalFile(join_asset_path(root, path)),
})
}
}
}
///|
fn RuntimeLaunchManifest::resolve_packaged_asset(
self : RuntimeLaunchManifest,
url : String,
parsed : ParsedLaunchAssetUrl,
) -> Result[RuntimeLaunchAsset, String] {
match parse_manifest_asset_path(parsed.path) {
Err(problem) => Err(problem)
Ok((window_label, path)) =>
match self.packaged_root_for(window_label) {
None => Err("packaged asset root not found: \{window_label}")
Some(root) =>
Ok({
url,
mime_type: mime_type_for_path(path),
body: LaunchPackagedFile(join_asset_path(root, path)),
})
}
}
}
///|
fn RuntimeLaunchManifest::local_root_for(
self : RuntimeLaunchManifest,
label : String,
) -> String? {
for mapping in self.protocol_mappings {
if mapping.window_label() == label &&
!mapping.root().has_prefix("memory:") &&
!mapping.root().has_prefix("package:") {
return Some(mapping.root())
}
}
None
}
///|
fn RuntimeLaunchManifest::packaged_root_for(
self : RuntimeLaunchManifest,
label : String,
) -> String? {
for mapping in self.protocol_mappings {
if mapping.window_label() == label && mapping.root().has_prefix("package:") {
return Some(mapping.root()[8:].to_owned())
}
}
None
}
///|
fn parse_launch_asset_url(url : String) -> Result[ParsedLaunchAssetUrl, String] {
match url.find("://") {
None => Err("asset url must include scheme")
Some(index) => {
let scheme = url[:index].to_owned()
let rest = url[index + 3:].to_owned()
match rest.find("/") {
None => Ok({ scheme, authority: rest, path: "" })
Some(path_index) =>
Ok({
scheme,
authority: rest[:path_index].to_owned(),
path: strip_asset_suffix(rest[path_index + 1:].to_owned()),
})
}
}
}
}
///|
fn strip_asset_suffix(path : String) -> String {
let without_query = match path.find("?") {
None => path
Some(index) => path[:index].to_owned()
}
match without_query.find("#") {
None => without_query
Some(index) => without_query[:index].to_owned()
}
}
///|
fn parse_manifest_asset_path(path : String) -> Result[(String, String), String] {
match path.find("/") {
None => Err("asset path must include window label")
Some(index) => {
let window_label = path[:index].to_owned()
let asset_path = path[index + 1:].to_owned()
if window_label == "" {
Err("asset window label is required")
} else if !is_safe_asset_path(asset_path) {
Err("unsafe asset path: \{asset_path}")
} else {
Ok((window_label, asset_path))
}
}
}
}
///|
fn is_safe_asset_path(path : String) -> Bool {
if path == "" || path.has_prefix("/") || path.has_prefix("\\") {
return false
}
for segment in path.split("/") {
if segment == "" || segment == "." || segment == ".." {
return false
}
if segment.contains("\\") {
return false
}
}
true
}
///|
fn join_asset_path(root : String, path : String) -> String {
let root = root.trim_end(chars="/\\").to_owned()
if root == "" {
path
} else {
"\{root}/\{path}"
}
}
///|
fn runtime_webview_boot_json(webview : RuntimeWebViewBoot) -> String {
[
"{",
"\"label\":\{webview.label().json_string()},",
"\"title\":\{webview.title().json_string()},",
"\"url\":\{webview.url().json_string()},",
"\"width\":\{webview.width()},",
"\"height\":\{webview.height()},",
"\"resizable\":\{webview.resizable().json_bool()},",
"\"titleBar\":\{webview.title_bar().manifest_name().json_string()},",
"\"devtools\":\{webview.devtools().json_bool()},",
"\"assetProtocol\":\{webview.asset_protocol().json_string()},",
"\"bridgeGlobalName\":\{webview.bridge_global_name().json_string()},",
"\"nativeHook\":\{webview.native_hook().json_string()},",
"\"eventDispatchHook\":\{webview.event_dispatch_hook().json_string()},",
"\"allowedRoutes\":[\{webview.allowed_routes().map(fn(route) { route.json_string() }).join(",")}],",
"\"initializationScripts\":[\{webview.initialization_scripts().map(fn(script) { script.json_string() }).join(",")}]",
"}",
].join("")
}
///|
fn runtime_protocol_binding_json(binding : RuntimeProtocolBinding) -> String {
[
"{",
"\"windowLabel\":\{binding.window_label().json_string()},",
"\"scheme\":\{binding.scheme().json_string()},",
"\"root\":\{binding.root().json_string()}",
"}",
].join("")
}
///|
fn runtime_virtual_asset_json(asset : RuntimeVirtualAsset) -> String {
[
"{",
"\"windowLabel\":\{asset.window_label().json_string()},",
"\"path\":\{asset.path().json_string()},",
"\"mimeType\":\{asset.mime_type().json_string()},",
"\"content\":\{asset.content().json_string()}",
"}",
].join("")
}
///|
fn runtime_capability_grant_json(grant : RuntimeCapabilityGrant) -> String {
[
"{",
"\"name\":\{grant.name().json_string()},",
"\"windows\":[\{grant.windows().map(fn(window) { window.json_string() }).join(",")}],",
"\"origins\":[\{grant.origins().map(fn(origin) { origin.json_string() }).join(",")}],",
"\"platforms\":[\{grant.platforms().map(fn(platform) { platform.json_string() }).join(",")}],",
"\"permissions\":[\{grant.permissions().map(fn(permission) { permission.json_string() }).join(",")}],",
"\"operationScopes\":[\{grant.operation_scopes().map(fn(scope) { scope.to_json() }).join(",")}]",
"}",
].join("")
}
///|
fn mime_type_for_path(path : String) -> String {
if path.has_suffix(".html") || path.has_suffix(".htm") {
"text/html"
} else if path.has_suffix(".js") || path.has_suffix(".mjs") {
"application/javascript"
} else if path.has_suffix(".css") {
"text/css"
} else if path.has_suffix(".json") {
"application/json"
} else if path.has_suffix(".svg") {
"image/svg+xml"
} else if path.has_suffix(".png") {
"image/png"
} else if path.has_suffix(".jpg") || path.has_suffix(".jpeg") {
"image/jpeg"
} else if path.has_suffix(".wasm") {
"application/wasm"
} else {
"application/octet-stream"
}
}