@adobe/fetch
Advanced tools
Comparing version 0.3.4 to 3.1.3
106
package.json
{ | ||
"name": "@adobe/fetch", | ||
"version": "0.3.4", | ||
"description": "Call Adobe APIs", | ||
"main": "dist/server.js", | ||
"browser": "dist/client.js", | ||
"version": "3.1.3", | ||
"description": "Light-weight Fetch implementation transparently supporting both HTTP/1(.1) and HTTP/2", | ||
"main": "src/index.js", | ||
"scripts": { | ||
"test": "nyc mocha", | ||
"lint": "./node_modules/.bin/eslint .", | ||
"semantic-release": "semantic-release" | ||
}, | ||
"mocha": { | ||
"timeout": "5000", | ||
"recursive": "true", | ||
"reporter": "mocha-multi-reporters", | ||
"reporter-options": "configFile=.mocha-multi.json" | ||
}, | ||
"engines": { | ||
"node": ">=12.0" | ||
}, | ||
"types": "src/index.d.ts", | ||
"exports": { | ||
"import": "./src/index.mjs", | ||
"require": "./src/index.js" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/adobe/adobe-fetch.git" | ||
"url": "https://github.com/adobe/fetch" | ||
}, | ||
"author": "", | ||
"license": "Apache-2.0", | ||
"bugs": { | ||
"url": "https://github.com/adobe/adobe-fetch/issues" | ||
"url": "https://github.com/adobe/fetch/issues" | ||
}, | ||
"engines": { | ||
"node": ">=6.0.0" | ||
}, | ||
"scripts": { | ||
"lint": "eslint src test sample index*.js", | ||
"test": "jest", | ||
"build": "webpack" | ||
}, | ||
"homepage": "https://github.com/adobe/fetch#readme", | ||
"keywords": [ | ||
"jwt", | ||
"api", | ||
"fetch", | ||
"adobe", | ||
"adobeio" | ||
"whatwg", | ||
"Fetch API", | ||
"http", | ||
"https", | ||
"http2", | ||
"h2", | ||
"promise", | ||
"async", | ||
"request", | ||
"RFC 7234", | ||
"7234", | ||
"caching", | ||
"cache" | ||
], | ||
"author": "Adobe Inc.", | ||
"license": "Apache-2.0", | ||
"publishConfig": { | ||
"registry": "https://registry.npmjs.org" | ||
}, | ||
"dependencies": { | ||
"@adobe/jwt-auth": "^0.3.1", | ||
"debug": "^4.3.1", | ||
"dotenv": "^8.2.0", | ||
"node-fetch": "^2.6.1", | ||
"node-persist": "^3.1.0", | ||
"uuid": "^8.3.1" | ||
"debug": "4.3.4", | ||
"http-cache-semantics": "4.1.0", | ||
"lru-cache": "7.14.0" | ||
}, | ||
"devDependencies": { | ||
"eslint": "^7.14.0", | ||
"eslint-config-prettier": "^6.15.0", | ||
"eslint-plugin-prettier": "^3.1.4", | ||
"jest": "^26.6.3", | ||
"prettier": "^2.2.0", | ||
"webpack": "^5.6.0", | ||
"webpack-cli": "^4.2.0", | ||
"webpack-node-externals": "^2.5.2" | ||
"@semantic-release/changelog": "6.0.1", | ||
"@semantic-release/git": "10.0.1", | ||
"chai": "4.3.6", | ||
"chai-as-promised": "7.1.1", | ||
"chai-bytes": "0.1.2", | ||
"chai-iterator": "3.0.2", | ||
"eslint": "8.23.1", | ||
"eslint-config-airbnb-base": "15.0.0", | ||
"eslint-plugin-header": "3.1.1", | ||
"eslint-plugin-import": "2.26.0", | ||
"formdata-node": "4.4.1", | ||
"lint-staged": "13.0.3", | ||
"mocha": "10.0.0", | ||
"mocha-multi-reporters": "1.5.1", | ||
"nock": "13.2.9", | ||
"nyc": "15.1.0", | ||
"parse-cache-control": "1.0.1", | ||
"pem": "1.14.6", | ||
"semantic-release": "19.0.5", | ||
"sinon": "14.0.0", | ||
"stream-buffers": "3.0.2" | ||
}, | ||
"jest": { | ||
"coverageDirectory": "./coverage/", | ||
"collectCoverage": true | ||
"lint-staged": { | ||
"*.js": "eslint" | ||
} | ||
} |
665
README.md
@@ -1,266 +0,567 @@ | ||
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) | ||
[![Version](https://img.shields.io/npm/v/@adobe/fetch.svg)](https://npmjs.org/package/@adobe/fetch) | ||
[![Downloads/week](https://img.shields.io/npm/dw/@adobe/fetch.svg)](https://npmjs.org/package/@adobe/fetch) | ||
[![Build Status](https://travis-ci.com/adobe/adobe-fetch.svg?branch=master)](https://travis-ci.com/adobe/adobe-fetch) | ||
[![codecov](https://codecov.io/gh/adobe/adobe-fetch/branch/master/graph/badge.svg)](https://codecov.io/gh/adobe/adobe-fetch) | ||
<div align="center"> | ||
<img src="banner.jpeg" alt="Adobe Fetch"/> | ||
<br> | ||
<p>Light-weight <a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API">Fetch API</a> implementation transparently supporting both <b>HTTP/1(.1)</b> and <b>HTTP/2</b></p> | ||
<a href="https://codecov.io/gh/adobe/fetch"><img src="https://img.shields.io/codecov/c/github/adobe/fetch.svg" alt="codecov"></a> | ||
<a href="https://circleci.com/gh/adobe/fetch"><img src="https://img.shields.io/circleci/project/github/adobe/fetch.svg" alt="CircleCI"></a> | ||
<a href="https://github.com/adobe/fetch/blob/main/LICENSE.txt"><img src="https://img.shields.io/github/license/adobe/fetch.svg" alt="GitHub license"></a> | ||
<a href="https://github.com/adobe/fetch/issues"><img src="https://img.shields.io/github/issues/adobe/fetch.svg" alt="GitHub issues"></a> | ||
<a href="https://lgtm.com/projects/g/adobe/fetch"><img src="https://img.shields.io/lgtm/grade/javascript/g/adobe/fetch.svg?logo=lgtm&logoWidth=18" alt="LGTM Code Quality Grade: JavaScript"></a> | ||
<a href="https://renovatebot.com/"><img src="https://img.shields.io/badge/renovate-enabled-brightgreen.svg" alt="Renovate enabled"></a> | ||
<a href="https://github.com/semantic-release/semantic-release"><img src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg" alt="semantic-release"></a> | ||
<a href="https://packagephobia.now.sh/result?p=@adobe/fetch"><img src="https://badgen.net/packagephobia/install/@adobe/fetch" alt="Install size"></a> | ||
<a href="https://www.npmjs.com/package/@adobe/fetch"><img src="https://img.shields.io/npm/v/@adobe/fetch" alt="Current version"></a> | ||
</div> | ||
[![Language grade: JavaScript](https://img.shields.io/lgtm/grade/javascript/g/adobe/adobe-fetch.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/adobe/adobe-fetch/context:javascript) | ||
--- | ||
# adobe-fetch | ||
<!-- TOC --> | ||
- [About](#about) | ||
- [Features](#features) | ||
- [Installation](#installation) | ||
- [API](#api) | ||
- [Context](#context) | ||
- [Common Usage Examples](#common-usage-examples) | ||
- [Access Response Headers and other Meta data](#access-response-headers-and-other-meta-data) | ||
- [Fetch JSON](#fetch-json) | ||
- [Fetch text data](#fetch-text-data) | ||
- [Fetch binary data](#fetch-binary-data) | ||
- [Specify a timeout for a `fetch` operation](#specify-a-timeout-for-a-fetch-operation) | ||
- [Stream an image](#stream-an-image) | ||
- [Post JSON](#post-json) | ||
- [Post JPEG image](#post-jpeg-image) | ||
- [Post form data](#post-form-data) | ||
- [GET with query parameters object](#get-with-query-parameters-object) | ||
- [Cache](#cache) | ||
- [Advanced Usage Examples](#advanced-usage-examples) | ||
- [HTTP/2 Server Push](#http2-server-push) | ||
- [Force HTTP/1(.1) protocol](#force-http11-protocol) | ||
- [HTTP/1.1 Keep-Alive](#http11-keep-alive) | ||
- [Self-signed Certificates](#self-signed-certificates) | ||
- [Set cache size limit](#set-cache-size-limit) | ||
- [Disable caching](#disable-caching) | ||
- [Set a custom user agent](#set-a-custom-user-agent) | ||
- [More examples](#more-examples) | ||
- [Development](#development) | ||
- [Build](#build) | ||
- [Test](#test) | ||
- [Lint](#lint) | ||
- [Troubleshooting](#troubleshooting) | ||
- [Acknowledgement](#acknowledgement) | ||
- [License](#license) | ||
<!-- /TOC --> | ||
Call Adobe APIs | ||
--- | ||
## Goals | ||
## About | ||
Make calling Adobe APIs a breeze! | ||
`@adobe/fetch` in general adheres to the [Fetch API Specification](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API), implementing a subset of the API. However, there are some notable deviations: | ||
This package will handle JWT authentication, token caching and storage. | ||
Otherwise it works exactly as [fetch](https://github.com/bitinn/node-fetch). | ||
* `Response.body` returns a Node.js [Readable stream](https://nodejs.org/api/stream.html#stream_readable_streams). | ||
* `Response.blob()` is not implemented. Use `Response.buffer()` instead. | ||
* `Response.formData()` is not implemented. | ||
* Cookies are not stored by default. However, cookies can be extracted and passed by manipulating request and response headers. | ||
* The following values of the `fetch()` option `cache` are supported: `'default'` (the implicit default) and `'no-store'`. All other values are currently ignored. | ||
* The following `fetch()` options are ignored due to the nature of Node.js and since `@adobe/fetch` doesn't have the concept of web pages: `mode`, `referrer`, `referrerPolicy`, `integrity` and `credentials`. | ||
* The `fetch()` option `keepalive` is not supported. But you can use the `h1.keepAlive` context option, as demonstrated [here](#http11-keep-alive). | ||
This library now works in the browser as well, see information below. | ||
`@adobe/fetch` also supports the following extensions: | ||
### Installation | ||
* `Response.buffer()` returns a Node.js `Buffer`. | ||
* `Response.url` contains the final url when following redirects. | ||
* The `body` that can be sent in a `Request` can also be a `Readable` Node.js stream, a `Buffer`, a string or a plain object. | ||
* There are no forbidden header names. | ||
* The `Response` object has an extra property `httpVersion` which is one of `'1.0'`, `'1.1'` or `'2.0'`, depending on what was negotiated with the server. | ||
* The `Response` object has an extra property `fromCache` which determines whether the response was retrieved from cache. | ||
* The `Response` object has an extra property `decoded` which determines whether the response body was automatically decoded (see Fetch option `decode` below). | ||
* `Response.headers.plain()` returns the headers as a plain object. | ||
* The Fetch option `follow` allows to limit the number of redirects to follow (default: `20`). | ||
* The Fetch option `compress` enables transparent gzip/deflate/br content encoding (default: `true`). | ||
* The Fetch option `decode` enables transparent gzip/deflate/br content decoding (default: `true`). | ||
Note that non-standard Fetch options have been aligned with [node-fetch](https://github.com/node-fetch/node-fetch) where appropriate. | ||
## Features | ||
* [x] supports reasonable subset of the standard [Fetch specification](https://fetch.spec.whatwg.org/) | ||
* [x] Transparent handling of HTTP/1(.1) and HTTP/2 connections | ||
* [x] [RFC 7234](https://httpwg.org/specs/rfc7234.html) compliant cache | ||
* [x] Support `gzip/deflate/br` content encoding | ||
* [x] HTTP/2 request and response multiplexing support | ||
* [x] HTTP/2 Server Push support (transparent caching and explicit listener support) | ||
* [x] overridable User-Agent | ||
* [x] low-level HTTP/1.* agent/connect options support (e.g. `keepAlive`, `rejectUnauthorized`) | ||
## Installation | ||
> **Note**: | ||
> | ||
> As of v2 Node version >= 12 is required. | ||
```bash | ||
$ npm install @adobe/fetch | ||
``` | ||
npm install --save @adobe/fetch | ||
## API | ||
Apart from the standard Fetch API | ||
* `fetch()` | ||
* `Request` | ||
* `Response` | ||
* `Headers` | ||
* `Body` | ||
`@adobe/fetch` exposes the following extensions: | ||
* `context()` - creates a new customized API context | ||
* `reset()` - resets the current API context, i.e. closes pending sessions/sockets, clears internal caches, etc ... | ||
* `onPush()` - registers an HTTP/2 Server Push listener | ||
* `offPush()`- deregisters a listener previously registered with `onPush()` | ||
* `clearCache()` - clears the HTTP cache (cached responses) | ||
* `cacheStats()` - returns cache statistics | ||
* `noCache()` - creates a customized API context with disabled caching (_convenience_) | ||
* `h1()` - creates a customized API context with enforced HTTP/1.1 protocol (_convenience_) | ||
* `keepAlive()` - creates a customized API context with enforced HTTP/1.1 protocol and persistent connections (_convenience_) | ||
* `h1NoCache()` - creates a customized API context with disabled caching and enforced HTTP/1.1 protocol (_convenience_) | ||
* `keepAliveNoCache()` - creates a customized API context with disabled caching and enforced HTTP/1.1 protocol with persistent connections (_convenience_) | ||
* `createUrl()` - creates a URL with query parameters (_convenience_) | ||
* `timeoutSignal()` - ceates a timeout signal (_convenience_) | ||
### Context | ||
An API context allows to customize certain aspects of the implementation and provides isolation of internal structures (session caches, HTTP cache, etc.) per API context. | ||
The following options are supported: | ||
```ts | ||
interface ContextOptions { | ||
/** | ||
* Value of `user-agent` request header | ||
* @default 'adobe-fetch/<version>' | ||
*/ | ||
userAgent?: string; | ||
/** | ||
* The maximum total size of the cached entries (in bytes). 0 disables caching. | ||
* @default 100 * 1024 * 1024 | ||
*/ | ||
maxCacheSize?: number; | ||
/** | ||
* The protocols to be negotiated, in order of preference | ||
* @default [ALPN_HTTP2, ALPN_HTTP1_1, ALPN_HTTP1_0] | ||
*/ | ||
alpnProtocols?: ReadonlyArray< ALPNProtocol >; | ||
/** | ||
* How long (in milliseconds) should ALPN information be cached for a given host? | ||
* @default 60 * 60 * 1000 | ||
*/ | ||
alpnCacheTTL?: number; | ||
/** | ||
* (HTTPS only, applies to HTTP/1.x and HTTP/2) | ||
* If not false, the server certificate is verified against the list of supplied CAs. An 'error' event is emitted if verification fails; err.code contains the OpenSSL error code. | ||
* @default true | ||
*/ | ||
rejectUnauthorized?: boolean; | ||
/** | ||
* Maximum number of ALPN cache entries | ||
* @default 100 | ||
*/ | ||
alpnCacheSize?: number; | ||
h1?: Http1Options; | ||
h2?: Http2Options; | ||
}; | ||
interface Http1Options { | ||
/** | ||
* Keep sockets around in a pool to be used by other requests in the future. | ||
* @default false | ||
*/ | ||
keepAlive?: boolean; | ||
/** | ||
* When using HTTP KeepAlive, how often to send TCP KeepAlive packets over sockets being kept alive. | ||
* Only relevant if keepAlive is set to true. | ||
* @default 1000 | ||
*/ | ||
keepAliveMsecs?: number; | ||
/** | ||
* (HTTPS only) | ||
* If not false, the server certificate is verified against the list of supplied CAs. An 'error' event is emitted if verification fails; err.code contains the OpenSSL error code. | ||
* @default true | ||
*/ | ||
rejectUnauthorized?: boolean; | ||
/** | ||
* (HTTPS only) | ||
* Maximum number of TLS cached sessions. Use 0 to disable TLS session caching. | ||
* @default 100 | ||
*/ | ||
maxCachedSessions?: number; | ||
} | ||
interface Http2Options { | ||
/** | ||
* Max idle time in milliseconds after which a session will be automatically closed. | ||
* @default 5 * 60 * 1000 | ||
*/ | ||
idleSessionTimeout?: number; | ||
/** | ||
* Enable HTTP/2 Server Push? | ||
* @default true | ||
*/ | ||
enablePush?: boolean; | ||
/** | ||
* Max idle time in milliseconds after which a pushed stream will be automatically closed. | ||
* @default 5000 | ||
*/ | ||
pushedStreamIdleTimeout?: number; | ||
/** | ||
* (HTTPS only) | ||
* If not false, the server certificate is verified against the list of supplied CAs. An 'error' event is emitted if verification fails; err.code contains the OpenSSL error code. | ||
* @default true | ||
*/ | ||
rejectUnauthorized?: boolean; | ||
}; | ||
``` | ||
### Common Usage | ||
## Common Usage Examples | ||
### Access Response Headers and other Meta data | ||
```javascript | ||
const { fetch } = require('@adobe/fetch'); | ||
const AdobeFetch = require('@adobe/fetch'); | ||
const fs = require('fs'); | ||
const config = { | ||
auth: { | ||
clientId: 'asasdfasf', | ||
clientSecret: 'aslfjasljf-=asdfalasjdf==asdfa', | ||
technicalAccountId: 'asdfasdfas@techacct.adobe.com', | ||
orgId: 'asdfasdfasdf@AdobeOrg', | ||
metaScopes: ['ent_dataservices_sdk'] | ||
} | ||
}; | ||
config.auth.privateKey = fs.readFileSync('private.key'); | ||
const resp = await fetch('https://httpbin.org/get'); | ||
console.log(resp.ok); | ||
console.log(resp.status); | ||
console.log(resp.statusText); | ||
console.log(resp.httpVersion); | ||
console.log(resp.headers.plain()); | ||
console.log(resp.headers.get('content-type')); | ||
``` | ||
const adobefetch = AdobeFetch.config(config); | ||
### Fetch JSON | ||
adobefetch("https://platform.adobe.io/some/adobe/api", { method: 'get'}) | ||
.then(response => response.json()) | ||
.then(json => console.log('Result: ',json)); | ||
```javascript | ||
const { fetch } = require('@adobe/fetch'); | ||
const resp = await fetch('https://httpbin.org/json'); | ||
const jsonData = await resp.json(); | ||
``` | ||
#### Config Auth object | ||
### Fetch text data | ||
The `config.auth` object is where you pass in all the required and optional parameters to authenticate API calls. | ||
```javascript | ||
const { fetch } = require('@adobe/fetch'); | ||
| parameter | integration name | required | type | default | | ||
| ------------------ | -------------------- | -------- | --------------------------------- | ------------------------------ | | ||
| clientId | API Key (Client ID) | true | String | | | ||
| technicalAccountId | Technical account ID | true | String | | | ||
| orgId | Organization ID | true | String | | | ||
| clientSecret | Client secret | true | String | | | ||
| privateKey | | true | String | | | ||
| passphrase | | false | String | | | ||
| metaScopes | | true | Comma separated Sting or an Array | | | ||
| ims | | false | String | <https://ims-na1.adobelogin.com> | | ||
const resp = await fetch('https://httpbin.org/'); | ||
const textData = await resp.text(); | ||
``` | ||
In order to determine which **metaScopes** you need to register for you can look them up by product in this [handy table](https://www.adobe.io/authentication/auth-methods.html#!AdobeDocs/adobeio-auth/master/JWT/Scopes.md). | ||
### Fetch binary data | ||
For instance, if you need to be authenticated to call API's for both GDPR and User Management you would [look them up](https://www.adobe.io/authentication/auth-methods.html#!AdobeDocs/adobeio-auth/master/JWT/Scopes.md) and find that they are: | ||
```javascript | ||
const { fetch } = require('@adobe/fetch'); | ||
- GDPR: <https://ims-na1.adobelogin.com/s/ent_gdpr_sdk> | ||
- User Management: <https://ims-na1.adobelogin.com/s/ent_user_sdk> | ||
const resp = await fetch('https://httpbin.org//stream-bytes/65535'); | ||
const imageData = await resp.buffer(); | ||
``` | ||
Then you would create an array of **metaScopes** as part of the `config` object. For instance: | ||
### Specify a timeout for a `fetch` operation | ||
Using `timeoutSignal(ms)` extension: | ||
```javascript | ||
const config = { | ||
auth: { | ||
clientId: 'asasdfasf', | ||
clientSecret: 'aslfjasljf-=asdfalasjdf==asdfa', | ||
technicalAccountId: 'asdfasdfas@techacct.adobe.com', | ||
orgId: 'asdfasdfasdf@AdobeOrg', | ||
metaScopes: [ | ||
'https://ims-na1.adobelogin.com/s/ent_gdpr_sdk', | ||
'https://ims-na1.adobelogin.com/s/ent_user_sdk' | ||
] | ||
const { fetch, timeoutSignal, AbortError } = require('@adobe/fetch'); | ||
const signal = timeoutSignal(1000); | ||
try { | ||
const resp = await fetch('https://httpbin.org/json', { signal }); | ||
const jsonData = await resp.json(); | ||
} catch (err) { | ||
if (err instanceof AbortError) { | ||
console.log('fetch timed out after 1s'); | ||
} | ||
} finally { | ||
// avoid pending timers which prevent node process from exiting | ||
signal.clear(); | ||
} | ||
}; | ||
``` | ||
However, if you omit the IMS URL, the package will automatically add it for you when making the call to generate the JWT. | ||
Using `AbortController`: | ||
For example: | ||
```javascript | ||
const { fetch, AbortController, AbortError } = require('@adobe/fetch'); | ||
```javascript | ||
const config = { | ||
auth: { | ||
clientId: 'asasdfasf', | ||
clientSecret: 'aslfjasljf-=asdfalasjdf==asdfa', | ||
technicalAccountId: 'asdfasdfas@techacct.adobe.com', | ||
orgId: 'asdfasdfasdf@AdobeOrg', | ||
metaScopes: ['ent_gdpr_sdk', 'ent_user_sdk'] | ||
const controller = new AbortController(); | ||
const timerId = setTimeout(() => controller.abort(), 1000); | ||
const { signal } = controller; | ||
try { | ||
const resp = await fetch('https://httpbin.org/json', { signal }); | ||
const jsonData = await resp.json(); | ||
} catch (err) { | ||
if (err instanceof AbortError) { | ||
console.log('fetch timed out after 1s'); | ||
} | ||
} finally { | ||
// avoid pending timers which prevent node process from exiting | ||
clearTimeout(timerId); | ||
} | ||
}; | ||
``` | ||
This is the recommended approach. | ||
### Stream an image | ||
#### Alternative authentication methods | ||
```javascript | ||
const fs = require('fs'); | ||
const { fetch } = require('@adobe/fetch'); | ||
To use this library with an alternative authentication flow such as OAuth, or execute the JWT authentication flow outside of adobe-fetch, it is possible to use the **Provided** mode and provide the access token directly to adobe-fetch via an asynchronious function: | ||
const resp = await fetch('https://httpbin.org/image/jpeg'); | ||
resp.body.pipe(fs.createWriteStream('saved-image.jpg')); | ||
``` | ||
### Post JSON | ||
```javascript | ||
const { fetch } = require('@adobe/fetch'); | ||
const AdobeFetch = require('@adobe/fetch'); | ||
const { AUTH_MODES } = AdobeFetch; | ||
const method = 'POST'; | ||
const body = { foo: 'bar' }; | ||
const resp = await fetch('https://httpbin.org/post', { method, body }); | ||
``` | ||
const adobefetch = AdobeFetch).config({ | ||
auth: { | ||
mode: AUTH_MODES.Provided, | ||
clientId: 'asasdfasf', | ||
orgId: 'asdfasdfasdf@AdobeOrg', | ||
tokenProvider: async () => { ... Logic returning a valid access token object ... } | ||
} | ||
}); | ||
### Post JPEG image | ||
adobefetch("https://platform.adobe.io/some/adobe/api", { method: 'get'}) | ||
.then(response => response.json()) | ||
.then(json => console.log('Result: ',json)); | ||
```javascript | ||
const fs = require('fs'); | ||
const { fetch } = require('@adobe/fetch'); | ||
const method = 'POST'; | ||
const body = fs.createReadStream('some-image.jpg'); | ||
const headers = { 'content-type': 'image/jpeg' }; | ||
const resp = await fetch('https://httpbin.org/post', { method, body, headers }); | ||
``` | ||
When the **adobefetch** call above happens for the first time, it will call the tokenProvider function provided and wait for it to return the access token. Access token is then cached and persisted, if it expires or is rejected by the API, the tokenProvider function will be called again to acquire a new token. | ||
### Post form data | ||
A valid token has the following structure: | ||
```javascript | ||
const { FormData, Blob, File } = require('formdata-node'); // spec-compliant implementations | ||
const { fileFromPath } = require('formdata-node/file-from-path'); // helper for creating File instance from disk file | ||
const { fetch } = require('@adobe/fetch'); | ||
const method = 'POST'; | ||
const fd = new FormData(); | ||
fd.set('field1', 'foo'); | ||
fd.set('field2', 'bar'); | ||
fd.set('blob', new Blob([0x68, 0x65, 0x6c, 0x69, 0x78, 0x2d, 0x66, 0x65, 0x74, 0x63, 0x68])); | ||
fd.set('file', new File(['File content goes here'], 'file.txt')); | ||
fd.set('other_file', await fileFromPath('/foo/bar.jpg', 'bar.jpg', { type: 'image/jpeg' })); | ||
const resp = await fetch('https://httpbin.org/post', { method, body: fd }); | ||
``` | ||
{ | ||
token_type: 'bearer', | ||
access_token: <<<TOKEN>>>, | ||
expires_in: <<<EXPIRY_IN_MILLISECONDS>>> | ||
} | ||
### GET with query parameters object | ||
```javascript | ||
const { createUrl, fetch } = require('@adobe/fetch'); | ||
const qs = { | ||
fake: 'dummy', | ||
foo: 'bar', | ||
rumple: "stiltskin", | ||
}; | ||
const resp = await fetch(createUrl('https://httpbin.org/json', qs)); | ||
``` | ||
#### Using in the browser | ||
or using `URLSearchParams`: | ||
In the browser only the **Provided** mode explained above is allowed, JWT is not supported. | ||
```javascript | ||
const { fetch } = require('@adobe/fetch'); | ||
This is because the JWT workflow requires direct access to the private key and should be done in the server for security reasons. With Provided mode the access token can be acquired via a standard OAuth authentication flow and then used by adobe-fetch to call Adobe APIs. | ||
const body = new URLSearchParams({ | ||
fake: 'dummy', | ||
foo: 'bar', | ||
rumple: "stiltskin", | ||
}); | ||
Using ```require('@adobe/fetch')``` in a web app will automatically use the browser version. | ||
You can also include the [bundled JS](dist/client.js) file directly in a script tag. | ||
const resp = await fetch('https://httpbin.org/json', { body }); | ||
``` | ||
### Cache | ||
#### Predefined Headers | ||
Responses of `GET` and `HEAD` requests are by default cached, according to the rules of [RFC 7234](https://httpwg.org/specs/rfc7234.html): | ||
If you have HTTP headers that are required for each request, you can provide them in the configuration. | ||
They will be then added automatically to each request. | ||
```javascript | ||
const { fetch } = require('@adobe/fetch'); | ||
You can provide either a value or a function. | ||
A function can be used when you need to generate a dynamic header value on each request. | ||
const url = 'https://httpbin.org/cache/60'; // -> max-age=60 (seconds) | ||
// send initial request, priming cache | ||
let resp = await fetch(url); | ||
assert(resp.ok); | ||
assert(!resp.fromCache); | ||
For example: | ||
// re-send request and verify it's served from cache | ||
resp = await fetch(url); | ||
assert(resp.ok); | ||
assert(resp.fromCache); | ||
``` | ||
You can disable caching per request with the `cache: 'no-store'` option: | ||
```javascript | ||
const config = { | ||
auth: { | ||
... Auth Configuration ... | ||
}, | ||
headers: { | ||
'x-sandbox-name': 'prod', | ||
'x-request-id': () => idGenerationFunc() | ||
} | ||
}; | ||
const { fetch } = require('@adobe/fetch'); | ||
const resp = await fetch('https://httbin.org/', { cache: 'no-store' }); | ||
assert(resp.ok); | ||
assert(!resp.fromCache); | ||
``` | ||
The following headers are added automatically. | ||
You can override these headers using a value or function as shown above, with the exception of **authorization**: | ||
You can disable caching entirely: | ||
- authorization **(Can not be overridden)** | ||
- x-api-key | ||
- x-request-id | ||
- x-gw-ims-org-id | ||
```javascript | ||
const { fetch } = require('@adobe/fetch').noCache(); | ||
``` | ||
#### Custom Storage | ||
## Advanced Usage Examples | ||
By default, [node-persist](https://github.com/simonlast/node-persist) is used to store all the active tokens locally. | ||
Tokens will be stored under **/.node-perist/storage** | ||
### HTTP/2 Server Push | ||
It is possible to use any other storage for token persistence. This is done by providing **read** and **write** methods as follows: | ||
Note that pushed resources will be automatically and transparently added to the cache. | ||
You can however add a listener which will be notified on every pushed (and cached) resource. | ||
```javascript | ||
const config = { | ||
auth: { | ||
clientId: 'asasdfasf', | ||
... | ||
storage: { | ||
read: function() { | ||
return new Promise(function(resolve, reject) { | ||
let tokens; | ||
// .. Some logic to read the tokens .. | ||
resolve(tokens); | ||
}); | ||
}, | ||
write: function(tokens) { | ||
return new Promise(function(resolve, reject) { | ||
// .. Some logic to save the tokens .. | ||
resolve(); | ||
}); | ||
} | ||
} | ||
} | ||
}; | ||
const { fetch, onPush } = require('@adobe/fetch'); | ||
onPush((url, response) => console.log(`received server push: ${url} status ${response.status}`)); | ||
const resp = await fetch('https://nghttp2.org'); | ||
console.log(`Http version: ${resp.httpVersion}`); | ||
``` | ||
Alternatively, use async/await: | ||
### Force HTTP/1(.1) protocol | ||
```javascript | ||
const config = { | ||
auth: { | ||
clientId: 'asasdfasf', | ||
... | ||
storage: { | ||
read: async function() { | ||
return await myGetTokensImplementation(); | ||
}, | ||
write: async function(tokens) { | ||
await myStoreTokensImplementation(tokens); | ||
} | ||
} | ||
} | ||
}; | ||
const { fetch } = require('@adobe/fetch').h1(); | ||
const resp = await fetch('https://nghttp2.org'); | ||
console.log(`Http version: ${resp.httpVersion}`); | ||
``` | ||
## Logging | ||
### HTTP/1.1 Keep-Alive | ||
Every request will include a unique request identifier sent via the **x-request-id**. | ||
The request identifier can be overriden by providing it through the headers: | ||
```javascript | ||
fetch(url, { | ||
headers: { 'x-request-id': myRequestID } | ||
}); | ||
const { fetch } = require('@adobe/fetch').keepAlive(); | ||
const resp = await fetch('https://httpbin.org/status/200'); | ||
console.log(`Connection: ${resp.headers.get('connection')}`); // -> keep-alive | ||
``` | ||
We use [debug](https://github.com/visionmedia/debug) to log requests. In order to see all the debug output, including the request identifiers, run your app with the **DEBUG** environment variable including the **@adobe/fetch** scope as follows: | ||
### Self-signed Certificates | ||
```javascript | ||
const { fetch } = require('@adobe/fetch').context({ rejectUnauthorized: false }); | ||
const resp = await fetch('https://localhost:8443/'); // a server using a self-signed certificate | ||
``` | ||
DEBUG=@adobe/fetch | ||
### Set cache size limit | ||
```javascript | ||
const { fetch, cacheStats } = require('@adobe/fetch').context({ | ||
maxCacheSize: 100 * 1024, // 100kb (Default: 100mb) | ||
}); | ||
let resp = await fetch('https://httpbin.org/bytes/60000'); // ~60kb response | ||
resp = await fetch('https://httpbin.org/bytes/50000'); // ~50kb response | ||
console.log(cacheStats()); | ||
``` | ||
### Contributing | ||
### Disable caching | ||
Contributions are welcomed! Read the [Contributing Guide](.github/CONTRIBUTING.md) for more information. | ||
```javascript | ||
const { fetch } = require('@adobe/fetch').noCache(); | ||
### Licensing | ||
let resp = await fetch('https://httpbin.org/cache/60'); // -> max-age=60 (seconds) | ||
// re-fetch | ||
resp = await fetch('https://httpbin.org/cache/60'); | ||
assert(!resp.fromCache); | ||
``` | ||
This project is licensed under the Apache V2 License. See [LICENSE](LICENSE) for more information. | ||
### Set a custom user agent | ||
```javascript | ||
const { fetch } = require('@adobe/fetch').context({ | ||
userAgent: 'custom-fetch' | ||
}); | ||
const resp = await fetch('https://httpbin.org//user-agent'); | ||
const json = await resp.json(); | ||
console.log(json['user-agent']); | ||
``` | ||
## More examples | ||
More example code can be found in the [test source files](/test/). | ||
## Development | ||
### Build | ||
```bash | ||
$ npm install | ||
``` | ||
### Test | ||
```bash | ||
$ npm test | ||
``` | ||
### Lint | ||
```bash | ||
$ npm run lint | ||
``` | ||
### Troubleshooting | ||
You can enable `@adobe/fetch` low-level debug console output by setting the `DEBUG` environment variable to `adobe/fetch*`, e.g.: | ||
```bash | ||
$ DEBUG=adobe/fetch* node test.js | ||
``` | ||
This will produce console outout similar to: | ||
```bash | ||
... | ||
adobe/fetch:core established TLS connection: #48 (www.nghttp2.org) +2s | ||
adobe/fetch:core www.nghttp2.org -> h2 +0ms | ||
adobe/fetch:h2 reusing socket #48 (www.nghttp2.org) +2s | ||
adobe/fetch:h2 GET www.nghttp2.org/httpbin/user-agent +0ms | ||
adobe/fetch:h2 session https://www.nghttp2.org established +1ms | ||
adobe/fetch:h2 caching session https://www.nghttp2.org +0ms | ||
adobe/fetch:h2 session https://www.nghttp2.org remoteSettings: {"headerTableSize":8192,"enablePush":true,"initialWindowSize":1048576,"maxFrameSize":16384,"maxConcurrentStreams":100,"maxHeaderListSize":4294967295,"maxHeaderSize":4294967295,"enableConnectProtocol":true} +263ms | ||
adobe/fetch:h2 session https://www.nghttp2.org localSettings: {"headerTableSize":4096,"enablePush":true,"initialWindowSize":65535,"maxFrameSize":16384,"maxConcurrentStreams":4294967295,"maxHeaderListSize":4294967295,"maxHeaderSize":4294967295,"enableConnectProtocol":false} +0ms | ||
adobe/fetch:h2 session https://www.nghttp2.org closed +6ms | ||
adobe/fetch:h2 discarding cached session https://www.nghttp2.org +0ms | ||
... | ||
``` | ||
Additionally, you can enable Node.js low-level debug console output by setting the `NODE_DEBUG` environment variable appropriately, e.g. | ||
```bash | ||
$ export NODE_DEBUG=http*,stream* | ||
$ export DEBUG=adobe/fetch* | ||
$ node test.js | ||
``` | ||
> Note: this will flood the console with highly verbose debug output. | ||
## Acknowledgement | ||
Thanks to [node-fetch](https://github.com/node-fetch/node-fetch) and [github/fetch](https://github.com/github/fetch) for providing a solid implementation reference. | ||
## License | ||
[Apache 2.0](LICENSE.txt) |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 5 instances in 1 package
No contributors or author data
MaintenancePackage does not specify a list of contributors or an author in package.json.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Deprecated
MaintenanceThe maintainer of the package marked it as deprecated. This could indicate that a single version should not be used, or that the package is no longer maintained and any new vulnerabilities will not be fixed.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 7 instances in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
No website
QualityPackage does not have a website.
Found 1 instance in 1 package
141647
3
3279
0
0
568
0
9
21
24
1
+ Addedhttp-cache-semantics@4.1.0
+ Addedlru-cache@7.14.0
+ Addeddebug@4.3.4(transitive)
+ Addedhttp-cache-semantics@4.1.0(transitive)
+ Addedlru-cache@7.14.0(transitive)
+ Addedms@2.1.2(transitive)
- Removed@adobe/jwt-auth@^0.3.1
- Removeddotenv@^8.2.0
- Removednode-fetch@^2.6.1
- Removednode-persist@^3.1.0
- Removeduuid@^8.3.1
- Removed@adobe/jwt-auth@0.3.3(transitive)
- Removedasynckit@0.4.0(transitive)
- Removedbuffer-equal-constant-time@1.0.1(transitive)
- Removedcombined-stream@1.0.8(transitive)
- Removeddebug@4.3.7(transitive)
- Removeddelayed-stream@1.0.0(transitive)
- Removeddotenv@8.6.0(transitive)
- Removedecdsa-sig-formatter@1.0.11(transitive)
- Removedform-data@3.0.1(transitive)
- Removedjsonwebtoken@8.5.1(transitive)
- Removedjwa@1.4.1(transitive)
- Removedjws@3.2.2(transitive)
- Removedlodash.includes@4.3.0(transitive)
- Removedlodash.isboolean@3.0.3(transitive)
- Removedlodash.isinteger@4.0.4(transitive)
- Removedlodash.isnumber@3.0.3(transitive)
- Removedlodash.isplainobject@4.0.6(transitive)
- Removedlodash.isstring@4.0.1(transitive)
- Removedlodash.once@4.1.1(transitive)
- Removedmime-db@1.52.0(transitive)
- Removedmime-types@2.1.35(transitive)
- Removedms@2.1.3(transitive)
- Removednode-fetch@2.7.0(transitive)
- Removednode-persist@3.1.3(transitive)
- Removedsafe-buffer@5.2.1(transitive)
- Removedsemver@5.7.2(transitive)
- Removedtr46@0.0.3(transitive)
- Removeduuid@8.3.2(transitive)
- Removedwebidl-conversions@3.0.1(transitive)
- Removedwhatwg-url@5.0.0(transitive)
Updateddebug@4.3.4