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 🙏

© 2026 – Pkg Stats / Ryan Hefner

bernard-js

v0.2.0

Published

Helper functions to communicate with BERNARD

Readme

bernard.js

Utility functions to communicate with BERNARD.

Install

For simplicity reasons, the source file can be loaded as-is in a browser or in webpack.

NPM/Webpack

yarn add bernard-js

And then

import bernard from 'bernard-js';

Browser

Add index.js to your project and load it as a script.

Usage

Three authentication mecanisms are provided out of the box, namely:

  • urlTokenAuth looks at a _b parameter in the query string
  • hashTokenAuth uses the hash to find the token and removes it once consumed, which makes it more secure than urlTokenAuth().
  • messengerExtensionsAuth uses messenger extensions

You can provide as many authentication methods as you want.

bernard.getUser(
    [
        bernard.hashTokenAuth(),
        bernard.urlTokenAuth(),
        bernard.messengerExtensionsAuth(FB_APP_ID)
    ],
    (err, user, token) => {
        if (err) {
            return console.log('Could not authenticate!');
        }

        // Sends a message with a Postback layer to the bot
        bernard.sendPostback(token, {action: 'foo'});

        // Tracks the current page into the analytics providers
        bernard.pageView(token);
    }
);

Of course, don't forget to replace FB_APP_ID with your own Facebook app ID.

Let's break it down.

  1. You create a list of authentication mecanisms you want to try
    [
        bernard.hashTokenAuth(),
        bernard.urlTokenAuth(),
        bernard.messengerExtensionsAuth(FB_APP_ID)
    ],
  1. You call getUser()
bernard.getUser(
    [
        bernard.hashTokenAuth(),
        bernard.urlTokenAuth(),
        bernard.messengerExtensionsAuth(FB_APP_ID)
    ],
    (err, user, token) => {
        // ...
    }
);
  1. You save the token and maybe user info. You will need the token for any other communication with the server.
        // Sends a message with a Postback layer to the bot
        bernard.sendPostback(token, {action: 'foo'});

        // Tracks the current page into the analytics providers
        bernard.pageView(token);

Enabling HTTP requests

This is a very important point

bernard.js will make HTTP requests to your BERNARD instance. Since BERNARD doesn't serve your front-end, it means that BERNARD and your front-end will run on different domains. Let's say:

  • bot.foobar.com is your BERNARD instance
  • front.foobar.com is your front-end instance

You have several options here, but by far the most simple is to create a proxy that maps front.foobar.com/postback to bot.foobar.com/postback. Otherwise you'd have to deal with CORS and other security-related problems.

Example nginx configuration for this:

server {
    listen 443 ssl default_server;
    listen [::]:443 ssl default_server;

    /* All sorts of things go here, like SSL configuration */

    index index.html;
    server_name front.foobar.com;

    location / {
        try_files $uri $uri/ =404;
        gzip_static on;
    }

    location /postback {
        proxy_pass https://bot.foobar.com;
    }
}

Simply doing so will allow your front-end to seamlessly communicate with your bot.

Bot-side

You might have noticed that there is two authentication methods.

Messenger Extensions-based

If your bot runs in Messenger, you should use the Messenger Extensions as they are way more secure than using a token in the URL (since the user could just copy/paste the URL to a friend by trying to share the page).

In order for the messenger extensions, you must:

  1. Add the domain of your front-end to the page's whitelist. By example, set add to the bot's environment variables FRONT_BASE_URL=https://front.foobar.com and then put in your bot's settings.py (provided that you use the default template):
def make_whitelist():
    out = []
    extract_domain('BERNARD_BASE_URL', out)
    extract_domain('FRONT_BASE_URL', out)
    return out

# ...

PLATFORMS = [
    {
        'class': 'bernard.platforms.facebook.platform.Facebook',
        'settings': [
            {
                # ...
                'whitelist': make_whitelist()
            },
        ],
    }
]
  1. When you make a URL Button, always set messenger_extensions to True. Example state from a hypothetical states.py file:
class TestState(MyBaseState):
    async def handle(self) -> None:
        url = 'https://front.foobar.com/'

        self.send(
            fbl.ButtonTemplate(
                text='Foo',
                buttons=[
                    fbh.UrlButton(
                        title='Bar',
                        url=url,
                        messenger_extensions=True,
                    ),
                ]
            )
        )

Hash-based

In the case where you're using the hash token, all you need to do is to sign the URL with the self.request.sign_url() function. It is pretty similar to urlTokenAuth() but is a little bit more secure since the token is removed from the URL once loaded.

By example:

class TestState(MyBaseState):
    async def handle(self) -> None:
        url = await self.request.sign_url('https://front.foobar.com/')

        self.send(
            fbl.ButtonTemplate(
                text='Foo',
                buttons=[
                    fbh.UrlButton(
                        title='Bar',
                        url=url,
                    ),
                ]
            )
        )

Query token-based

In the case where you're using the URL token, all you need to do is to sign the URL with the self.request.sign_url() function. It is a bit less secure than hashTokenAuth() so please avoid using it if you don't need to.

By example:

class TestState(MyBaseState):
    async def handle(self) -> None:
        url = await self.request.sign_url(
            'https://front.foobar.com/', method=self.request.QUERY
        )

        self.send(
            fbl.ButtonTemplate(
                text='Foo',
                buttons=[
                    fbh.UrlButton(
                        title='Bar',
                        url=url,
                    ),
                ]
            )
        )

Reference

Authentication methods

urlTokenAuth(tokenName)

    /**
     * Authenticate by URL token. The `tokenName` parameter is optional,
     * default value is _b.
     */
    function urlTokenAuth(tokenName) {
        // ...
    }

hashTokenAuth()

    /**
     * Authenticates using the hash fragment. It is more secure than the
     * url token because it will automatically be removed from the URL once
     * consumed.
     */
    function hashTokenAuth() {
        // ...
    }

messengerExtensionsAuth(appId)

    /**
     * Authenticate the user using Messenger Extensions
     * @param appId {String} Facebook app ID
     */
    function messengerExtensionsAuth(appId) {
        // ...
    }

Messenger-related

messengerExtensionsReady(cb)

    /**
     * Install the messenger extensions, wait for them to load and calls the
     * callback with the extensions as parameter.
     */
    function messengerExtensionsReady(cb) {
        // ...
    }

Bernard communication

getUser(methods, cb, endpoint, tokenKey)

    /**
     * Authenticate and get the user.
     *
     * @param methods {Function[]} Authentication functions to be called
     * @param cb {Function} Node-style callback (cb(err, user)) that will be
     *                      called when there's a user.
     * @param endpoint {String} Optional parameter. Address of the endpoint to
     *                          use for authentication
     * @param tokenKey {String} Key of the token to use
     */
    function getUser(methods, cb, endpoint, tokenKey) {
        // ...
    }

sendPostback(token, payload, cb, endpoint, tokenKey)

    /**
     * Sends a postback message to the user
     *
     * @param token {String} Token returned by `getUser()`
     * @param payload {Object} Payload to be sent
     * @param cb {Function} Callable that will receive the result. First
     *                      argument will be the error if any, undefined
     *                      otherwise.
     * @param endpoint {String} Optional endpoint URL
     * @param tokenKey {String} Optional custom name for custom token key
     */
    function sendPostback(token, payload, cb, endpoint, tokenKey) {
        // ...
    }

pageView(token, path, title, cb, endpoint, tokenKey)

    /**
     * Track a page view in the analytics provider(s)
     *
     * @param token {String} Token returned by `getUser()`
     * @param path {String} URL path. By default, uses the current path.
     * @param title {String} Page title. By default, uses the current title.
     * @param cb {String} Optional callback called when the tracking is done.
     * @param endpoint {String} Optional custom endpoint URL
     * @param tokenKey {String} Optional custom token key
     */
    function pageView(token, path, title, cb, endpoint, tokenKey) {
        // ...
    }