Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
JavaScript API client generator
Install the package apion
using npm
, yarn
or your preferred package manager. For example:
npm install apion
Import in your code:
import apion from 'apion';
// or import * as apion from 'apion'
import { json } from 'apion/helpers';
const apion = require('apion');
const { json } = require('apion/helpers');
import apion from 'apion';
import { json } from 'apion/helpers';
const apiConfg = apion.config()
.use(json)
.url('https://example.com/api');
const profile = apion.action('profile', id => api => api.query(`id=${id}`));
const auth = apion.group('auth', token => ({ token }))
.path('auth')
.headers((prev, { token }) => ({ ...prev, Authorization: token }))
.nest(profile);
const login = apion.action('login', (user, password) =>
api => api.body({ user, password })
)
.post();
const root = apion.group('root')
.use(apiConfig)
.nest(auth)
.nest(login);
const client = root.build();
// implementation
const { body: { token } } = await client.login('test@example.com', '123456');
/*
method: 'POST'
url: 'https://example.com/api/login'
headers: { 'content-type': 'application/json' }
body: '{"email":"test@example.com","password":"123456"}'
*/
const { body: profile } = await cient.auth(token).profile('abc123');
/*
method: 'GET'
url: 'https://example.com/api/auth/profile?id=abc123'
headers: { 'content-type': 'application/json', authorization: <token> }
body: -
*/
Methods exposed on apion
.
Create a ConfigBuilder
with an optional name
name
String: Sets the name of the configuration for reference in debugging.import apion from 'apion';
const myConfig = apion.config('my_config')
.url('https://example.com/api')
.headers({ 'my-header': 'some value' });
const isRunning = apion.action('isRunning')
.use(myConfig)
.path('_health')
.build();
await isRunning();
/*
method: 'GET'
url: 'https://example.com/api/_health'
headers: { 'my-header': 'some value' }
body: -
*/
Create a GroupBuilder
with a name
and an optional constructor
for dynamically setting context for nested builders
name
String: Sets the name of the group which by default is used as the name of the property added when nesting under another group or action.constructor
Function: A callback used to dynamically set the context for nested builders.import apion from 'apion';
import { json } from 'apion/helpers';
const login = apion
.action('login', (email, password) => api => api.body({ email, password }))
.post();
const publicClient = apion.group('public')
.use(json)
.url('https://example.com/api/public')
.nest(login)
.build();
await publicClient.login('test@example.com', '123456');
/*
method: 'POST'
url: 'https://example.com/api/public/login'
headers: { 'content-type': 'application/json' }
body: '{"email":"test@example.com","password":"123456"}'
*/
Used to dynamically set properties in the context for nested builders.
const profile = apion.action('profile')
.path('profile');
const authClient = apion.group('auth', token => ({ token }))
.url('https://example.com/api/admin')
.headers((prev, { token }) => ({ ...prev, Authorization: token }))
.nest(profile)
.build();
await authClient('XXX-000-AAA').profile();
/*
method: 'GET'
url: 'https://example.com/api/admin/profile'
headers: { authorization: 'XXX-000-AAA' }
body: -
*/
It can also be used to set more complex request properies by returning a callback which receives the containing GroupBuilder
.
const profile = apion.action('profile')
.path('profile');
const userClient = apion.group('user', (user, password) =>
api => api.path(user).headers({ Authorization: password })
)
.url('https://example.com/api/admin/user')
.nest(profile)
.build();
await userClient('greg123', '456789').profile();
/*
method: 'GET'
url: 'https://example.com/api/admin/user/greg123/profile'
headers: { authorization: '456789' }
body: -
*/
name
String: Sets the name of the action which by default is used as the name of the property added when nesting under another group or action.constructor
Function: A callback used to dynamically set the context for nested builders.requestBuilder
RequestBuilder: An instance of a RequestBuilder
used to inject a simple builder pattern when constructing complex request bodies.Automatically injects a builder to be used for creating requests.
import apion from 'apion';
import { json } from 'apion/helpers';
const requestBuilder = apion.builder()
.with('range', (start, end) => ({ start, end }))
.with('interval')
.with('timezone');
const getTimeseries = apion.action('timeseries', requestBuilder)
.use(json)
.url('https://example.com/api/timeseries')
.post();
await getTimeseries((req) =>
req.range(10, 200)
.interval(25)
.timezone('UTC')
);
/*
method: 'POST'
url: 'https://example.com/api/timeseries'
headers: { 'content-type': 'application/json' }
body: '{"start":10,"end":200,"interval":25,"timezone":"UTC"}'
*/
Create a RequestBuilder
with an optional formatter
formatter
Function: Sets a formatting callback to construct the final request body.import apion from 'apion'
const searchRequestBuilder = apion.builder()
.with('query')
.with('attributes', (...attributes) => ({ attributes }))
.with('range', (start, end) => ({ range: { start, end } }));
/*
creates request bodies of the form:
{
query: any,
attibutes: any[],
range: { start: any, end: any }
}
*/
Used to format the generated request body.
import apion from 'apion'
const searchRequestBuilder =
apion.builder((payload) => ({ type: 'search', payload }))
.with('query')
.with('pageSize')
.with('sort', (attribute, order) => ({ sort: { attribute, order } }));
/*
creates request bodies of the form:
{
type: 'search',
payload: {
query: any,
pageSize: any,
sort: { attribute: any, order: any }
}
}
*/
The base class
for ConfigBuilder
, GroupBuilder
and ActionBuilder
. Contains utility methods for managing request properties.
url
String: An full URL to set for the request, overrides the existing url.transform
Function: A callback to transform the previous value to the next value.import apion from 'apion';
apion.config()
.url('https://example.com');
A callback which is passed the previous value and the context object and should return the next value.
import apion from 'apion';
apion.config()
.ctx({ path: 'some/path' })
.url('https://example.com')
.url((prev, ctx) => `${prev}/${ctx.path}`);
port
Number: A port to set for the request, overrides the existing port.transform
Function: A callback to transform the previous value to the next value. (see transform)import apion from 'apion';
apion.config().port(8080);
query
String: A query to set for the request, overrides the existing query.transform
Function: A callback to transform the previous value to the next value. (see transform)import apion from 'apion';
apion.config().query('x=y&a=10');
path
String: A path to set for the request, overrides the existing path. If the path starts with a forward slash (/
) then the whole path will be replaced, otherwise it will be added to the existing path.transform
Function: A callback to transform the previous value to the next value. (see transform)import apion from 'apion';
apion.config()
.url('https://example.com/api')
.path('my/path'); // 'https://example.com/api/my/path'
apion.config()
.url('https://example.com/api')
.path('/my/path'); // 'https://example.com/my/path'
method
String: A method to set for the request, overrides the exting method.apion.config()
.url('https://example.com/api')
.method('POST');
apion
also exports its ownMethod
object for easy use
import { Method } from 'apion';
apion.config()
.url('https://example.com/api')
.method(Method.POST);
Sets the request method to GET
.
Sets the request method to POST
.
Sets the request method to PATCH
.
Sets the request method to PUT
.
Sets the request method to DELETE
.
headers
Object: A headers to set for the request, overrides the existing headers.transform
Function: A callback to transform the previous value to the next value. (see transform)import apion from 'apion';
apion.config().headers({ 'content-type': 'application/json' });
body
Object: A body to set for the request, overrides the existing body.transform
Function: A callback to transform the previous value to the next value. (see transform)import apion from 'apion';
apion.config().body('test@example.com:123456');
Add a callback to transform the request body before sending it.
formatter
Function: A callback to transform the request body before sending it.import apion from 'aption';
apion.config()
.formatter(body =>
typeof body === 'string' ? body : JSON.stringify(body)
);
Add a callback to transform the response body after receiving it.
parser
Function: A callback to transform the response body after receiving it.import apion from 'aption';
apion.config()
.parser(body =>
typeof body === 'string' ? JSON.parse(body) : body
);
Used to construct re-usable updates to context and request properties. Inherits all methods from HTTPBuilder
.
Update the context by merging in a new object (uses the same logic as Object.assign()
).
obj
Object: An object that will be merged with the existing context.import apion from 'apion';
apion.config().ctx({ id: 123 });
Add the configuration from the provided builder
or the result of the createBuilder
function to the builder instance.
builder
ConfigBuilder: Configuration from this builder will be inherited by the builder instance.createBuilder
Function: A function which accepts the context object and should return an instance of a ConfigBuilder
import apion from 'apion';
const config = apion.config()
.url('https://example.com');
apion.action('isRunning')
.use(config);
Used to have full control over dynamically setting request properties based on the context object.
import apion from 'apion';
apion.action('login', (user) => ({ user }))
.use(({ user }) =>
user === 'admin'
? apion.config().path('admin').headers({ Authorization: 'skip' })
: apion.config().path(`user/${user}`)
)
.url('https://example.com/api');
Like use()
except that configuration transformations are pushed to the top of the builder's inheritance chain.
import apion from 'apion';
const config = apion.config()
.url('https://example.com');
apion.action('isRunning')
.use(apion.config().path('api'))
.inherit(config);
A utility method to help apply re-usable chained methods to a builder instance.
import apion from 'apion';
const configure = api => api
.url('https://example.com/api')
.headers({ 'my-header': 'some value' });
// hard to chain
const test = configure(apion.action('test'));
// easy to chain
const test = apion.action('test').pipe(configure);
Clone a builder instance in order to create an extended version of it.
import apion from 'apion';
const config = apion.config()
.url('https://example.com');
const extended = config.extend()
.path('api');
Used to construct groups of nested builders. Inherits all methods from ConfigBuilder
.
Override or set the builder's constructor
to be used to provide dynamic context properties.
constructor
Function: A callback used to dynamically set the context for nested builders (see constructor).import apion from 'apion';
apion.group('auth')
.ctor(token => ({ token }));
Add a nested builder to a GroupBuilder
or ActionBuilder
.
name
String: A name to override the name on the builder.builder
GroupBuilder | ActionBuilder: A builder to be nested under this builder instance. If no name is provided, the name provided when constructing the builder will be used.Build an API client with this builder as the root node.
fetch
Function: A replacement for the fetch
instance used under the hood when making requests (defaults to cross-fetch
).Used to construct action builders for sending requests over the network. Inherits all methods from GroupBuilder
.
Override or set the builder's constructor
to be used to provide dynamic context properties.
constructor
Function: A callback used to dynamically set the context for nested builders (see constructor).requestBuilder
RequestBuilder: An instance of a RequestBuilder
used to inject a simple builder pattern when constructing complex request bodies (see requestBuilder).import apion from 'apion';
import { json } from 'apion/helpers';
// JSON.stringify() request body
// JSON.parse() response body
// set 'Content-Type: application/json' header
apion.config().use(json);
FAQs
Light and flexible API client creation library.
We found that apion 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.
Security News
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.