
Research
Supply Chain Attack on Axios Pulls Malicious Dependency from npm
A supply chain attack on Axios introduced a malicious dependency, plain-crypto-js@4.2.1, published minutes earlier and absent from the project’s GitHub releases.
@pdftron/react-ab
Advanced tools
AB testing tools for React projects.
yarn add @pdftron/react-ab
Also requires React and React DOM as peer dependencies.
First, you need to register your experiments with the ABService. This should happen as soon as possible in your application.
import { ABService } from '@pdftron/react-ab';
ABService.registerExperiments({
buttonTest: {
id: 'experimentID',
variantCount: 2
}
});
Next, you must wrap your entire app in the Provider component, and provide a backend for it to use.
import { ABService, ABProvider } from '@pdftron/react-ab';
import GoogleOptimizeBackend from './backend'
ABService.registerTests({
buttonTest: {
id: 'experimentID',
variantCount: 2
}
});
export default () => {
return (
<ABProvider backend={GoogleOptimizeBackend}>
<App />
</ABProvider>
)
}
Now, anywhere in your app, you can set up an AB test with the ABTest and Variant components:
import { ABTest, Variant } from '@pdftron/react-ab';
export const Button = ({text}) => {
return (
<ABTest name='buttonTest'>
<Variant variant={0}>
<button style={{color: 'blue'}}>
{text}
</button>
</Variant>
<Variant variant={1}>
<button style={{color: 'red'}}>
{text}
</button>
</Variant>
</ABTest>
)
}
The ABTest component will render whichever variant your backend specifies. In this scenerio, if the backend returns 0, then it will render a blue button, if it returns 1 it will render a red button.
An "experiment" is an object that specifies the experiment name, its ID, and how many variants it should have. There are also a few other useful options you can set.
To register a test, call ABService.registerExperiments and pass in an object containing your experiments. The key is the name of the experiment, and the value is data describing that experiment.
ABService.registerExperiments({
buttonTest: {
id: 'experimentID',
variantCount: 2
}
});
Each experiment contains the following data:
id (string) the ID of the experiment. This will be passed into your backends getVariant function.variantCount (number) how many variants this experiment is expected to have.testingId (number - optional) A unique ID/Index that is used purely for testing. See testing for more detailsA "backend" in this context is just an object that implements an interface that will be used by the component libarary.
A backend must satisfy the following interface:
type Backend = {
getVariant: (experimentId: string) => Promise<number>;
setVariant?: (experimentId: string, variant: number) => void;
}
The getVariant function accepts an experiment ID, and must return which variant of that experiment to use. This is where you would call your AB providers API to get a variant.
The setVariant function is used for the SSR feature. It is only required if SSR is enabled.
Example
This example uses Google Optimize and GTAG to get a variant index, but you could use any provider you wish.
const GoogleOptimizeBackend = {
getVariant: (experimentId) => {
return new Promise(resolve => {
gtag('event', 'optimize.callback', {
name: experimentId,
callback: (value) => {
resolve(Number(value));
}
})
})
}
}
ABProviderThis is a high level component that must wrap all instances of ABTest component. It should be at the very top of your application.
backend (Backend) the backend to use for your experimentschildren (ReactNode) React children to renderssrVariants (Object) See the SSR Guide for more infoExample
import { ABProvider } from '@pdftron/react-ab';
const backend = {
getVariant: async (expId) => {
const result = await fetch(`http://my-provider.com/api/get-variant?id=${expId}`);
const json = await result.json();
const { variantIndex } = json;
return variantIndex;
}
}
export const Root = () => {
return (
<ABProvider backend={backend}>
<App />
</ABProvider>
)
}
ABTestThis is a wrapper component that is the heart of your AB tests. It accepts a set of Variants, and renders only one depending on what index your getVariant function returns.
name (string) the name of the experiement that this represents. The experiment must be registered before you can use it. This should equal the key of the experiment that you pass to registerExperiments. See experiments for more info.children (Variant[]) The variants for this experimentloadingComponent (ReactNode - optional) A placeholder component to render while the experiment is loading. By default, nothing is rendered.Example
import { ABTest, Variant } from '@pdftron/react-ab'
export default () => {
return (
<ABTest name='experiment-name' loadingComponent={<div>Loading...</div>}>
<Variant variant={0}>
<p>This is rendered if `0` is returned from your backends `getVariant` function</p>
</Variant>
<Variant variant={1}>
<p>This is rendered if `1` is returned from your backends `getVariant` function</p>
</Variant>
</ABTest>
)
}
VariantThis component represents a single or multiple variants for your experiment. The children of this component are only rendered if the variant is chosen.
variant (number | number[]) the index of the variant. If the result of getVariant matches this number, then the children will be rendered. If you pass an array of numbers, it will render the children if the selected variant mathces any number in the arraychildren (ReactNode) the children to render if this variant is selecteddescription (string - optional) a description of the variant, used purely for readabilityExample
import { ABTest, Variant } from '@pdftron/react-ab'
export default () => {
return (
<ABTest name='experiment-name' loadingComponent={<div>Loading...</div>}>
<Variant variant={0} description="Original variant">
<p>Rendered if variant 0 is selected</p>
</Variant>
<Variant variant={[1,2,3]} description="Rendered as an h1">
<h1>Rendered if variant 1, 2, or 3 is selected</h1>
</Variant>
<Variant variant={4} description="Rendered as an h2">
<h2>Rendered if variant 4 is selected</h2>
</Variant>
</ABTest>
)
}
PageSwitchThis is a utility component that only renders components on certain pages. It is not directly related to AB testing, but is useful in scenerios where you want to AB test parts of a global component (header, footer), but only on a certiain page.
This component is similar to a JS router.
pathname (string) The current pathname. Can be fetched from window.location.pathname, or from your JS router.children (Page[]) An array of pages.Example
This example sets up an AB test that changes the text of a header. However, this test will only run when the user is on /contact-sales. If the user is not on that page, it will default to the Page that has isDefault set.
import { PageSwitch, Page } from '@pdftron/react-ab';
export const Footer = () => {
return (
<div>
<ul>
<li>Home</li>
<li>Docs</li>
<PageSwitch pathname={window.location.pathname}>
<Page path='/contact-sales'>
<ABTest name='sales-button'>
<Variant variant={0}>
<li>Free trial</li>
</Variant>
<Variant variant={1}>
<li>Get Started</li>
</Variant>
</ABTest>
</Page>
<Page isDefault>
<li>Free trial</li>
</Page>
</PageSwitch>
</ul>
</div>
)
}
PageA component that gets rendered inside PageSwitch
Note: One of path or isDefault is required.
path (string | string[] - optional) Only render this component if the pathname passed into PageSwitch matches this value or one of these valuesisDefault (boolean - optional) Render this component if no other Page components have a matching page.children (ReactNode) The children to renderSee PageSwitch for an example.
In the case of an error, the components will fall back to rendering the original variant (variant 0).
The following scenerios will cause the component to go into fallback mode:
getVariant on your backend times out. See timeouts for more info.By default, if your backend takes more than 1500 to return a variant from the getVariant function, the component will go into fallback mode and default to variant 0.
To change this timeout, you can call ABService.setVariantTimeout(ms: number) and pass a new value for this timeout (in ms).
For example, to change the timeout to 3 seconds, you would do
import { ABService } from '@pdftron/react-ab';
ABService.setVariantTimeout(3000)
In some scenerios, you may want to disable AB testing for the whole app, but you might not want to strip out all your AB code. One common scenerio of this is if the users AdBlock extension blocks your AB provider code from being loaded.
In this scenerio, you can disable AB testing by calling ABService.disable(). This will set the AB suite into fallback mode, and variant 0 will always be rendered.
import { ABService } from '@pdftron/react-ab';
ABService.disable()
You can test your experiments by setting query parameters that force an experiment to use a certain variant.
To force override an experiment, you must provide two query params:
force (number) The testingId of the experiment to override. See experiments for more info.variant (number) The variant index to forceExample
Consider the following configuration
import {ABProvider, ABService, ABTest, Variant} from '@pdftron/react-ab';
ABService.registerTests({
buttonTest: {
id: 'experimentID',
variantCount: 2,
testingId: 1
}
});
// Somewhere else in app
export default () => {
return (
<ABTest name='buttonTest'>
<Variant variant={0}>
<p>V1</p>
</Variant>
<Variant variant={1}>
<p>V2</p>
</Variant>
</ABTest>
)
}
To force variant zero, you could navigate to http://localhost:8000?force=1&variant=0
To force variant one, you could navigate to http://localhost:8000?force=1&variant=1
To enable debug logging, call ABService.enableLogging()
import { ABService } from '@pdftron/react-ab';
ABService.enableLogging()
If your application is server rendered, you may want to select and render your variant on the server in order to prevent content flashes when your site loads.
To enable server side rendering, please follow these steps:
enableSSR to your registerExperiments callimport { ABService } from '@pdftron/react-ab';
ABService.registerTests({
buttonTest: {
id: 'experimentID',
variantCount: 2
}
}, {
enableSSR: true
});
ABProviderA variant map is just a object where the keys are the experiment name, and the value is the variant you want to select.
We also provide a utility function to handle this for you if you do not want to implement yourself.
import { ABService, ABProvider } from '@pdftron/react-ab';
ABService.registerTests({
buttonTest: {
id: 'experimentID',
variantCount: 2
}
}, {
enableSSR: true
});
export const Root = () => {
return (
<ABProvider
backend={backend}
ssrVariants={{
buttonTest: 1
}}
>
<App />
</ABProvider>
)
}
setVariant function to your backend (optional)This function is used to report to your AB Testing service which variant you rendered.
Example using Google Optimize:
const GoogleOptimizeBackend = {
getVariant: (experimentId) => {
// getVariant code
},
setVariant: (experimentId, variant) => {
ga('set', 'exp', `${experimentId}.${variant}`);
}
}
We provide a utility function to generate a variant map for you. It looks at all your registered experiments and outputs a map. It also handles client side hydration and cookies.
ABService.getSSRVariants(cookies: Record<string, any>, setCookie: (name: string, value: number) => void)
Params:
cookies (object) A map of cookies in the request. Used for making sure the user always sees the same variantssetCookie (function) A function that is called whenever a new cookie is generated and needs to be set. It is passed the cookie name and the cookie value.Returns a map of cookies names -> variant indexes.
Example (in the context of Next.js):
import { ABService, ABProvider } from '@pdftron/react-ab';
ABService.registerTests({
buttonTest: {
id: 'experimentID',
variantCount: 2
}
}, {
enableSSR: true
});
export const Root = ({ variants }) => {
return (
<ABProvider
backend={backend}
ssrVariants={variants}
>
<App />
</ABProvider>
)
}
export const getServerSideProps = async (context) => {
const variants = ABService.getSSRVariants(
context.req.cookies,
(name, value) => {
context.req.cookies.set(name, value)
}
)
return {
props: {
variants
},
};
};
FAQs
AB testing suite for PDFTron React websites
The npm package @pdftron/react-ab receives a total of 112 weekly downloads. As such, @pdftron/react-ab popularity was classified as not popular.
We found that @pdftron/react-ab demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Research
A supply chain attack on Axios introduced a malicious dependency, plain-crypto-js@4.2.1, published minutes earlier and absent from the project’s GitHub releases.

Research
Malicious versions of the Telnyx Python SDK on PyPI delivered credential-stealing malware via a multi-stage supply chain attack.

Security News
TeamPCP is partnering with ransomware group Vect to turn open source supply chain attacks on tools like Trivy and LiteLLM into large-scale ransomware operations.