![Oracle Drags Its Feet in the JavaScript Trademark Dispute](https://cdn.sanity.io/images/cgdhsj6q/production/919c3b22c24f93884c548d60cbb338e819ff2435-1024x1024.webp?w=400&fit=max&auto=format)
Security News
Oracle Drags Its Feet in the JavaScript Trademark Dispute
Oracle seeks to dismiss fraud claims in the JavaScript trademark dispute, delaying the case and avoiding questions about its right to the name.
Fetch wrapper. Polyfills optional. Aborts, retries, intercepts all in 5kb
Simple (and tiny) fetch wrapper with nifty features such as intercepts, easy aborts, and retries, for everywhere - that's browser, react-native, and ES5/6 front-ends.
Yet another fetch wrapping library? Well, various fetch-wrapping libraries have some of the above features, but none have them all.
More importantly, almost all fetch wrapping libraries investigated include their polyfills right in the main packages (or don't include polyfills at all requiring you to find out what you're missing). Flighty has an opt-in polyfill for fetch (and tiny polyfills for AbortController and ES6 promise, because you'll likely need those, too if you don't have fetch), so you don't have to bloat your code if you don't absolutely need to.
Everything you'll need is included in Flighty, it's just a matter of figuring out what you need. Running in a fetch-unknown environment - use flighty/fetch. You know you'll already have a fetch but unsure of AbortController? Use flighty/abort. Supporting the latest and greatest? Just import plain ol' flighty.
<!-- no polyfills -->
<script src="https://unpkg.com/flighty"></script>
<!-- fetch, abort, and promise polyfills -->
<script src="https://unpkg.com/flighty/fetch"></script>
<!-- abort only polyfill -->
<script src="https://unpkg.com/flighty/abort"></script>
<script>
// no matter which package you choose
flighty.get('/somepath').then(...)
</script>
// no polyfill
var flighty = require('flighty');
// fetch, abort, and promise polyfills
var flighty = require('flighty/fetch')
// abort only polyfill
var flighty = require('flighty/abort')
// no polyfill
import flighty from "flighty";
// fetch, abort, and promise polyfills
import flighty from "flighty/fetch";
// abort only polyfill
import flighty from "flighty/abort";
Note: React Natives import from Flighty includes the AbortController polyfill. If React Native ever updates it's fetch, Flighty will remove this. If you do import flighty from "flighty/abort"
in React Native you'll get the same package as import flighty from "flighty"
, so it's recommended to do the latter.
Regardless of the package and implementation you choose, flighty is tiny. The biggest implementation (which is the browser build that has all polyfills) is less than 9kb minified and gzipped.
Drop in replacement for fetch. This works:
const res = await fetch('/somepath',{some options});
const res = await flighty.get('/somepath',{some options});
This works because Flighty returns the standard Response but with the addition of the flighty object.
The drop in replacement makes this library relatively simple to add/remove from your codebase. If you keep your use of the flighty object on the response limited to interceptors then refactoring Flighty into/out of your codebase becomes a breeze.
res.flighty = {
method, // the method you called flighty with - e.g. get, post, put
retryCount, // the number of times this request has been retried
json, // what you'd normally get from await res.json()
text, // what you'd normally get from await res.text()
// the values the original flighty request was called with
call: {
path,
options,
extra
},
// retry method - calls the same request you made the first time again - hello JWT 401s
retry: async ()
}
Flighty comes with two abort APIs. abortAll()
which cancels all ongoing requests and cancellation via an abortToken
(similar to axios cancellation token but easier!).
Aborting Fetch requests comes with a hairy, verbose AbortController API that requires you to construct, pass in a signal to the fetch, and then abort the controller like so:
const controller = new AbortController();
const req = fetch('/',{signal:controller.signal})
controller.abort();
try {
const res = await req;
} catch(err) {
// AbortError!
}
Flighty allows you to pass in a token (any Symbol) and then call abort(token)
to cancel the request.
const req = flighty.get('/',{abortToken:"my token"});
flighty.abort("my token");
try {
const res = await req;
} catch(err) {
// AbortError!
}
Tokens, like AbortController signals, can be used to abort multiple requests. Let Flighty automate the creation and management of AbortController's for your requests. Just pass in a token and your request is then easy to abort.
const abortToken = "some token";
const reqOne = flighty('/pathone',{abortToken})
const reqTwo = flighty('/pathtwo',{abortToken})
const reqThree = flighty('/paththree',{abortToken})
// cancel them all!
flighty.abort(abortToken);
Drop in replacement for anybody using Frisbee interceptors or fetch-intercept, but with a couple of extra things:
const interceptor = {
request: (path,options,extra,retryCount) => {
// extra is an immutable object of the data passed in when
// creating the request - e.g. flighty('/mypath',{myFetchOptions},{someExtraData})
// it doesn't get changed between interceptors if you modify it.
// retryCount is the number of times this request has been
// retried via res.flighty.retry() or by using the retry parameters
// and is also immutable between interceptors
return [path,options];
}
}
Here's an example interceptor object:
{
request: function (path, options, extra, retryCount) {
// remember - extra and retryCount are immutable and will
// be passed to each interceptor the same
return [path, options];
},
requestError: function (err) {
// Handle an error occurred in the request method
return Promise.reject(err);
},
response: function (response) {
// do something with response (or res.flighty!)
return response;
},
responseError: function (err) {
// Handle error occurred in the last ran requestInterceptor, or the fetch itself
return Promise.reject(err);
}
}
Flighty implements the same retry parameters found in fetch-retry but it adds two important features:
retryFn
that will be executed between retriesretries
- the maximum number of retries to perform on a fetch (default 0 - do not retry)
*retryDelay
- a timeout in ms to wait between retries (default 1000ms)
retryOn
- an array of HTTP status codes that you want to retry (default you only retry if there was a network error)
*retryFn
- a function that gets called in between the failure and the retry. This function is await
ed so you can do some asynchronous work before the retry. Combine this with retryOn:[401] and you've got yourself a(nother) recipe to refresh JWTs (more at the end of this README):
*Note: The retryDelay
parameter will be ignored if retryFn
is declared. If you're using retryFn
it's up to you to handle the delay, if any, between retries.
res = await flighty.get('/path-requiring-authentication',{
retries:1,
retryOn:[401],
retryFn:() => flighty.get('/path_to_refresh_you_token')
})
The Flighty object also has a retry method to make it simply to retry a request:
let res;
res = await flighty.get('/');
if(!res.ok && res.flighty.retryCount === 0) {
// try it one more time...
res = res.flighty.retry();
}
flighty
- default export - an instance of Flighty
. It has a create
method that can be used to instantiate other instances of Flighty
. The create
method accepts an object with can contain the following options:
baseURI
- the default URI use to prefix all your paths
headers
- an object containing default headers to send with every request
arrayFormat
- how to stringify array in passed body. See qs for available formats
Instances of Flighty
contain the following methods:
jwt(token)
- Set your Bearer Authorization header via this method. Pass in a token and Flighty will add the header for you, pass in something false-y and Flighty won't automatically add an auth header (in case you want to do it yourself)
auth(username,password)
- Set your Basic Authorization header via this method. Pass in a username and password and Flighty will add the header Authorization Basic bas64encoded username and password
for you, pass in something false-y and Flighty won't automatically add an auth header (in case you want to do it yourself)
abort
- method that accepts an abortToken to abort specific requests.
abortAll
- aborts all in-progress requests controlled by this instance.
HTTP wrapper methods (e.g. get, post, put, delete, etc) require a path
string, and accept two optional plain object arguments - options
and extra
Accepted method arguments:
path
required - the path for the HTTP request (e.g. /v1/login
, will be prefixed with the value of baseURI
if set)
options
optional - everything you'd pass into fetch's init plus optional abortToken
and retry parameters: retries
,retryFn
,retryDelay
,retryFn
extra
optional object - sometimes you have some meta data about a request that you may want interceptors or other various listeners to know about. Whatever you pass in here will come out the other end attached the to res.flighty.call
object and will also be passed along to all interceptors along the way
List of available HTTP methods:
api.get(path, options, extra)
- GETapi.head(path, options, extra)
- HEADapi.post(path, options, extra)
- POSTapi.put(path, options, extra)
- PUTapi.del(path, options, extra)
- DELETEapi.options(path, options, extra)
- OPTIONSapi.patch(path, options, extra)
- PATCHregisterInterceptor
- method that accepts an interceptor
and calls it on every fetch. Returns a function that allows you to remove it:
const api = new Flighty({});
const undo = api.registerInterceptor(someInterceptor);
await api.get('/'); // your interceptor will run
undo(); // your interceptor is gone!
removeInterceptor
- method that accepts an reference to interceptor and removes it
clearInterceptors
- removes all interceptors.
For convenience, Flighty has exposed an interceptor
property that has the same API as frisbee to register and unregister interceptors.
Keep it short - don't mock Flighty. It'd be over-complicated and unnecessary to mock it's features - so just mock the fetch and let Flighty do it's thing in your tests. I recommend jest-fetch-mock.
Don't know about you, but I found it annoying that I always had to check res.ok
to handle my error conditions - why not just throw if the response isn't ok? Interceptor!
Before:
const res = await fetch('/');
if(res.ok) {
// do some good stuff
} else {
// do some bad stuff
}
After:
flighty.registerInterceptor({
response:res => {
if(!res.ok) {
throw res;
}
return res;
}
});
// Now all my responses throw if I get a non-2xx response
try {
const res = await flighty.get('/');
} catch(e) {
// response returned non-2xx
}
const interceptor = {
request: (path,options) => {
flighty.jwt(path === REFRESH_ENDPOINT ? myRefreshToken : myAccessToken);
return [path,options]
},
response: async res => {
// our only job when hitting the login path is to set the tokens locally
if(path === LOGIN_ENDPOINT) {
if(res.ok) {
// store your access and refresh tokens
setTokensLocally()
}
return res;
}
// if we get a 401 and we're not trying to refresh and this is our first retry
if (res.status === 401 && path !== REFRESH_TOKEN_PATH && res.flighty.retryCount === 0) {
try {
await requestToRefreshYourToken();
return response.flighty.retry()
} catch(e) {
// log the user out
}
}
return res;
}
}
// same request interceptor as before
const interceptor = {
request:(path,options) => {
flighty.jwt(path === REFRESH_ENDPOINT ? myRefreshToken : myAccessToken);
return [path,options]
}
}
const authenticatedApiRequest = (path,options,extra) => {
return flighty(
path,
{
...options,
// retry the request 1 time
retries:1,
// if a 401 or network error is received
retryOn:[401],
// and request a new token in between
retryFn:() => flighty.get(REFRESH_TOKEN_ENDPOINT)
}
extra)
};
const myRequest = authenticatedApiRequest('/some-path-requiring-authentication');
PRs and ideas welcome!
FAQs
Fetch wrapper. Polyfills optional. Aborts, retries, intercepts all in 5kb
The npm package flighty receives a total of 0 weekly downloads. As such, flighty popularity was classified as not popular.
We found that flighty 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
Oracle seeks to dismiss fraud claims in the JavaScript trademark dispute, delaying the case and avoiding questions about its right to the name.
Security News
The Linux Foundation is warning open source developers that compliance with global sanctions is mandatory, highlighting legal risks and restrictions on contributions.
Security News
Maven Central now validates Sigstore signatures, making it easier for developers to verify the provenance of Java packages.