///|
/// One JavaScript-facing API descriptor exposed by a user app command extension.
pub struct AppCommandApiDescriptor {
api_name : String
}
///|
/// JavaScript bridge metadata exposed by a user app command extension.
pub struct AppCommandExtensionDescriptor {
id : String
js_namespace : String
apis : Array[AppCommandApiDescriptor]
scripts : Array[String]
}
///|
/// Commands exposed by the user app process to the framework runtime.
pub struct AppCommandsConfig {
op_names : Array[String]
extensions : Array[@ipc.IpcExtensionDescriptor]
framework_extension_ids : Array[String]
}
///|
pub type AppCommandRequestContext = @core.AppCommandRequestContext
///|
/// Declarative user-process command extension spec.
pub struct AppCommandExtensionSpec {
id : String
js_namespace : String
apis : Array[AppCommandApiDescriptor]
scripts : Array[String]
dependencies : Array[String]
register : (CommandRegistrar) -> Unit raise
resolve_permission : (Json, String) -> Json raise PermissionScopeValidationError
on_destroy : () -> Unit raise
}
///|
/// Registry of command extension specs available to one user app process.
pub struct AppCommandExtensionRegistry {
specs : Map[String, AppCommandExtensionSpec]
registration_order : Array[String]
}
///|
pub fn AppCommandApiDescriptor::new(
api_name : String,
) -> AppCommandApiDescriptor {
AppCommandApiDescriptor::{ api_name, }
}
///|
pub fn AppCommandExtensionDescriptor::new(
id : String,
js_namespace : String,
apis : Array[AppCommandApiDescriptor],
scripts? : Array[String] = [],
) -> AppCommandExtensionDescriptor {
AppCommandExtensionDescriptor::{
id,
js_namespace,
apis: copy_command_api_descriptors(apis),
scripts: copy_strings(scripts),
}
}
///|
pub fn AppCommandsConfig::new(
op_names : Array[String],
extensions? : Array[AppCommandExtensionDescriptor] = [],
framework_extension_ids? : Array[String] = [],
) -> AppCommandsConfig {
AppCommandsConfig::from_ipc_descriptors(
op_names,
extensions.map(fn(extension) { extension.to_ipc_descriptor() }),
framework_extension_ids,
)
}
///|
pub fn AppCommandsConfig::from_ipc_descriptors(
op_names : Array[String],
extensions : Array[@ipc.IpcExtensionDescriptor],
framework_extension_ids : Array[String],
) -> AppCommandsConfig {
AppCommandsConfig::{
op_names: copy_strings(op_names),
extensions: copy_ipc_extension_descriptors(extensions),
framework_extension_ids: copy_strings(framework_extension_ids),
}
}
///|
/// Creates an extension spec whose JavaScript APIs and native operation names
/// are derived from typed extension command descriptors.
pub fn AppCommandExtensionSpec::typed(
contract : @proton_contract.ExtensionContract,
command_routes : Array[@proton_contract.ContractRoute],
register : (CommandRegistrar) -> Unit raise,
scripts? : Array[String] = [],
dependencies? : Array[String] = [],
resolve_permission? : (Json, String) -> Json raise PermissionScopeValidationError,
on_destroy? : () -> Unit raise = fn() { },
) -> AppCommandExtensionSpec raise ExtensionSpecError {
contract.validate() catch {
error => raise InvalidContract(detail=error.message())
}
let id = contract.id()
let js_namespace = contract.js_namespace()
let apis : Array[AppCommandApiDescriptor] = []
for route in command_routes {
guard route.extension_id() == Some(id) &&
route.extension_namespace() == Some(js_namespace) else {
raise ForeignCommandRoute(
extension_id=id,
operation_name=route.operation_name(),
)
}
apis.push(AppCommandApiDescriptor::new(route.member_name()))
}
let resolve_permission : (Json, String) -> Json raise PermissionScopeValidationError = match
resolve_permission {
Some(resolve) => resolve
None =>
fn(scope, _base_path) -> Json raise PermissionScopeValidationError {
guard scope is Object(fields) else {
raise PermissionScopeValidationError::Invalid(
detail="extension " + id + " permission scope must be an object",
)
}
for _, _ in fields {
raise PermissionScopeValidationError::Invalid(
detail="extension " +
id +
" does not accept a non-empty permission scope",
)
}
scope
}
}
AppCommandExtensionSpec::{
id,
js_namespace,
apis,
scripts: copy_strings(scripts),
dependencies: copy_strings(dependencies),
register,
resolve_permission,
on_destroy,
}
}
///|
pub fn AppCommandExtensionSpec::id(self : AppCommandExtensionSpec) -> String {
self.id
}
///|
pub fn AppCommandExtensionSpec::js_namespace(
self : AppCommandExtensionSpec,
) -> String {
self.js_namespace
}
///|
pub fn AppCommandExtensionSpec::dependencies(
self : AppCommandExtensionSpec,
) -> Array[String] {
copy_strings(self.dependencies)
}
///|
pub fn AppCommandExtensionSpec::op_names(
self : AppCommandExtensionSpec,
) -> Array[String] {
self.apis.map(fn(api) {
app_command_extension_op_name(self.js_namespace, api.api_name)
})
}
///|
pub fn AppCommandExtensionSpec::to_ipc_descriptor(
self : AppCommandExtensionSpec,
) -> @ipc.IpcExtensionDescriptor {
@ipc.IpcExtensionDescriptor::new(
self.id,
self.js_namespace,
self.apis.map(fn(api) { api.to_ipc_api() }),
scripts=copy_strings(self.scripts),
)
}
///|
pub fn AppCommandExtensionSpec::register(
self : AppCommandExtensionSpec,
registrar : CommandRegistrar,
) -> Unit raise {
(self.register)(registrar)
}
///|
pub fn AppCommandExtensionSpec::resolve_permission(
self : AppCommandExtensionSpec,
scope : Json,
base_path : String,
) -> Json raise PermissionScopeValidationError {
(self.resolve_permission)(scope, base_path)
}
///|
pub fn AppCommandExtensionSpec::destroy(
self : AppCommandExtensionSpec,
) -> Unit raise {
(self.on_destroy)()
}
///|
pub fn AppCommandExtensionSpec::apis(
self : AppCommandExtensionSpec,
) -> Array[AppCommandApiDescriptor] {
copy_command_api_descriptors(self.apis)
}
///|
pub fn AppCommandExtensionSpec::scripts(
self : AppCommandExtensionSpec,
) -> Array[String] {
copy_strings(self.scripts)
}
///|
pub fn AppCommandExtensionRegistry::new() -> AppCommandExtensionRegistry {
AppCommandExtensionRegistry::{ specs: Map([]), registration_order: [] }
}
///|
pub fn AppCommandExtensionRegistry::register(
self : AppCommandExtensionRegistry,
spec : AppCommandExtensionSpec,
) -> AppCommandExtensionRegistry {
let id = spec.id()
guard id.trim().to_owned() != "" else {
abort(
"AppCommandExtensionRegistry::register: extension id must not be empty",
)
}
guard self.specs.get(id) is None else {
abort(
"AppCommandExtensionRegistry::register: duplicate extension id: " + id,
)
}
guard spec.js_namespace().trim().to_owned() != "" else {
abort(
"AppCommandExtensionRegistry::register: extension namespace must not be empty",
)
}
self.specs.set(id, copy_app_command_extension_spec(spec))
self.registration_order.push(id)
self
}
///|
pub fn AppCommandExtensionRegistry::get(
self : AppCommandExtensionRegistry,
id : String,
) -> AppCommandExtensionSpec? {
self.specs.get(id).map(copy_app_command_extension_spec)
}
///|
pub fn AppCommandExtensionRegistry::registered_ids(
self : AppCommandExtensionRegistry,
) -> Array[String] {
copy_strings(self.registration_order)
}
///|
pub fn AppCommandExtensionRegistry::spec_map(
self : AppCommandExtensionRegistry,
) -> Map[String, AppCommandExtensionSpec] {
self.specs
}
///|
pub fn AppCommandExtensionRegistry::ordered_names(
self : AppCommandExtensionRegistry,
) -> Array[String] {
self.registration_order
}
///|
pub fn AppCommandApiDescriptor::api_name(
self : AppCommandApiDescriptor,
) -> String {
self.api_name
}
///|
pub fn AppCommandApiDescriptor::to_ipc_api(
self : AppCommandApiDescriptor,
) -> @ipc.IpcExtensionApi {
@ipc.IpcExtensionApi::new(self.api_name)
}
///|
pub fn AppCommandExtensionDescriptor::to_ipc_descriptor(
self : AppCommandExtensionDescriptor,
) -> @ipc.IpcExtensionDescriptor {
@ipc.IpcExtensionDescriptor::new(
self.id,
self.js_namespace,
self.apis.map(fn(api) { api.to_ipc_api() }),
scripts=copy_strings(self.scripts),
)
}
///|
pub fn AppCommandsConfig::op_names(self : AppCommandsConfig) -> Array[String] {
copy_strings(self.op_names)
}
///|
pub fn AppCommandsConfig::extensions(
self : AppCommandsConfig,
) -> Array[@ipc.IpcExtensionDescriptor] {
copy_ipc_extension_descriptors(self.extensions)
}
///|
pub fn AppCommandsConfig::framework_extension_ids(
self : AppCommandsConfig,
) -> Array[String] {
copy_strings(self.framework_extension_ids)
}
///|
pub fn app_command_js_property_is_reserved(name : String) -> Bool {
name.has_prefix("@@") ||
name == "__proto__" ||
name == "prototype" ||
name == "constructor"
}
///|
pub fn app_command_extension_op_name(
js_namespace : String,
api_name : String,
) -> String {
"ext:" + js_namespace + "/" + api_name
}
///|
fn copy_app_command_extension_spec(
spec : AppCommandExtensionSpec,
) -> AppCommandExtensionSpec {
AppCommandExtensionSpec::{
id: spec.id,
js_namespace: spec.js_namespace,
apis: copy_command_api_descriptors(spec.apis),
scripts: copy_strings(spec.scripts),
dependencies: copy_strings(spec.dependencies),
register: spec.register,
resolve_permission: spec.resolve_permission,
on_destroy: spec.on_destroy,
}
}
///|
fn copy_command_api_descriptors(
values : Array[AppCommandApiDescriptor],
) -> Array[AppCommandApiDescriptor] {
values.map(fn(value) { value })
}
///|
fn copy_ipc_extension_descriptors(
values : Array[@ipc.IpcExtensionDescriptor],
) -> Array[@ipc.IpcExtensionDescriptor] {
values.map(fn(value) {
@ipc.IpcExtensionDescriptor::new(
value.id,
value.js_namespace,
value.apis,
scripts=value.scripts,
)
})
}
///|
fn copy_strings(values : Array[String]) -> Array[String] {
values.map(fn(value) { value })
}