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

@darkobits/doorman

v0.2.0

Published

An attempt to make access control systems Suck Less.

Downloads

10

Readme

Modern apartment communities equipped with access control systems typically work like this:

  1. The resident gives their phone number (usually a cell) to their apartment community.
  2. When a guest arrives, they dial a number associated with the resident in the directory.
  3. The access control system calls the resident.
  4. The resident enters a digit or sequence of digits to open the door/gate for the guest.

This is annoying because of its synchronicity; it requires the resident to answer their phone, call quality is often very poor, and if anything goes wrong both parties wind up confused and frustrated. Additionally, this process does not scale well; a resident hosting a large event, for example, would have to remain tethered to their phone, anxious that one missed call will delay and frustrate one of their guests.

What if there was a better way?

  1. The resident gives their phone number (in this case, a Twilio number that points to a server running Doorman) to their apartment community.
  2. When a guest arrives, they dial a number associated with the resident in the directory.
  3. The access control system calls Doorman which, depending on configuration, will:
    • Immediately dial the digit or sequence of digits to open the door/gate for the guest
    • Send the resident an SMS letting them know someone has arrived.

This process is completely asynchronous; the resident need not answer their phone and guests are never needlessly delayed or confused.

Setup

Doorman is an HTTP server. To use it, you will need to install it someplace where HTTP servers like to live, such as Heroku, Now, et. al.

Next, you will need to set up a Twilio account. Create a TwiML App and configure the Voice URL to point to the server running Doorman.

Doorman is configured using a key/value store where each key is a phone number and each value is a JSON blob describing how Doorman should behave. You can use any key/value store you like, including an in-memory object, but Redis is ideal, and Heroku offers free nodes.

Installation

Doorman is available on NPM:

$ npm i @darkobits/doorman

Configuration

Doorman has the following configurable options:

|Option|Description| |---|---| |assetPath|Path to a folder containing any static assets (such as audio files) you wish to serve.| |port|Which port to listen on. This is typically configured automatically by providers like Heroku. Default: 8080| |primaryPhoneNumber|If Doorman can't find a matching key in its data store for an incoming call, it will forward the call to this number.| |twilioAccountSid|Twilio Account SID.| |twilioApplicationSid|Twilio Application SID.| |callDataFn|This function will be invoked by Doorman and will be passed the current incoming caller ID and a callback. The callback has the signature (err, data) and should be invoked and passed the call flow data matching the provided caller ID.| |logLevel|How much logging information to display. Uses npmlog. (Default: info)| |allowInsecure|Whether to allow connections over HTTP (default: false).|

Examples

Here is an example using Redis as a data store:

import { resolve } from 'path';
import redis from 'redis';
import doorman from '@darkobits/doorman';

const {
  PORT
  PRIMARY_PHONE_NUMBER,
  REDIS_URL,
  TWILIO_ACCOUNT_SID,
  TWILIO_APPLICATION_SID
} = process.env;


const client = new redis.createClient({
  url: REDIS_URL
});


doorman({
  assetPath: resolve(__dirname, 'assets'),
  port: PORT,
  primaryPhoneNumber: PRIMARY_PHONE_NUMBER,
  twilioAccountSid: TWILIO_ACCOUNT_SID,
  twilioApplicationSid: TWILIO_APPLICATION_SID,
  callDataFn: client.get.bind(client)
})
.startServer();

And here is an example using a basic in-memory data store:

import { resolve } from 'path';
import doorman from '@darkobits/doorman';

const {
  PORT
  PRIMARY_PHONE_NUMBER,
  REDIS_URL,
  TWILIO_ACCOUNT_SID,
  TWILIO_APPLICATION_SID
} = process.env;

const data = {
  // ...
};


doorman({
  assetPath: resolve(__dirname, 'assets'),
  port: PORT,
  primaryPhoneNumber: PRIMARY_PHONE_NUMBER,
  twilioAccountSid: TWILIO_ACCOUNT_SID,
  twilioApplicationSid: TWILIO_APPLICATION_SID,
  callDataFn: (callerId, cb) => cb(undefined, data[callerId])
})
.startServer();

Scripting Calls

Doorman is programmed using JSON. Each key in Doorman's data-store represents an inbound caller ID, and each value represents how Doorman should handle calls from that number. Doorman supports numerous directives which can be composed to construct a call flow.

The basic structure of a call flow is:

[
  ["directiveName", { /* Directive options. */ }],
  ["directiveName", { /* Directive options. */ }],
  ["directiveName", { /* Directive options. */ }]
]

Directives

forwardCall

Forwards the call to the provided number.

|Option|Type|Description| |---|---|---| |value|String|Number to forward to.|

Example:

[
  ["forwardCall", {
    "value": "+14155551212"
  }]
]

sendSms

Sends a text message.

|Option|Type|Description| |---|---|---| |value|String|Body of the message.| |to|String|Number to send the message to.|

Example:

[
  ["sendSms", {
    "to": "+14155551212",
    "value": "Hello, world!"
  }]
]

say

Uses Twilio's text-to-speech feature to speak the provided message. See the Twilio <Say> documentation for allowed values.

|Option|Type|Description| |---|---|---| |value|String|Message to speak.| |[voice='woman']|String|Voice to use.| |[language='en-GB']|String|Language to use.|

Example:

[
  ["say", {
    "value": "Greetings!"
  }]
]

sendDigits

Sends a sequence of DTMF tones for the provided digit or digits.

|Option|Type|Description| |---|---|---| |value|String|Digit or digit sequence.|

Example:

[
  ["sendDigits", {
    "value": "1234"
  }]
]

gatherDigits

Pauses the call and waits for the caller to enter a sequence of digits. The call will then proceed down the branch matching the sequence entered. A default branch must be provided to handle cases where the caller does not enter a matching sequence.

|Option|Type|Description| |---|---|---| |branches|Object|Object mapping possible responses to nested Doorman JSON blobs.

Example:

[
  ["gatherDigits", {
    "123": [
      ["directiveName", { /* Directive options. */ }]
    ],
    "456": [
      ["directiveName", { /* Directive options. */ }]
    ],
    "default": [
      ["directiveName", { /* Directive options. */ }]
    ]
  }]
]

play

Instructs Twilio to play the audio file at the provided URL. To ensure Doorman serves static assets correctly, configure the assetPath option to point to the folder containing your audio files.

|Option|Type|Description| |---|---|---| |value|String|Path to the audio file (relative to Doorman's web root) to play.|

Example:

[
  ["play", {
    "value": "assets/foo.mp3"
  }]
]

hangUp

Ends the call.

Note: This directive is typically not needed, as Doorman will end the call if it reaches the end of the call flow.

Example:

[
  ["hangUp"]
]

Examples

In the following examples, let's assume we are working with an access control system that has the phone number +14155551111, a resident who has a cell number +14155552222, and that residents must enter the digit 9 to grant access to guests. Note that if using a data store such as Redis, we would store the access control system's caller ID as a key and the call flow as a value. These examples use a plain object for clarity.

In this first example, we will program Doorman to immediately grant access to guests, then send an SMS to the resident.

{
  "+14155551111": [
    ["sendDigits", {
      "value": "9"
    }],
    ["sendSms", {
      "to": "+14155552222",
      "value": "A guest has arrived!"
    }]
  ]
}

Next, let's prompt the guest for a simple passcode (123) and forward the call to the resident if an incorrect passcode is entered:

{
  "+14155551111": [
    ["say", {
      "value": "Please enter your passcode."
    }],
    ["gatherDigits", {
      "123": [
        ["say", {
          "value": "Access granted!"
        }],
        ["sendDigits", {
          "value": "9"
        }],
        ["sendSms", {
          "to": "+14155552222",
          "value": "A guest has arrived!"
        }]
      ],
      "default": [
        ["forwardCall", {
          "value": "+14155552222"
        }]
      ]
    }]
  ]
}