@passes/reqs
TypeScript icon, indicating that this package has built-in type declarations

0.1.5 • Public • Published

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' }));
  }
}

Readme

Keywords

none

Package Sidebar

Install

npm i @passes/reqs

Weekly Downloads

0

Version

0.1.5

License

none

Unpacked Size

127 kB

Total Files

85

Last publish

Collaborators

  • danscan