Security News
38% of CISOs Fear They’re Not Moving Fast Enough on AI
CISOs are racing to adopt AI for cybersecurity, but hurdles in budgets and governance may leave some falling behind in the fight against cyber threats.
@canopytax/fetcher
Advanced tools
Canopy's wrapper around the [`window.fetch` api](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) . It automatically provides:
Canopy's wrapper around
the window.fetch
api
. It automatically provides:
Content-Type
header to be application/json
for all requests
that have a body.passThrough401
property on the fetch
configuration object. (For
example, fetchAsObservable('/url', {passThrough401: true})
). When
the passThrough401
property is set, the auth token will not be refreshed and
the request will be passed through like normal.Fetcher has an opt-in caching feature (fetchWithSharedCache
) that allows
redundant calls to the same URL without the performance/load penalty of making
many ajax requests. It also gets even fancier by pushing changes to cached
objects out to everyone who's interested in them. This is done by detecting if a
PUT/PATCH is changing an object that other parts of the app have cached. If so,
the response to the PUT/PATCH will be pushed out to all subscribers. See
the fetchWithSharedCache
and forceBustCache
APIs
Returns Promise<any>
The default exported value, fetcher
, is a function that simply wraps
the fetch
api. You should call fetcher just as if you were calling fetch. The
only known limitation that we know of right now is that if you call fetcher with
the first parameter being
a Request object,
fetcher will not be able to do its auth token refreshes for you if a 401 http
status is returned for the server.
import fetcher from "fetcher!sofe";
fetcher("/my-url").then((res) => res);
Returns Observable<any>
Takes almost the exact same params as fetcher
but returns an RxJS 6 observable
rather than a promise. If response.ok
, it will by default automatically try to
return JSON with a fallback of text. To modify the default return behavior, you
can including an optional responseType
(ex. responseType: 'blob'
) in the
second argument's object to explicitly specify the fetch response body
operation (i.e. .json()
, .text()
, .blob()
, etc.).
import { fetchAsObservable } from "fetcher!sofe";
fetchAsObservable("/my-url").subscribe((res) => res);
Returns (url: string, options: {}) => Promise<any>
This function will return a new fetch function, while also instantiating a new abort controller. That abort signal will be tied to all uses of this fetch.
The fetch function will also have a new property method on it called abort
,
with each use subsequently instantiating a new abort controller (basically
this means you can reuse the fetch as much as you'd like).
Abort all fetches:
import { fetchAbort } from "fetcher!sofe";
// only one fetch is needed if you're going to abort them together
const runFetch = fetchAbort();
runFetch("/my-url-1").then((res) => res);
runFetch("/my-url-2").then((res) => res);
// abort all
runFetch.abort();
Abort a single fetch:
import { fetchAbort } from "fetcher!sofe";
// controlling each abort differently requires multiple fetches
const runFetch1 = fetchAbort();
const runFetch2 = fetchAbort();
runFetch1("/my-url-1").then((res) => res);
runFetch2("/my-url-2").then((res) => res);
// abort only "runFetch1"
runFetch1.abort();
React hook version of fetchAbort
. Hook returns an array of two variables:
(url: string, options: {}) => Promise<any>
- fetcher method() => void
- method to abort fetchWhen fetching on component mount, fetching/aborting is as simple as throwing them in a useEffect:
import React, { useEffect } from 'react';
import { useFetchAbort } from 'fetcher!sofe';
function MyComponent() {
const [runFetch, runAbort] = useFetchAbort();
useEffect(() => {
runFetch('/my-url').then(res => res);
() => runAbort();
});
return (...);
}
(Keep in mind React 18's quirk of running useEffect
twice on initial
render...)
If you need more finite control of aborting, this works the same way as
fetchAbort
:
import React, { useEffect } from 'react';
import { useFetchAbort } from 'fetcher!sofe';
function MyComponent() {
const [runFetch1, runAbort1] = useFetchAbort();
const [runFetch2, runAbort2] = useFetchAbort();
async function getOne() {
const res = await runFetch1('/my-url-1');
}
async function getTwo() {
const res = await runFetch2('/my-url-2');
}
useEffect(() => {
// only abort "runFetch1"
() => runAbort1();
});
return (...);
}
By default, the opts.abortOnUnmount
argument is set to true
, so you don't
need to manually abort - the hook will do that for you 😉.
import React, { useEffect } from 'react';
import { useFetchAbort } from 'fetcher!sofe';
function MyComponent() {
const [runFetch] = useFetchAbort();
async function getTheThing() {
const res = await runFetch('/my-url');
}
return (...);
}
You can set opts.abortOnUnmount
to false if you need to manually control how the
fetch aborts.
A helper that allows you to run fetcher on component mount. useFetcher
takes
two arguments the url and optionalProps:
api/clients/${contactId}?include=users,contacts,tags,contact_for,contact_sources
)
,fire
method
that allows you to "fire when ready" and not immediately.
}const { loading, results, error, refetch } = useFetcher(
`api/clients/${contactId}?include=users,contacts,tags,contact_for,contact_sources`,
{
pluck: "clients",
}
);
A more powerful version of useFetcher
but requires more configuration.
useObservable
takes two arguments an rxjs observable and the initialResults
fire
method
that allows you to "fire when ready" and not immediately.NOTE you must memoize your observable if dynamically created
const observable = useMemo(
() => {
return fromEvent(...).pipe(
tap(...),
debounceTime(...),
switchMap(...),
pluck(...)
)
},
[]
)
const { loading, results, error, resubscribe } = useObservable(observable, [])
import fetcher, {
onPusher,
fetchWithSharedCache,
forceBustCache,
fetchAsObservable,
fetchWithProgress,
} from "fetcher";
import canopyUrls from "canopy-urls!sofe";
import { skipLast, last } from "rxjs/operators";
fetcher(`${canopyUrls.getWorkflowUrl()}/api/users/0`)
.then((resp) => {
if (resp.ok) {
resp
.json() // or .text()
.then((json) => {
console.log(json.users);
})
.catch((ex) => {
throw ex;
});
} else {
throw new Error(
`Couldn't get user -- server responded with ${resp.status}`
);
}
})
.catch((ex) => {
throw ex;
});
fetchAsObservable(`${canopyUrls.getWorkflowUrl()}/api/users/0`).subscribe(
(user) => {
/* user json object */
},
(error) => {
throw error;
}
);
const formData = new FormData();
formData.append("file", document.getElementById("file-input").files[0]);
const withProgress = fetchWithProgress(
`${canopyUrls.getAPIUrl()}/contacts/1/files`,
{
method: "post",
body: formData,
}
);
withProgress.pipe(skipLast(1)).subscribe((progressEvent) => {
console.log(
`File upload progress is now at ${progressEvent.overallProgress}`
);
});
withProgress.pipe(last()).subscribe((data) => {
console.log(`Response from server`, data);
});
withProgress.connect();
const subscription = fetchWithSharedCache(
`${canopyUrls.getWorkflowUrl()}/api/clients/1/engagements/1`
).subscribe(
(engagement) => {
// this is the json from the response, not the response itself
console.log(engagement.id);
},
(err) => {
throw err;
}
);
setTimeout(subscription.dispose, 10000);
forceBustCache(`${canopyUrls.getWorkflowUrl()}/api/clients/1`)
.then((resp) => {
console.log("cache was busted!");
resp
.json()
.then((json) => console.log(json))
.catch((ex) => {
throw ex;
});
})
.catch((ex) => {
throw ex;
});
const disposable = onPusher("message-type").subscribe((data) =>
console.log(data)
);
disposable.dispose();
fetcher(params)
: The default exported value, fetcher
, is a function that
simply wraps the fetch
api. You should call fetcher just as if you were
calling fetch. The only known limitation that we know of right now is that if
you call fetcher with the first parameter being
a Request object,
fetcher will not be able to do its auth token refreshes for you if a 401 http
status is returned for the server.
fetchAsObservable(params)
: Returns an RxJS 6 Observable. Takes almost the
exact same params as fetcher
but returns an RxJS 6 observable rather than a
promise. If response.ok
, it will by default automatically try to return JSON
with a fallback of text. To modify the default return behavior, you can
including an optional responseType
(ex. responseType: 'blob'
) in the
second argument's object to explicitly specify the fetch response body
operation (i.e. .json()
, .text()
, .blob()
, etc.).
onPusher(message-type)
: Subscribe to server push events. Unsubscribe by
disposing the subscriptions. The returned observable is deferred.
fetchWithSharedCache(url, subscriptionDuration, forceBust)
: This function
takes in the string url
, a string or function subscriptionDuration
, and an
optional boolean forceBust
. It will either make a network request or return
the cached object, depending on if the cache already has the object for that
URL. Here are some things to note:
dispose()
your subscription to the observable when you are done with itResponse
object, but a json object. This is
different than when you do fetcher(url).then(response => ...)
, which
indeed gives you a Response object.subscriptionDuration
parameter is either a string (recommended) or a
function. Whenever a hashchange event fires, fetcher will check to see if
the subscriptionDuration has finished. It will check this for all
subscriptions
to the same url
and if any of them says that the duration has ended,
then it will bust the cache and call onCompleted
on the observable which
will end all of the subscriptions for that url. The reasoning for this
is that
the supported use case is subscribing to a data source as long as a
certain SPA route is active, and then not caring about updates to the data
once that SPA route is no longer active.
fetcher
GET on a url that is cached, when
the response comes back it will automatically update the cache and its
subscribers.forceBustCache(url)
: This function will forcibly bust the cache for a URL,
and also trigger a re-fetch of the URL so that all subscribers to the cache
are notified of the new change. It returns a promise just the same as if you
had done fetch(url)
.
bustCacheForSubscriptionDuration(subscriptionDuration, [refetchCachedUrls = false])
:
Will bust the cache for all subscriptionDuration
key/url hash that ===
what
you pass in. By default, it will notify all observers that the observable
returned from fetchWithSharedCache is completed., but optionally you can
make refetchCachedUrls = true
to have all subjects refetch their data and
update the cache instead of being removed.
Note: will only work for subscriptionDurations
that are typeof string
.
Also see fetchWithSharedCache
's subscriptionDuration
for more info
fetchWithProgress(url, options)
: This function uses XMLHttpRequest to call
an API so that the progress of the API request can be tracked. It returns
an ConnectableObservable
that will produce
a stream
of ProgressEvent
objects, except the last value is the response data the server responds with.
The ProgressEvent objects are also modified to include an overallProgress
property,
which is a number between 0 and 1 which approximates what percentage of the
overall user wait time has occurred. Note that since it is a
ConnectableObservable, you have to call connect()
on it after you subscribe.
The fetchWithProgress
function takes two arguments: a string url
and an
object options
. The following properties are supported in options:
method
(required): a string for the HTTP method of the requestheaders
(optional): an object where the keys are header names and the
values are header values.body
(optional): Something that you can call xhr.send(body). This is
usually a string
or FormData
object.
The FormData object is useful when you want to upload files.baseServerLatency
(optional): A number of milliseconds to use when
approximating the overallProgress
of the api request. This number
represents how long it takes on
average for the server to handle a small request. For example, file
uploads incur a set amount of latency on the server even when the file
size is very small.
baseServerLatency
defaults to 1500 ms, which seems about right for file
uploads.fetchWithSharedCache(
`https://workflow.canopytax.com/api/clients/1`,
`clients/1`
).subscribe((json) => {
console.log(`Client name is ${json.clients.name}`);
});
fetchWithSharedCache(`https://workflow.canopytax.com/api/clients/1`, () => {
const pleaseEndMySubscription = true;
return pleaseEndMySubscription;
}).subscribe((json) => {
console.log(`Client name is ${json.clients.name}`);
});
redirectOrCatch() is a shortcut for asyncStacktrace -> check for 403/404 and maybe redirect -> else catchSyncStacktrace. It checks the request error for 404/403, logs to console the failure, and redirect the user to relevant 404/403 page
import {redirectOrCatch} from 'fetcher!sofe';
useEffect(() => {
const sub = getContact().subscribe(
successFunction(),
asyncStacktrace(err => {
if (err.status === 404 || err.status === 403) {
console.info(`api/contact returned ${err.status}, redirecting`);
window.history.replaceState(null, "", `/#/${err.status}`);
} else catchSyncStacktrace(err);
}
)
return () => sub.unsubscribe()
}, [])
// becomes
useEffect(() => {
const sub = getContact().subscribe(
successFunction(),
redirectOrCatch()
)
return () => sub.unsubscribe()
}, [])
FAQs
Canopy's wrapper around the [`window.fetch` api](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) . It automatically provides:
The npm package @canopytax/fetcher receives a total of 1 weekly downloads. As such, @canopytax/fetcher popularity was classified as not popular.
We found that @canopytax/fetcher demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 8 open source maintainers 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
CISOs are racing to adopt AI for cybersecurity, but hurdles in budgets and governance may leave some falling behind in the fight against cyber threats.
Research
Security News
Socket researchers uncovered a backdoored typosquat of BoltDB in the Go ecosystem, exploiting Go Module Proxy caching to persist undetected for years.
Security News
Company News
Socket is joining TC54 to help develop standards for software supply chain security, contributing to the evolution of SBOMs, CycloneDX, and Package URL specifications.