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

perimeterx-axios-interceptor

v1.1.1

Published

🧱 Intercept requests which are blocked by PerimeterX - pop up the challenge and retry the request

Downloads

1,660

Readme

perimeterx-axios-interceptor

🧱 Intercept requests which are blocked by PerimeterX - pop up the challenge and retry the request

Using Advanced Blocking Response blocked JSON requests receive a JSON response with status 403. This response includes a payload which allow us to display PerimeterX's challenge. After visitor is exonerated, the original request will be sent and resolved using the original promise.

Playground

Visit the playground to experience the behaviour

Quick implementation

import axios from 'axios';
import { attach } from 'perimeterx-axios-interceptor';

attach(axios);

Implement all the things!

import axios from 'axios';
import { attach, detach } from 'perimeterx-axios-interceptor';

attach(axios, {
    filter: ({ path }) => !/\/logger/.test(path),
    onintercept: request => logger.info(`Intercepted a block response from request ${request.url}`),
    onignore: request => logger.info(`Ignored a block response from request ${request.url}`),
    onsuccess: request => logger.info(`Exonerated a request to ${request.url}`),
    onfailure: (request, error) => logger.info(`Failed to exonerate request to ${request.url}: ${error.message}`),
    onerror: error => logger.error(error),
    simulate: true, // Will **not** load the challenge
    modalConfig: {
        className: 'my-challenge-popup',
        title: 'Are you human?',
        subtitle: 'Please complete the challenge',
        quickfixes: [
            '1. Disable adblocker',
            '2. Enable Javascript'
        ],
        suffix: 'Still having issues? Contact support at [email protected]',
        timeout: 3000,
        allowClose: false
    }
});

// Remove the interceptor for some reason. Perhaps in order to re attach with different settings
detach(axios);

Flow description

Using the feature Advanced Blocking Response featured in PerimeterX's NginX Lua plugin

  1. Request is blocked by PerimeterX (403)
  2. Challenge modal is added to UI
  3. Challenge is resolved by user (user is exonerated)
  4. Replay original request and resolve original promise

HTML output

<dialog class="perimeterx-async-challenge" open="open">
    <div>
        <p class="title">One Small Step</p>
        <p class="subtitle">Please check the box below to continue your normal visit</p>
        <div id="px-captcha" class="challenge-box">
            <!-- Challange markup (div.g-recaptcha) injected by PerimeterX Javascript -->
        </div>
        <p class="quickfix">Please exclude this website from ad blocking or ad filtering software.</p>
        <p class="quickfix">Make sure you don't have any browser extensions tampering with request headers or user agent string.</p>
        <p>If you're still having trouble accessing the site, please contact customer support.</p>
        <style>
.perimeterx-async-challenge {
    z-index: 10000;
    position: fixed;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    border: 0;
    margin: 0;
    padding: 0;
    background: rgba(0, 0, 0, .3);
    color: black;
}
.perimeterx-async-challenge > div {
    margin: 20vh 20vw 0;
    background: white;
    box-shadow: 0 0 2em rgba(0, 0, 0, .4);
    padding: 1em 1.5em;
}
.perimeterx-async-challenge p,
.perimeterx-async-challenge .challenge-box {
    margin: 0 0 .5em;
}
.perimeterx-async-challenge .title {
    font-size: 2em;
    font-weight: bold;
}
.perimeterx-async-challenge .subtitle {
    font-size: 1.4em;
}
.perimeterx-async-challenge .quickfix {
    font-size: .8em;
    margin: 0;
}
.perimeterx-async-challenge .quickfix:before {
    content: "•";
    margin: 0 .5em
}
@media screen and (max-width:1040px) {
    .perimeterx-async-challenge > div {
        margin: 10vh 10vw 0;
    }
}
@media screen and (max-width:800px) {
    .perimeterx-async-challenge > div {
        margin: 5vw 5vw 0;
    }
}
        </style>
    </div>
</dialog>
<script src="https://captcha.px-cdn.net/<PERIMETERX_APP_IP>/captcha.js"></script>

If you add a custom class, dialog element will include both class names: <dialog class="perimeterx-async-challenge my-challenge-popup" open>

API

1st argument: axios

This is the Axios instance this plugin will intercept. If you use a global axios instance, just pass that one in

2nd argument: options

This is an optional object. All of its properties are also optional:

filter {function}

Filter function is fired before the intercepting function. If filter is passed as an argument, it must return a truthy value for the interceptor to fulfil it's role. Falsy values will result in the interceptor passing the response as is. It's signature includes the following named arguments:

| Name | Type | Meaning | Usage | - | - | - | - | path | string | Request original path | filter: ({ path }) => !/^\/(tracking\|beacon)(\/\|$)/.test(path) | appId | string | PerimeterX Application ID | filter: ({ appId }) => appId === window._pxAppId

It is considered a good practice not to disrupt the user experience for background communication suck as liveness beacons, logs and metrics. The axios error will be thrown to the listener and will be added a new field, ignored, so that consumers can elect to ignore these skipped blocks.

Requests that will fail the filter method will fire the "onignore" callback from your configuration.

axios('/beacon')
    .catch(
        error => error.ignored
            ? logger.debug('Ignored blocked request')
            : logger.error(error)
    );
onerror {function}

This function is called when an internal error happened with this interceptor The signature includes the error:

onerror: (error) => logger.error(error)
onintercept {function}

This function is called on every time a request is recognised as a PerimeterX block. The signature includes the original request object (axios.config):

onintercept: (request) => logger.info({ message: 'Axios intercepted a PerimeterX block response', url: request.url })
onignore {function}

Similar to onintercept, only this will fire for ignored request that did not pass the filter method. The signature includes the original request object (axios.config):

onignore: (request) => logger.info({ message: 'Axios ignored a PerimeterX block response', url: request.url })
onsuccess {function}

This function is called when a challenge was successfully completed. The signature includes the original request object (axios.config):

onsuccess: (request) => logger.info({ message: 'Axios interceptor exonerated request', url: request.url })
onfailure {function}

This function is called when a challenge was successfully completed. The signature includes the original request object (axios.config) and the rejection error:

onfailure: (request, error) => logger.info({ message: error.message, url: request.url, stack: error.stack })
simulate {boolean}

Set "simulate" to a truthy value to allow monitoring without prompting users with exoneration. The callback onintercept will fire, the rest will not. The promise will be rejected with the PerimeterX 403 response.

modalConfig {object}

This object allows configuration of the modal GUI:

  • className ({string}): Add custom className to modal
    • Default: None
  • title ({string}): Replace or disable default title
    • Default: "One Small Step"
  • subtitle ({string}): Replace or disable default subtitle
    • Default: "Please check the box below to continue your normal visit"]
  • quickfixes ({string[]}): Replace or disable default quick fixes (list)
    • Default:
      • "• Please exclude this website from ad blocking or ad filtering software."
      • "• Make sure you don't have any browser extensions tampering with request headers or user agent string.",
  • suffix ({string}): Replace or disable default suffix
    • Default: "If you're still having trouble accessing the site, please contact customer support."
  • timeout ({number}): Time, in milliseconds, to allow PerimeterX script to load before aborting
    • Default: 3000 (3 seconds)
  • allowClose ({boolean}): Allow users to close the modal
    • Default: true

Setting "title", "subtitle", "quickfixes", or "suffix" to a falsy value (null, empty string...) will prevent them from being rendered to GUI.

modalConfig: {
    className: 'my-challenge-modal',
    title: 'Just a little check',
    quickfixes: [
        '=> Turn off ad blockers',
        '=> Contact support if you need further assistance'
    ],
    suffix: null,
    timeout: 5000
}

About axios instances

If you plan to create axios instances, I suggest you consider using axios-inherit to add interceptor inheritance capability.