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

@vicerp/rpc

v1.0.0

Published

Platform-agnostic bidirectional RPC library for game client <-> browser communication

Readme

@vicerp/rpc

Platform-agnostic bidirectional RPC library for game frameworks. Provides procedure calls, events, and relay capabilities across the full Browser <-> Client <-> Server chain.

Architecture

Browser ──RPC──> Client ──RPC──> Server ──gRPC──> Engine
Browser <──RPC── Client <──RPC── Server <──gRPC── Engine

The client acts as a pure passthrough relay. All RPC messages from the browser are forwarded to the server and vice versa. The server is where business logic lives.

Installation

npm install @vicerp/rpc

Core API

Rpc

The main class for registering procedures, making calls, and sending events.

import { Rpc } from '@vicerp/rpc';

const rpc = new Rpc(transport);

// Register a procedure
rpc.register('inventory:split', async (args) => {
  const { slot } = args as { slot: number };
  // ... business logic
  return { success: true };
});

// Call a remote procedure
const result = await rpc.call<{ success: boolean }>('inventory:split', { slot: 4 });

// Fire-and-forget event
rpc.trigger('inventory:update', { items: [...] });

// Listen for events
rpc.on('inventory:update', (args) => {
  console.log('Inventory updated:', args);
});

// Unsubscribe
rpc.off('inventory:update', handler);

// Cleanup
rpc.destroy();

Options

// Call with custom timeout (default: 10s)
const result = await rpc.call('procedure', args, { timeout: 5000 });

RpcRelay

Bidirectionally forwards all messages between two transports. Zero knowledge of message content — just pipes through.

import { RpcRelay } from '@vicerp/rpc';

const relay = new RpcRelay(transportA, transportB);

// Messages from A are forwarded to B, and vice versa
// Cleanup
relay.destroy();

RpcTransport (interface)

All transports implement this interface:

interface RpcTransport {
  send(message: RpcMessage): void;
  onMessage(handler: (message: RpcMessage) => void): void;
  destroy(): void;
}

Message Types

// Procedure call
interface RpcRequest {
  type: 'request';
  id: string;
  procedure: string;
  args?: unknown;
}

// Procedure response
interface RpcResponse {
  type: 'response';
  id: string;
  result?: unknown;
  error?: string;
}

// Fire-and-forget event
interface RpcEvent {
  type: 'event';
  name: string;
  args?: unknown;
}

Adapters

RAGE:MP

Browser (CEF) Transport

import { RageBrowserTransport } from '@vicerp/rpc/rage/browser';

// In CEF browser code
const transport = new RageBrowserTransport(mp);
const rpc = new Rpc(transport);

Client Transport (Client <-> Browser)

import { RageClientTransport } from '@vicerp/rpc/rage/client';

// In client script
const transport = new RageClientTransport(browser, mp);
const rpc = new Rpc(transport);

Client-Server Transport (Client <-> Server)

import { RageClientServerTransport } from '@vicerp/rpc/rage/client-server';

// In client script — sends via mp.events.callRemote(), receives via mp.events
const transport = new RageClientServerTransport(mp);

Server Transport (Server <-> Client)

import { RageServerTransportHub, RageServerTransport } from '@vicerp/rpc/rage/server';

// Create ONE hub per server (registers global event listener)
const hub = new RageServerTransportHub(mp);

// Create per-player transports
const transport = hub.createTransport(player);
const rpc = new Rpc(transport);

// Cleanup when player disconnects
hub.removeTransport(player);

// Shutdown
hub.destroy();

Client as Relay (recommended pattern)

The client doesn't need its own Rpc instance — it just relays between browser and server:

import { RpcRelay } from '@vicerp/rpc';
import { RageClientTransport } from '@vicerp/rpc/rage/client';
import { RageClientServerTransport } from '@vicerp/rpc/rage/client-server';

const browserTransport = new RageClientTransport(browser, mp);
const serverTransport = new RageClientServerTransport(mp);
const relay = new RpcRelay(browserTransport, serverTransport);

FiveM

NUI Transport (Browser side)

import { FiveMNuiTransport } from '@vicerp/rpc/fivem/nui';

const transport = new FiveMNuiTransport(window);
const rpc = new Rpc(transport);

Client Transport (Client <-> NUI)

import { FiveMClientTransport } from '@vicerp/rpc/fivem/client';

const transport = new FiveMClientTransport(fivem);
const rpc = new Rpc(transport);

Client-Server Transport (Client <-> Server)

import { FiveMClientServerTransport } from '@vicerp/rpc/fivem/client-server';

// Sends via TriggerServerEvent(), receives via on()
const transport = new FiveMClientServerTransport(fivem);

Server Transport (Server <-> Client)

import { FiveMServerTransportHub } from '@vicerp/rpc/fivem/server';

// Create ONE hub per server (registers global onNet listener)
const hub = new FiveMServerTransportHub(fivem);

// Create per-player transports (uses source for player ID)
const transport = hub.createTransport(playerId);
const rpc = new Rpc(transport);

// Cleanup
hub.removeTransport(playerId);
hub.destroy();

React Integration

import { RpcProvider, useRpc, useRpcCall, useRpcEvent, useRpcRegister } from '@vicerp/rpc/react';

RpcProvider

Wraps your app and provides an Rpc instance via context.

<RpcProvider transport={transport}>
  <App />
</RpcProvider>

useRpc()

Access the Rpc instance directly.

const rpc = useRpc();

useRpcCall<T>(procedure, options?)

Make RPC calls with loading/error state management.

const { data, loading, error, call } = useRpcCall<Item[]>('inventory:getItems');

// Trigger the call
await call();

// Or with args
await call({ playerId: '123' });

useRpcEvent(name, handler)

Subscribe to events (auto-cleanup on unmount).

useRpcEvent('inventory:update', (items) => {
  setInventory(items);
});

useRpcRegister(name, handler)

Register a procedure handler (auto-cleanup on unmount).

useRpcRegister('ui:getState', () => {
  return { theme: 'dark', locale: 'en' };
});

Full Chain Example

Player clicks "split" button in inventory UI:

1. Browser:  rpc.call('inventory:split', { slot: 4 })
2. Client:   RpcRelay forwards to server
3. Server:   Rpc handler receives, sends gRPC to engine
4. Engine:   Processes split, responds via gRPC
5. Server:   rpc.trigger('inventory:update', { ... })
6. Client:   RpcRelay forwards to browser
7. Browser:  useRpcEvent('inventory:update', ...) fires

Browser (React)

import { RpcProvider, useRpcCall, useRpcEvent } from '@vicerp/rpc/react';
import { RageBrowserTransport } from '@vicerp/rpc/rage/browser';

const transport = new RageBrowserTransport(mp);

function Inventory() {
  const [items, setItems] = useState([]);
  const { call: splitItem } = useRpcCall('inventory:split');

  useRpcEvent('inventory:update', (data) => setItems(data.items));

  return (
    <button onClick={() => splitItem({ slot: 4 })}>Split</button>
  );
}

function App() {
  return (
    <RpcProvider transport={transport}>
      <Inventory />
    </RpcProvider>
  );
}

Client (RAGE:MP)

import { RpcRelay } from '@vicerp/rpc';
import { RageClientTransport } from '@vicerp/rpc/rage/client';
import { RageClientServerTransport } from '@vicerp/rpc/rage/client-server';

const browser = mp.browsers.new('http://localhost:3005');
const browserTransport = new RageClientTransport(browser, mp);
const serverTransport = new RageClientServerTransport(mp);
const relay = new RpcRelay(browserTransport, serverTransport);

Server (RAGE:MP)

import { Rpc } from '@vicerp/rpc';
import { RageServerTransportHub } from '@vicerp/rpc/rage/server';

const hub = new RageServerTransportHub(mp);

mp.events.add('playerReady', (player) => {
  const transport = hub.createTransport(player);
  const rpc = new Rpc(transport);

  rpc.register('inventory:split', async (args) => {
    const { slot } = args as { slot: number };
    // Call engine via gRPC...
    const result = await engineConnection.splitItem(playerId, slot);
    // Push update back to browser
    rpc.trigger('inventory:update', { items: result.items });
    return { success: true };
  });
});

Subpath Exports

| Import path | Description | |---|---| | @vicerp/rpc | Core: Rpc, RpcRelay, types, errors | | @vicerp/rpc/rage/browser | RAGE:MP CEF browser transport | | @vicerp/rpc/rage/client | RAGE:MP client transport (client <-> browser) | | @vicerp/rpc/rage/client-server | RAGE:MP client transport (client <-> server) | | @vicerp/rpc/rage/server | RAGE:MP server transport hub + per-player transport | | @vicerp/rpc/fivem/nui | FiveM NUI browser transport | | @vicerp/rpc/fivem/client | FiveM client transport (client <-> NUI) | | @vicerp/rpc/fivem/client-server | FiveM client transport (client <-> server) | | @vicerp/rpc/fivem/server | FiveM server transport hub + per-player transport | | @vicerp/rpc/react | React hooks and provider |

Development

# Install dependencies
npm install

# Run tests
npm test

# Run tests in watch mode
npm run test:watch

# Run tests with coverage
npm run test:cov

# Build (CJS + ESM)
npm run build

# Clean build output
npm run clean

Publishing

# From libs/vice/rpc directory:
npm publish --access public

The prepublishOnly script automatically runs clean, build, and test before publishing.

License

MIT