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

@passes/reqs

v0.1.5

Published

Reqs (pronounced "rex" – /rɛks/) provides a high-level interface for using Pass Requests in your app.

Downloads

62

Readme

Reqs (pronounced "rex" – /rɛks/) provides a high-level interface for using Pass Requests in your app.

Installation

npm add @passes/reqs    # or bun, yarn, pnpm

Defining a Pass Request Topic

To create a Pass Request topic, you need 3 things:

  1. A Topic ID. A string to identify your Request Topic. This is what Pass Providers will use to interpret a Pass Request of your topic, so it should uniquely identify your Pass Request topic. This should use reverse-dns notation and be namespaced to your organization – for example org.passes.my-example-request-topic.

  2. A Request Body Codec. This translates the rich representation of the request body data your Pass Request topic uses to binary and back.

  3. A Result Body Codec. This is just like the request body codec, but for your Pass Request's result body data.

Codecs

The Codecs module includes many common codecs for your convenience. It's available at @passes/reqs/codecs.

import * as Codecs from '@passes/reqs/codecs';

| Name | Rich Type | | --------------------- | ------------------ | | Codecs.BigInt | bigint | | Codecs.Boolean | boolean | | Codecs.Bytes | Uint8Array | | Codecs.Json | TJson (generic) | | Codecs.Number | number | | Codecs.String | string | | Codecs.Void | undefined |

You can also write your own codec to support any data type you'd like! The interface for a codec is:

type Codec<T> = {
  encode(value: T) => Uint8Array;
  decode(raw: Uint8Array) => T;
}

RequestTopic

Let's make a request topic that allows us to ask the user a yes-or-no question, and they can accept the request with a boolean representing their answer.

import { RequestTopic } from '@passes/reqs';
import * as Codecs from '@passes/reqs/codecs';

const yesOrNoQuestion = new RequestTopic({
  id: 'org.passes.example.yes-or-no-question',
  requestBodyCodec: Codecs.String,
  resultBodyCodec: Codecs.Boolean,
});

Now, we can send our request like so:

const userAnswer = await yesOrNoQuestion.sendRequest('Have you ever been to Olive Garden?');
//    ^ { status: 'accepted', body: true }

SignedRequestTopic

It's generally recommended to use signed Pass Requests, since they use asymmetric key cryptography to assert and verify that Pass Request results have been approved by the user.

To make a RequestTopic signed, we simply create a SignedRequestTopic to wrap it, and provide a signResult or verifyResult implementation...

import { RequestTopic, SignedRequestTopic, SignedBodyWrapper, SignedBodyWrapperHeader } from '@passes/reqs';
import * as Codecs from '@passes/reqs/codecs';

// For this demo implementation, we'll use the SubtleCrypto API
const keypair = await crypto.subtle.generateKey(keyParams, true, ['sign', 'verify']);

const yesOrNoQuestion = new SignedRequestTopic({
  // We're wrapping the same request topic we defined above
  requestTopic: new RequestTopic({
    id: 'org.passes.example.yes-or-no-question',
    requestBodyCodec: Codecs.String,
    resultBodyCodec: Codecs.Boolean,
  }),
  signResult: async (body: boolean): Promise<SignedBodyWrapperHeader> => ({
    publicKey: await crypto.subtle.exportKey(
      keyFormat,
      keyPair.value.publicKey
    ),
    signature: new Uint8Array(await crypto.subtle.sign(
      keyParams,
      keyPair.value.privateKey,
      Codecs.Boolean.encode(body)
    )),
  }),
  verifyResult: async (signed: SignedBodyWrapper<Boolean>): Promise<boolean> =>
    crypto.subtle.verify(
      keyParams,
      publicKey: await crypto.subtle.importKey(keyFormat, signed.header.publicKey, keyParams, true, ['verify']),
      signed.header.signature,
      Codecs.Boolean.encode(signed.body)
    ),
});

Building a Pass Provider

For building a Pass Provider, reqs exports a PassProviders namespace with relevent APIs, and some of the common APIs will be useful as well.

parseTopic

To get the topic of a Pass Request before you know its RequestTopic, you can use parseTopic on the raw request bytes.

import { parseTopic } from '@passes/reqs';

const id = parseTopic(
  // Replace this with a raw request
  new Uint8Array([/* ... */]),
);

PassProviders.providePass

When your user signs up or re-authenticates with your Pass Provider, you can send a providePass Pass Request to ask them if they want to direct future Pass Requests to your Pass Provider.

import * as PassProviders from '@passes/reqs/topics/pass-providers';

const { status } = await PassProviders.providePass('https://my-pass-provider.com', 'optional-user-id');

if (status === 'accepted') {
  // Future Pass Requests to this user will be sent to your Pass Provider for handling
}

Messaging.sendResult

Once your user has accepted or rejected a Pass Request, you can send the result back to the requesting app via Messaging.sendResult.

import * as Messaging from '@passes/reqs/messaging';

// Note: `handleRequest` is a placeholder for your handling logic for the given request topic
const result = await handleRequest(request);

await Messaging.sendResult(result);

Putting It All Together

Here's an example of how to use the above APIs together to implement support for a set of Pass Request topics in a basic Web Pass Provider.

import * as Messaging from '@passes/reqs/messaging';
import * as PassProviders from '@passes/reqs/topics/pass-providers';
import * as SupportedRequestTopics from './supported-request-topics'; // A map of the request topics supported by your Pass Provider

// Called when your user signs in to set your app as their Pass Provider
async function onUserAuthn(userToken) {
  await PassProviders.providePass(
    // The URI of your pass provider
    'https://your-pass-provider.com',
    // A token you can use later to identify the user when presenting a Pass Request UI to them - for example, a JWT
    userToken,
  );
}

// Presents a UI for the user to review and handle the incoming Pass Request, and sends the result to the requesting app
async function handlePassRequest(request: Uint8Array) {
  const id = parseTopic(request);

  switch (id) {
    case SupportedRequestTopics.GetUserEmail.RequestTopic: {
      const requestTopic = SupportedRequestTopics.Example1.RequestTopic;
      try {
        const requestBody = await requestTopic.decodeRequest(request);
        const result = await presentRequestReviewUIAndGetResult(requestTopic, requestBody);
        await Messaging.sendResult(result);
      } catch (error) {
        await Messaging.sendResult(await requestTopic.encodeResult({ status: 'exception', message: error.message }));
      }
      break;
    }

    // ... (other supported request topics)

    default:
      // Communicate to your user that the incoming request topic is not supported by your Pass Provider
      await Messaging.sendResult(EnvelopeV0.encodeResult({ status: 'unsupported' }));
  }
}