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 🙏

© 2024 – Pkg Stats / Ryan Hefner

safe-rpc

v0.1.8

Published

An encrypted, authenticated, transport agnostic rpc protocol.

Downloads

4

Readme

safe-rpc

npm version npm downloads bundle Codecov License JSDocs

An encrypted, authenticated, transport agnostic rpc protocol.

Create RPC services that communicate over atypical transport layers like the postMessage API(see safe-rpc-iframe), usb flash drive, homing pigeon, snail mail, audio, QR codes and more.

🚀 Quick Start

Install:

# npm
npm i safe-rpc

# yarn
yarn add safe-rpc

Example Usage: First define your channel and peer classes:

import {
  EncryptedRPCSession,
  EncryptedSession,
  EncryptedSessionMessageHub,
  IEncryptedMessage,
  ISyncRawMessageChannel,
  deserializePeerSessionConfig,
  serializePeerSessionConfig,
} from "safe-rpc";
import {
  SimpleSyncTransport,
  ISimpleSyncTransportChannelHandler,
} from "safe-rpc/simpleTransport";

function parseConfigString(cfgString: string) {
  const splitParts = cfgString.split("~");
  if (splitParts.length !== 3) {
    throw new Error("invalid cfg string");
  }
  const [serializedPeerSessionConfig, id, peerId] = splitParts;

  return {
    serializedPeerSessionConfig,
    id,
    peerId,
  };
}

function encodeConfigString(
  serializedPeerSessionConfig: string,
  id: string,
  peerId: string
) {
  if(serializedPeerSessionConfig.indexOf("~") !== -1){
    throw new Error("serializedPeerSessionConfig must not contain the character '~'");
  }
  if(id.indexOf("~") !== -1){
    throw new Error("id must not contain the character '~'");
  }
  if(peerId.indexOf("~") !== -1){
    throw new Error("peerId must not contain the character '~'");
  }
  return [serializedPeerSessionConfig, id, peerId].join("~");
}

class SimpleSyncChannel
  implements ISyncRawMessageChannel, ISimpleSyncTransportChannelHandler
{
  id: string;
  peerId: string;
  transport: SimpleSyncTransport;
  disposed = false;
  callbacks: ((message: IEncryptedMessage) => void)[] = [];

  constructor(id: string, peerId: string, transport: SimpleSyncTransport) {
    this.id = id;
    this.peerId = peerId;
    this.transport = transport;
    transport.registerPeer(id, this);
  }
  sendMessageRaw(message: IEncryptedMessage) {
    this.transport.sendMessage(this.peerId, message);
  }
  addMessageListener(callback: (message: IEncryptedMessage) => void) {
    if (this.callbacks.indexOf(callback) !== -1) {
      return;
    }
    this.callbacks.push(callback);
  }

  notifyMessage(message: any): any {
    for (const cb of this.callbacks) {
      try {
        cb(message);
      } catch (err) {
        // comment the line below to ignore callback errors
        throw err;
      }
    }
  }
  removeMessageListener(callback: (message: IEncryptedMessage) => void) {
    const index = this.callbacks.indexOf(callback);
    if (index !== -1) {
      this.callbacks = this.callbacks
        .slice(0, index)
        .concat(this.callbacks.slice(index + 1));
    }
  }
  connect(config?: any) {
    return true;
  }
  canSendMessage(): boolean {
    return !this.disposed;
  }
  dispose() {
    if (this.disposed) {
      return;
    }
    this.transport.unregisterPeer(this.id);
    this.disposed = true;
    this.callbacks = [];
  }
}

class SimplePeer extends EncryptedSessionMessageHub {
  //@ts-ignore
  channel: SimpleSyncChannel;
  id: string;
  peerId: string;
  constructor(
    session: EncryptedSession,
    channel: SimpleSyncChannel,
    id: string,
    peerId: string,
    onMessageHandlerError: (error: Error) => any | null = (error: Error) =>
      console.error(error),
    onDecryptionError?: (error: Error) => any
  ) {
    super(session, channel, onMessageHandlerError, onDecryptionError);
    this.channel = channel;
    this.id = id;
    this.peerId = peerId;
  }
  async getConfigStringForPeer(): Promise<string> {
    const peerSessionConfig = await this.session.generatePeerSessionConfig();
    return encodeConfigString(
      serializePeerSessionConfig(peerSessionConfig),
      this.peerId,
      this.id
    );
  }

  static async createWithPeerFromConfigString(
    transport: SimpleSyncTransport,
    configString: string,
    onMessageHandlerError: (error: Error) => any | null = (error: Error) =>
      console.error(error),
    onDecryptionError?: (error: Error) => any
  ): Promise<SimplePeer> {
    const { id, peerId, serializedPeerSessionConfig } =
      parseConfigString(configString);
    const deserializedPeerSessionInfo = deserializePeerSessionConfig(
      serializedPeerSessionConfig
    );
    const session = await EncryptedSession.fromPeerSessionInfo(
      deserializedPeerSessionInfo
    );
    const channel = new SimpleSyncChannel(id, peerId, transport);
    const hub = new SimplePeer(
      session,
      channel,
      id,
      peerId,
      onMessageHandlerError,
      onDecryptionError
    );
    return hub;
  }

  static async newPeerSession(
    transport: SimpleSyncTransport,
    id: string,
    peerId: string,
    onMessageHandlerError: (error: Error) => any | null = (error: Error) =>
      console.error(error),
    onDecryptionError?: (error: Error) => any
  ): Promise<SimplePeer> {
    const session = await EncryptedSession.newRandomSession();

    const channel = new SimpleSyncChannel(id, peerId, transport);
    const peer = new SimplePeer(
      session,
      channel,
      id,
      peerId,
      onMessageHandlerError,
      onDecryptionError
    );
    return peer;
  }
  dispose(): void {
    super.dispose();
    this.channel.dispose();
  }
}

Then create your sessions, register RPC handlers and start making calls 🔥

const transportHub = new SimpleSyncTransport();
const peerA = await SimplePeer.newPeerSession(
  transportHub,
  "peer_a",
  "peer_b"
);
const peerBConfigString = await peerA.getConfigStringForPeer();
const peerB = await SimplePeer.createWithPeerFromConfigString(
  transportHub,
  peerBConfigString
);
const rpcSessionPeerA = new EncryptedRPCSession(peerA);
const rpcSessionPeerB = new EncryptedRPCSession(peerB);
rpcSessionPeerA.registerRPCFunction("ping", async () => {
  const result = await rpcSessionPeerA.callRPC("reverse", "gnop".split(""));
  return result.join("");
});
rpcSessionPeerB.registerRPCFunction("reverse", (array: any[]) =>
  array.concat([]).reverse()
);
const result = await rpcSessionPeerB.callRPC("ping");
// should print "pong"
console.log(result);

✔️ Works with Node.js

We use conditional exports to detect Node.js

📦 Bundler Notes

  • All targets are exported with Module and CommonJS format and named exports
  • No export is transpiled for sake of modern syntax
  • You probably need to transpile safe-rpc with babel for ES5 support

❓ FAQ

Why do HMAC if GCM already does authentication with GMAC? GMAC is known to have some problems (Authentication Failures in NIST version of GCM , Cycling Attacks on GCM, GHASH and Other Polynomial MACs and Hashes), and we don't want to take any chances.

Why not transpiled?

safe-rpc is only compatible with modern Node/Deno (v10.0.0+) and modern browsers with support for SubtleCrypto.

License

MIT. Copyright 2023 Zero Knowledge Labs Limited