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

x-hook-service

v1.0.1

Published

Webhook implementation for service providers (w/ generic X-Hub-Signatures). Lightweight, zero-dependency, WebCrypto

Downloads

41

Readme

X-Hook Service

A concurrency-safe "track and retry" service for Webhooks (and other things?).

You provide the event storage and webhook request mechanism.
We provide the logic and sane defaults to retry failed webhooks with exponential backoff.
(and a bit of random jitter to prevent duplicate events on multiple servers)

Lightweight, zero-dependency.

Install

npm install --save x-hook-service@1

Usage

To run the service which retries failed webhooks in the background:

let XHookService = require("x-hook-service");

let xhookService = XHookService.create({
  store: store,
  retryWindow: 1 * 60 * 1000,
  retryOffset: 15 * 1000,
  // backoffMap: XHooks._backoffMap,
  random: Math.random(),
});

// fetches webhook events every minute
xhookService.run();

// for graceful shutdowns
// (not always necessary since weak timeout references are used)
await xhookService.stop();

Run a webhook immediately (typically the first time):

// schedules a webhook immediately
let date = new Date();
let event = { ulid: "01H9Y080000000000000000000", retries: 0, retry_at: null };
await xhookService.immediate(date, event);

Store & Event Implementation

You must provide a Store which can retrieve and update Webhook Events and Attempts (however you choose to track those).

They will be called by the service like this (with all the logic omitted):

let events = await store.anyIncomplete(nearFutureDate);
let event = await store.oneEvent(eventId);
eventId = store.getEventId(event);

let attempt = await store.addAttempt(attemptDate, { retry_at }, event);
let result = await store.runAttempt(attemptDate, attempt, event);
await store.updateAttempt(attemptDate, attempt, event, result);

The Store Interface

let store = {};

These are used to get a list of webhooks that should be attempted again, as well as the most up-to-date version of a specific event (it is checked just before a retry).

store.anyIncomplete = async function (nearFutureDate) {
  // return events for which the webhook should be tried again

  // Example:
  //     SELECT
  //         *
  //     FROM
  //         events
  //     WHERE
  //         completed_at is NULL
  //         AND
  //         retry_at <= :near_future_date

  return [event];
};

store.oneEvent = async function (eventId) {
  // return the event by the given id
  return event;
};

store.getEventId = function (event) {
  return event.ulid;
};

The actual running and reporting of the webhook is broken into 3 steps to ensure data consistency:

store.addAttempt = async function (attemptDate, { retry_at }, event) {
  // create and return the data representing a new webhook attempt

  // Example:
  //
  //     UPDATE
  //         events
  //     SET
  //         retries = :retries,
  //         retry_at = :retry_at
  //     WHERE
  //         event.ulid = :ulid

  return attempt;
};

store.runAttempt = async function (attemptDate, attempt, event) {
  // make the webhook request and return the result

  // Example:
  //
  //     let payload = JSON.stringify(event.details)
  //     let xHubSig = xHubSign(payload);
  //     fetch(event.webhook_url, {
  //         headers: { x-hub-signature-256: xHubSig },
  //         body: payload
  //     })

  return result;
};

store.updateAttempt = async function (attemptDate, attempt, event, result) {
  // update the record of attempt with the result

  // Example:
  //
  //     UPDATE
  //         events
  //     SET
  //         completed_at = CURRENT_TIMESTAMP,
  //         retry_at = null
  //     WHERE
  //         event.ulid = :ulid

  return;
};

The Event Interface

The event must have

  • some form of id (retrieved via getEventId())
  • a retries property indicating how many times the webhook has failed
  • retry_at, indicate the next time a webhook request should be tried
  • whatever details you need to attempt the webhook request
{
  ulid: '',
  retries: 0,
  retry_at: new Date(),
  // the rest is up to you
}