npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

tauri-awesome-rpc

v0.1.3

Published

TypeScript API for tauri-awesome-rpc

Readme

tauri-awesome-rpc

A custom invoke system for Tauri v2 that replaces the default IPC with a WebSocket-based RPC system following the JSON-RPC 2.0 specification.

Features

  • 🚀 JSON-RPC 2.0 over WebSocket - Full spec compliance with request/response correlation
  • 🔌 Single Persistent Connection - Efficient connection management with automatic reconnection
  • 📡 Request Multiplexing - Multiple concurrent requests over one WebSocket connection
  • ⚡ Event System - Alternative event API with both continuous and one-time listeners
  • 🎯 Backend Event Listening - Rust-side event subscriptions with convenient macros
  • ⏱️ Configurable Timeouts - Per-instance timeout configuration

Installation

Add to your src-tauri/Cargo.toml:

[dependencies]
tauri-awesome-rpc = { git = "https://github.com/ahkohd/tauri-awesome-rpc", branch = "v2" }

Quick Start

1. Backend Setup

use tauri::Manager;
use tauri_awesome_rpc::{AwesomeRpc, EmitterExt, ListenerExt};
use serde_json::json;

fn main() {
  let allowed_origins = if cfg!(dev) {
    vec!["http://localhost:1420", "http://localhost:5173"]
  } else {
    vec!["tauri://localhost"]
  };

  let awesome_rpc = AwesomeRpc::new(allowed_origins);

  tauri::Builder::default()
    .invoke_system(awesome_rpc.initialization_script())
    .setup(move |app| {
      awesome_rpc.start(app.handle().clone());

      // Optional: Setup backend event listeners
      let handle = app.handle();

      let _unlisten = handle.listen("user-action", |payload| {
        println!("User action: {:?}", payload);
      });

      // Emit an event
      handle.emit("app-ready", json!({"version": "1.0.0"}));

      Ok(())
    })
    .invoke_handler(tauri::generate_handler![my_command])
    .run(tauri::generate_context!())
    .expect("error while running tauri application")
}

#[tauri::command]
fn my_command(name: String) -> String {
  format!("Hello, {}!", name)
}

2. Frontend Setup

Install the npm package:

npm install tauri-awesome-rpc

Use in your frontend code:

import { invoke } from "@tauri-apps/api/tauri";
import { listen, once } from "tauri-awesome-rpc";

// Regular Tauri invoke - automatically uses WebSocket
const result = await invoke("my_command", { name: "World" });

// Listen to backend events
const _unlisten = listen("backend-event", (data) => {
  console.log("Received:", data);
});

// One-time event listener
const _unlisten = once("app-ready", (data) => {
  console.log("App initialized:", data);
});

API Reference

Backend APIs

Using Extension Traits (Recommended)

use tauri_awesome_rpc::{EmitterExt, ListenerExt};
use serde_json::json;

// Emit events - looks just like Tauri's native API!
app_handle.emit("event-name", json!({"data": "value"}));
app_handle.emit_to("main", "event-name", json!({"data": "value"}));

// Listen to events
let unlisten = app_handle.listen("event-name", |payload| {
  println!("Event received: {:?}", payload);
});

let unlisten = app_handle.once("event-name", |payload| {
  println!("One-time event: {:?}", payload);
});

// Stop listening
unlisten();

Note: If you import both tauri::Emitter and tauri_awesome_rpc::EmitterExt, you'll get a conflict. Solutions:

  • Use only EmitterExt for WebSocket transport
  • Import with aliases: use tauri::Emitter as TauriEmitter;
  • Use fully qualified syntax: EmitterExt::emit(&handle, "event", data)?;

Using Macros (Alternative)

use tauri_awesome_rpc::{emit, listen, once};
use serde_json::json;

// Emit events
emit!(app_handle, "event-name", json!({"data": "value"}));
emit!(app_handle, "main", "event-name", json!({"data": "value"}));

// Listen to events
let unlisten = listen!(app_handle, "event-name", |payload| {
  println!("Event received: {:?}", payload);
});

let unlisten = once!(app_handle, "event-name", |payload| {
  println!("One-time event: {:?}", payload);
});

Frontend APIs

Using TypeScript Module

import { listen, once } from "tauri-awesome-rpc";

const unlisten = listen("event-name", (data) => {
  console.log("Event:", data);
});

const unlisten = once("startup-event", (data) => {
  console.log("Startup:", data);
});

Using Window API

// Continuous listener
const unlisten = window.AwesomeListener.listen("event-name", (data) => {
  console.log("Event:", data);
});

// One-time listener
const unlisten = window.AwesomeListener.once("event-name", (data) => {
  console.log("Once:", data);
});

Advanced Configuration

Custom Configuration

use std::time::Duration;

// Default configuration
let awesome_rpc = AwesomeRpc::new(allowed_origins);

// Custom configuration with builder pattern
let awesome_rpc = AwesomeRpc::new(allowed_origins)
  .invoke_timeout(Duration::from_secs(60));  // Default: 30 seconds

WebSocket Buffer Configuration

Configure WebSocket buffer capacities to handle large payloads:

let awesome_rpc = AwesomeRpc::new(allowed_origins)
  .max_connections(2)                          // Default: 1 (single app connection)
  .max_payload(50 * 1024 * 1024)             // Default: 10MB
  .max_in_buffer_capacity(100 * 1024 * 1024)  // Default: 10MB
  .max_out_buffer_capacity(100 * 1024 * 1024); // Default: 10MB

This is particularly useful when:

  • Reading large files through the RPC system
  • Handling high-volume data transfers
  • Supporting many concurrent connections

Environment-based Origins

let allowed_origins = if cfg!(dev) {
  vec![
    "http://localhost:1420",     // Vite
    "http://localhost:5173",     // Vite alternative
    "http://localhost:3000"      // Next.js
  ]
} else {
  vec!["tauri://localhost"]      // Production
};

How It Works

Architecture

  1. WebSocket Server: Runs on a dynamically allocated port
  2. Invoke Interception: JavaScript postMessage calls are redirected to WebSocket
  3. JSON-RPC Protocol: All messages follow JSON-RPC 2.0 specification
  4. Event Bus: Internal broadcast channel for backend event distribution

Integration Details

Frontend → Backend

All standard Tauri APIs automatically use WebSocket:

  • invoke() commands
  • emit() / emitTo() from JavaScript
  • Plugin invocations

Backend → Frontend

When using awesome-rpc with extension traits:

use tauri_awesome_rpc::{EmitterExt, ListenerExt};

// When extension traits are imported, these use WebSocket
app_handle.emit("event", data);
app_handle.emit_to("main", "event", data);

let unlisten = app_handle.listen("event", |payload| {
  println!("Received: {:?}", payload);
});

let unlisten_once = app_handle.once("startup", |payload| {
  println!("Startup event: {:?}", payload);
});

// Without importing the extension traits, emit would use Tauri's built-in IPC

Important Notes

Event System Behavior

When you import tauri_awesome_rpc::EmitterExt:

  • app_handle.emit(), app_handle.emit_to() uses WebSocket transport
  • window.emit(), window.emit_to() uses WebSocket transport

When you import tauri_awesome_rpc::ListenerExt:

  • app_handle.listen(), app_handle.once() listens to WebSocket events
  • window.listen(), window.once() listens to WebSocket events
  • These shadow Tauri's built-in event listeners

Without importing the extension traits:

  • app_handle.emit() uses Tauri's standard IPC
  • app_handle.listen(), window.listen() use Tauri's standard event system
  • You can still use macros: emit!, listen!, once! for WebSocket transport

Command Execution Context

All commands invoked through awesome-rpc run in an async context, not on the main thread. This differs from Tauri's default behavior where sync commands run on the main thread.

This is actually a benefit! Running commands async by default:

  • Prevents accidental blocking of the main thread with long-running sync operations
  • Keeps the UI responsive during command execution
  • Makes main thread usage explicit and intentional
  • Makes debugging easier - explicit run_on_main_thread calls are easy to find and audit

If you need main thread execution (e.g., for UI operations):

#[tauri::command]
fn my_sync_command(app: tauri::AppHandle) {
  // This runs async with awesome-rpc

  // To run on main thread when needed:
  app.run_on_main_thread(|| {
    // UI operations or other main-thread-only code
    println!("Running on main thread!");
  });
}

This is particularly important for:

  • Platform-specific UI operations
  • Libraries that require main thread access
  • Code that depends on sync command behavior

The explicit run_on_main_thread pattern makes it clear which operations need main thread access, improving code maintainability.

Examples

Check out the complete example for a working demo.