Hey there! This package is no longer maintained. We've learned a lot while beta testing this API client and built a brand new, better @foxy.io/sdk
package as a result. Check out our upgrade guide for more details. Thank you to everyone who participated in our beta program and we hope that you will enjoy working with the new tools from Team Foxy :)
npm i @foxy.io/node-api
const { FoxyApi } = require("@foxy.io/node-api");
With TypeScript or Node v13+ you can also use ES Modules:
import { FoxyApi } from "@foxy.io/node-api";
const foxy = new FoxyApi({
clientId: "client_MY-CLIENT-ID",
clientSecret: "long-alphanumeric-client-secret",
refreshToken: "long-alphanumeric-refresh-token",
});
If you don't provide the full configuration, our hAPI client will look for the missing values in the env vars. At the moment, the following variables are available:
FOXY_API_CLIENT_ID # config.clientId
FOXY_API_CLIENT_SECRET # config.clientSecret
FOXY_API_REFRESH_TOKEN # config.refreshToken
You can also specify the API version you'd like to work with. At the moment only version 1 is supported and used by default. It's recommended to explicitly set the version because the default value may change in the future:
const foxy = new FoxyApi({
// ...
version: "1",
});
By default, FoxyApi
will cache access tokens in memory. You can change that by providing an alternative caching mechanism, such as DiskCache
that comes with this package:
const { DiskCache } = FoxyApi.cache;
const foxy = new FoxyApi({
// ...
cache: new DiskCache("./path/to/cache"),
});
You can also combine various cache providers together. It can be useful in serverless environments where your cloud function may benefit from using in-memory cache while running and save a request on cold start by reading the most recent data from disk cache:
const { MemoryCache, MixedCache, DiskCache } = FoxyApi.cache;
const foxy = new FoxyApi({
// ...
cache: new MixedCache(new MemoryCache(), new DiskCache("./path/to/cache")),
});
If you'd like to see debug messages or silence the logger completely, change the logger on initialization:
const foxy = new FoxyApi({
// ...
logLevel: "silly", // log everything
});
const foxy = new FoxyApi({
// ...
silent: true, // don't log errors and such to console at all
});
const store = foxy.follow("fx:stores").follow(8);
// for store there's also a root rel:
const store = foxy.follow("fx:store");
const store = await foxy.fetchRaw({
url: new URL("/stores/8", FoxyApi.endpoint),
});
await store.fetch({ method: "GET" });
// or simply:
await store.fetch();
fetch
accepts a few parameters, including query
and zoom
, which can be used for searching, filtering, or otherwise modifying the request. For instance, here's an example showing how to retrieve a specific cart by ID:
const getCart = async (id) => {
const carts = await store.follow("fx:carts").fetch({
query: { id },
zoom: { items: ["item_options"] },
});
return carts._embedded["fx:carts"][0] || {};
};
By default our client will try to leverage the built-in resolvers to make as few network calls as possible when following relations. These resolvers aren't perfect yet and even though we have a failsafe mechanism that runs a full tree traversal on failure, silent errors are still a possibility (e.g. when the resolved path is valid, but points to a wrong resource). In that case you can set FetchInit.skipCache
option to true
to disable smart path resolution:
await store.fetch({
// ...
skipCache: true,
});
await store.fetch({
method: "PATCH",
body: { store_name: "New Store Name" },
});
// or using a serialized payload:
await store.fetch({
method: "PATCH",
body: '{ "store_name": "New Store Name" }',
});
await store.fetch({ method: "DELETE" });
The HMAC validation is recommended to prevent a malicious user from tampering with your add-to-cart links and forms. Though you could also use the Foxy's webhooks (both the pre-payment and the post-payment webhooks) to validate orders, using our hmac link/form signing is recommend where possible. Refer to HMAC Product Verification: Locking Down your Add-To-Cart Links and Forms for technical details.
The HMAC signer utility is provided as part of the set of tools available in the FoxyAPI
. It is available as the hmacSign
property of FoxyApi
instance.
The FoxySigner utility provides the following basic methods:
- hmacSign.htmlString(html: string): Signs an HTML snippet.
- hmacSign.htmlFile(path: string, output: string): Signs an HTML file asynchronously.
- hmacSign.url(url: string): Signs a URL.
There are also some more advanced methods that allow you to create signatures to be used in your fields and queries, thus integrating the signatures more directly in your application or templates.
Please, notice that HMAC VALIDATION IS ALL OR NOTHING. Signing individual name/value elements is only useful if you do sign all fields individually.
When a new FoxyApi instance is created, it holds an hmacSign
attribute, which is an instance of FoxySigner
.
If you are not using FoxyApi
, you can directly create an instance of FoxySigner
, but you will be required to call the setSecret
method in order to provide your key. Note that this is HIGHLY RECOMMENDED in situations where you don't actually need the full FoxyApi
. Principle of Least Privilege stuff; don't include full API access in a system that only needs the FoxySigner
secret.
This is the recommended approach, unless you actually need the rest of FoxyApi
.
import { FoxySigner } from "@foxy.io/utils/signer";
const hmacSign = new FoxySigner();
hmacSign.setSecret("long-alphanumeric-client-secret");
Note, again, that unless you actually need FoxyApi
, you should use the above method.
import { FoxyApi } from "@foxy.io/node-api";
const foxy = new FoxyApi({
clientId: "client_MY-CLIENT-ID",
clientSecret: "long-alphanumeric-client-secret",
refreshToken: "long-alphanumeric-refresh-token",
});
const hmacSign = foxy.hmacSign;
The simplest method is to sign a full HTML page.
Note that this option imposes a performance hit if you are building your pages in runtime. In real-world usage, you'd want to ensure this method is not called on every pageload, for instance. You'd typically accomplish this by ensuring you have some degree of caching that prevents your pages from being dynamically built on every request.
hmacSign.setSecret("MySuperSecretKey");
const signedHTML = hmacSign.htmlString(myHTMLcode);
You may also sign static HTML files.
This operation is asynchronous.
hmacSign.setSecret("MySuperSecretKey");
const signedHTML = hmacSign.htmlFile(pathToInputFile, pathToOutputFile).then(callback);
When simple links it is simpler and more efficient to sign the query string.
Please, notice that the URL query must contain the product code.
Here is an example URL: https://yourdomain.foxycart.com/cart?name=Flute%20Warm-Up%20Book&code=warmups&price=1.99
Note that the code
parameter is required. If code
is not present, the URL will not be signed. (This goes for all links and forms.)
hmacSign.setSecret("MySuperSecretKey");
const signedURL = hmacSign.url(unsigedURL);
The signedURL
variable should be used as the link href
attribute.
Signing individual name/value elements is a more advanced topics. It will allow you to provide purchases of several products with a single click and to specify complex products.
You must, however, be sure to sign all the fields properly.
Please, refer to the documentation on how to use your signed values.
Here is how you obtain a signed name/value to use in your element.
hmacSign.setSecret("MySuperSecretKey");
const signedName = hmacSign.name(unsignedName, code, parentCode, value);
hmacSign.setSecret("MySuperSecretKey");
const signedValue = hmacSign.value(unsignedName, code, parentCode, value);
Please, be careful to use the signed value in the name or value attribute as described in the documentation.
Notably, the signed value is used for the option
elements in a select
element and also for radio buttons.
To get started, clone this repo locally and install the dependencies:
git clone https://github.com/foxy/foxy-node-api.git
npm install
Running tests:
npm run test # runs all tests and exits
npm run test:watch # looks for changes and re-runs tests as you code
Committing changes with commitizen:
git commit # precommit hooks will lint the staged files and help you format your message correctly