Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ scripts/codegen.sh regenerate the TS client from the Rust crate
- Remove legacy compatibility code by default. Keep or add it only when explicitly requested.
- In Rust format strings, prefer inlined variables: `"log value: {value:?}"` over `"log value: {:?}", value`.
- **No `any` in TypeScript types**: If a type can't be expressed cleanly, stop and ask the user whether to (a) refactor or import the right type or (b) add a scoped `// eslint-disable-next-line @typescript-eslint/no-explicit-any` exception. Never silently leave `any`.
- Don't introduce typealias chains that just rename a public type from another crate (e.g. `pub type StorageError = crate::v01::HostLocalStorageReadError`). Use the canonical name directly. A typealias is only worth its indirection when it captures a real abstraction.
- After any code change, update `README.md` (and CLAUDE.md if the layout changed) so the top-level docs reflect what the repo actually contains. Stale docs are a regression.
- In codegen emitters, prefer `indoc::writedoc!` / `formatdoc!` over chains of `writeln!`. A single `writedoc!` with a multi-line raw string keeps the emitted shape visible in source instead of fragmenting it across one-line `writeln!` calls. Reserve `writeln!` for the genuinely-one-line case (a single import, a single statement inside a loop).
- In PR descriptions, issue comments, and other artifacts that outlive the conversation: describe the resulting state, not the transition between commits. Avoid "previously X, now Y", "we removed", "the old shim is gone", "this PR replaces", those read as ephemeral history once the PR is squash-merged. Write what the system *does* after the change, not what each commit *changed* on the way there. (Commit messages are the place for transition narrative; they survive in `git log` even after the squash.)

## First-time setup

Expand Down
50 changes: 49 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 10 additions & 3 deletions js/packages/truapi/src/transport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@ import { err, ok, type Result, type ResultAsync } from "neverthrow";

import { str, u8, type ResultPayload } from "./scale.js";

/**
* Coerce an unknown thrown value into an `Error` instance.
*/
function toError(error: unknown): Error {
return error instanceof Error ? error : new Error(String(error));
}

/**
* Handle returned by TrUAPI subscription APIs.
**/
Expand Down Expand Up @@ -408,7 +415,7 @@ function createBaseProvider() {
/** Transition to the closed state. Idempotent. */
close(error: unknown) {
if (closedError) return;
closedError = error instanceof Error ? error : new Error(String(error));
closedError = toError(error);
for (const fn of [...onCloseCleanup]) {
try {
fn();
Expand Down Expand Up @@ -494,7 +501,7 @@ export function createIframeProvider(options: {
target.postMessage(message, hostOrigin);
} catch (error) {
base.close(error);
throw error instanceof Error ? error : new Error(String(error));
throw toError(error);
}
},
subscribe: base.subscribe,
Expand Down Expand Up @@ -559,7 +566,7 @@ export function createMessagePortProvider(
resolvedPort.postMessage(message);
} catch (error) {
base.close(error);
throw error instanceof Error ? error : new Error(String(error));
throw toError(error);
}
} else {
pending.push(message);
Expand Down
2 changes: 1 addition & 1 deletion rust/crates/truapi-codegen/src/rustdoc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ pub struct WireAttrs {
}

/// Wire-shape classification of a trait method.
#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MethodKind {
/// One request, one response.
Request,
Expand Down
1 change: 1 addition & 0 deletions rust/crates/truapi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ edition.workspace = true
description = "TrUAPI trait and type definitions"

[dependencies]
derive_more = { version = "2", features = ["display"] }
futures = "0.3"
hex = "0.4"
parity-scale-codec = { version = "3", features = ["derive"] }
Expand Down
2 changes: 1 addition & 1 deletion rust/crates/truapi/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ _Source of truth for the TrUAPI protocol: shared traits, versioned types, and th

It defines:

- **Versioned data types** under `v01`, `v02`, and `versioned`.
- **Versioned data types** under `v01` and `versioned`.
- **Domain API traits** under `api/`, plus the composed `TrUApi` trait.
- **Wire ids** via per-method `#[wire(id = N)]` annotations that pin the byte-level method table.
- **Subscription primitives** through `Subscription<T>` for streamed host responses.
Expand Down
25 changes: 22 additions & 3 deletions rust/crates/truapi/src/v01/permissions.rs
Original file line number Diff line number Diff line change
@@ -1,47 +1,66 @@
use derive_more::Display;
use parity_scale_codec::{Decode, Encode};

/// Device-capability permission requested from the host (RFC 0002).
///
/// The user's decision is persisted indefinitely after the first prompt and
/// survives app restarts, whether the decision was grant or deny; the host
/// does not re-prompt on subsequent requests for the same capability.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode, Display)]
#[allow(clippy::upper_case_acronyms)]
pub enum HostDevicePermissionRequest {
#[display("notifications")]
Notifications,
#[display("camera")]
Camera,
#[display("microphone")]
Microphone,
#[display("bluetooth")]
Bluetooth,
#[display("NFC")]
NFC,
#[display("location")]
Location,
#[display("clipboard")]
Clipboard,
#[display("open URL")]
OpenUrl,
#[display("biometrics")]
Biometrics,
}

/// One remote-operation permission requested by the product (RFC 0002).
///
/// `ChainSubmit`, `PreimageSubmit`, and `StatementSubmit` are also triggered
/// implicitly by the corresponding business calls when not yet granted.
#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)]
#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, Display)]
pub enum RemotePermission {
/// Outbound HTTP/WebSocket access to a set of domains.
#[display("access to {}", domains.join(", "))]
Remote {
/// Domain patterns requested by the product.
domains: Vec<String>,
},
/// WebRTC media access.
#[display("WebRTC connections")]
WebRtc,
/// Submitting transactions on behalf of the user via `remote_chain_transaction_broadcast`.
#[display("submit chain transactions")]
ChainSubmit,
/// Submitting preimages on behalf of the user via `remote_preimage_submit`.
#[display("submit preimages")]
PreimageSubmit,
/// Submitting statements on behalf of the user via `remote_statement_store_submit`.
#[display("submit statements")]
StatementSubmit,
}

/// Batched remote-permission request (RFC 0002).
#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)]
#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, Display)]
#[display(
"{}",
permissions.iter().map(ToString::to_string).collect::<Vec<_>>().join("; ")
)]
pub struct RemotePermissionRequest {
/// Permissions requested by the product.
pub permissions: Vec<RemotePermission>,
Expand Down
10 changes: 10 additions & 0 deletions rust/crates/truapi/src/versioned/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pub trait IntoVersion: Sized {
macro_rules! versioned_type {
(
$(
$(#[$enum_meta:meta])*
pub enum $name:ident {
$($body:tt)*
}
Expand All @@ -32,6 +33,7 @@ macro_rules! versioned_type {
$(
versioned_type! {
@one
$(#[$enum_meta])*
pub enum $name {
$($body)*
}
Expand All @@ -41,13 +43,17 @@ macro_rules! versioned_type {

(
@one
$(#[$enum_meta:meta])*
pub enum $name:ident {
$(#[$variant_meta:meta])*
V1 => $v1:ty $(,)?
}
) => {
$(#[$enum_meta])*
#[doc = concat!("Versioned envelope for [`", stringify!($name), "`].")]
#[derive(Debug, Clone, PartialEq, Eq, parity_scale_codec::Encode, parity_scale_codec::Decode)]
pub enum $name {
$(#[$variant_meta])*
#[codec(index = 0)]
V1($v1),
}
Expand All @@ -61,13 +67,17 @@ macro_rules! versioned_type {

(
@one
$(#[$enum_meta:meta])*
pub enum $name:ident {
$(#[$variant_meta:meta])*
V1 $(,)?
}
) => {
$(#[$enum_meta])*
#[doc = concat!("Versioned envelope for [`", stringify!($name), "`].")]
#[derive(Debug, Clone, PartialEq, Eq, parity_scale_codec::Encode, parity_scale_codec::Decode)]
pub enum $name {
$(#[$variant_meta])*
#[codec(index = 0)]
V1,
}
Expand Down
4 changes: 4 additions & 0 deletions rust/crates/truapi/src/versioned/permissions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@
use crate::v01;

versioned_type! {
#[derive(derive_more::Display)]
#[display("{_0}")]
pub enum HostDevicePermissionRequest { V1 => v01::HostDevicePermissionRequest }
pub enum HostDevicePermissionResponse { V1 => v01::HostDevicePermissionResponse }
pub enum HostDevicePermissionError { V1 => v01::GenericError }
#[derive(derive_more::Display)]
#[display("{_0}")]
pub enum RemotePermissionRequest { V1 => v01::RemotePermissionRequest }
pub enum RemotePermissionResponse { V1 => v01::RemotePermissionResponse }
pub enum RemotePermissionError { V1 => v01::GenericError }
Expand Down
Loading