powhttp-sdk 0.2.0

Official SDK for building powhttp extensions in Rust.
Documentation

powhttp-sdk

docs.rs crates.io

Official SDK for building powhttp extensions in Rust.

Extensions run as separate processes that communicate with powhttp over JSON-RPC. The SDK handles the runtime lifecycle and provides an ExtensionHandle for registering context-menu items, overview fields, inspector tabs, connect handlers and querying session data.

Example

use base64::prelude::{BASE64_URL_SAFE_NO_PAD, Engine};
use powhttp_sdk::{
    run, Error, ExtensionHandle,
    ContextMenuItemSingle, SingleEntryContext,
    OverviewField, MessageTab, TabContent,
};
use powhttp_sdk::sessions::SessionEntry;

fn bearer_token(entry: &SessionEntry) -> Option<&str> {
    entry.request.headers.get("authorization")?.strip_prefix("Bearer ")
}

fn decode_jwt_payload(token: &str) -> Option<String> {
    let payload = token.split('.').nth(1)?;
    let bytes = BASE64_URL_SAFE_NO_PAD.decode(payload).ok()?;
    String::from_utf8(bytes).ok()
}

#[tokio::main]
async fn main() -> Result<(), Error> {
    run(async |handle: ExtensionHandle| {
        // Add a context-menu item that copies the request URL
        handle.extend_context_menu_single(ContextMenuItemSingle::new(
            "copy-url",
            "Copy URL",
            async |ctx: SingleEntryContext, handle: ExtensionHandle| {
                let entry = handle
                    .get_session_entry(ctx.session_id, ctx.entry_id)
                    .await?;

                if let Some(entry) = entry {
                    handle.write_text_to_clipboard(&entry.url.to_string()).await?;
                }
                Ok(())
            },
        )).await?;

        // Add an overview field that shows the response content length
        handle.extend_overview(OverviewField::new(
            "content-length",
            "Content Length",
            async |ctx: SingleEntryContext, handle: ExtensionHandle| {
                let entry = handle
                    .get_session_entry(ctx.session_id, ctx.entry_id)
                    .await?;

                let size = entry
                    .and_then(|entry| entry.response)
                    .and_then(|res| res.body_size)
                    .map(|size| size.to_string());
                Ok(size)
            },
        )).await?;

        // Add a request tab that decodes the JWT sent in the Authorization header,
        // shown only for requests that carry a bearer token
        handle.add_request_tab(
            MessageTab::new(
                "jwt",
                "JWT",
                async |ctx: SingleEntryContext, handle: ExtensionHandle| {
                    let entry = handle
                        .get_session_entry(ctx.session_id, ctx.entry_id)
                        .await?;

                    let payload = entry
                        .as_ref()
                        .and_then(bearer_token)
                        .and_then(decode_jwt_payload)
                        .unwrap_or_default();

                    Ok(TabContent::json(payload))
                },
            )
            .visible_when(async |ctx: SingleEntryContext, handle: ExtensionHandle| {
                let entry = handle
                    .get_session_entry(ctx.session_id, ctx.entry_id)
                    .await?;

                Ok(entry.as_ref().and_then(bearer_token).is_some())
            }),
        ).await?;

        Ok(())
    })
    .await
}