Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@anvilco/anvil

Package Overview
Dependencies
Maintainers
5
Versions
39
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@anvilco/anvil - npm Package Compare versions

Comparing version 2.1.0 to 2.2.0

scratch/dummy.pdf

21

CHANGELOG.md

@@ -10,6 +10,25 @@ # Changelog

## [v2.1.0](https://github.com/anvilco/node-anvil/compare/v1.0.3...v2.1.0) - 2020-08-20
## [v2.2.0](https://github.com/anvilco/node-anvil/compare/v2.1.0...v2.2.0) - 2020-09-28
### Merged
- Add `aliasId` AND `routingOrder` to `createEtchPacket` mutation query response default [`#24`](https://github.com/anvilco/node-anvil/pull/24)
- rename `send` to `isDraft` (and flip the logic) in `createEtchPacket` [`#23`](https://github.com/anvilco/node-anvil/pull/23)
- no `organizationEid` allowed when calling via API key [`#22`](https://github.com/anvilco/node-anvil/pull/22)
- change `fillPayload` param to just `data` [`#21`](https://github.com/anvilco/node-anvil/pull/21)
- Fix file Upload related stuff to reflect server-side [`#20`](https://github.com/anvilco/node-anvil/pull/20)
- Etch API Support for Embedded Signers [`#18`](https://github.com/anvilco/node-anvil/pull/18)
- Bump node-fetch from 2.6.0 to 2.6.1 [`#19`](https://github.com/anvilco/node-anvil/pull/19)
- Add support for GraphQL API, starting with `createEtchPacket` mutation [`#16`](https://github.com/anvilco/node-anvil/pull/16)
### Commits
- dependency upgrades: nodemon, mocha, and yargs due to dot-prop and yargs-parser vulnerabilities [`c118d23`](https://github.com/anvilco/node-anvil/commit/c118d234c4717a429b8ebda8c1f69e1002c11f56)
- added graphql folders. wrapping all request/responses in throttle and body parsing helper. tidied up a lot [`036c615`](https://github.com/anvilco/node-anvil/commit/036c615290fbd4c4d830dde6e8ea28275647ab64)
- getting the file uploads stuff more in line with the actual way the API expects things now [`2c7bab6`](https://github.com/anvilco/node-anvil/commit/2c7bab6d44c9893884c3e73a60792f0e84a936a7)
## [v2.1.0](https://github.com/anvilco/node-anvil/compare/v1.0.3...v2.1.0) - 2020-08-19
### Merged
- replace `request` with `node-fetch` [`#13`](https://github.com/anvilco/node-anvil/pull/13)

@@ -16,0 +35,0 @@ - Bump lodash from 4.17.15 to 4.17.19 [`#12`](https://github.com/anvilco/node-anvil/pull/12)

14

package.json
{
"name": "@anvilco/anvil",
"version": "2.1.0",
"version": "2.2.0",
"description": "Anvil API Client",
"main": "src/index.js",
"scripts": {
"test": "mocha --require test/environment.js 'test/**/*.test.js'",
"test": "mocha --config ./test/mocha.js",
"test:watch": "nodemon --signal SIGINT --watch test --watch src -x 'yarn test'",

@@ -33,2 +33,3 @@ "version": "auto-changelog -p --template keepachangelog && git add CHANGELOG.md"

"babel-eslint": "^10.0.3",
"bdd-lazy-var": "^2.5.4",
"chai": "^4.2.0",

@@ -46,9 +47,12 @@ "chai-as-promised": "^7.1.1",

"eslint-plugin-standard": "^4.0.1",
"mocha": "^7.1.1",
"nodemon": "^2.0.2",
"mocha": "^8.1.3",
"nodemon": "^2.0.4",
"sinon": "^9.0.1",
"sinon-chai": "^3.5.0",
"yargs": "^15.1.0"
"yargs": "^16.0.3"
},
"dependencies": {
"abort-controller": "^3.0.0",
"extract-files": "^6",
"form-data": "^3.0.0",
"limiter": "^1.1.5",

@@ -55,0 +59,0 @@ "node-fetch": "^2.6.0"

@@ -51,4 +51,6 @@ # Anvil API Client for Node

### new Anvil(options)
### Instance Methods
##### new Anvil(options)
Creates an Anvil client instance.

@@ -61,15 +63,6 @@

```
<br />
### Options
##### fillPDF(pdfTemplateID, payload[, options])
Options for the Anvil Client. Defaults are shown after each option key.
```js
{
apiKey: <your_api_key> // Required. Your API key from your Anvil organization settings
}
```
### Anvil::fillPDF(pdfTemplateID, payload[, options])
Fills a PDF with your JSON data.

@@ -120,2 +113,54 @@

##### createEtchPacket(options)
Creates an Etch Packet and optionally sends it to the first signer.
* `options` (Object) - An object with the following structure:
* `variables` (Object) - See the [API Documentation](#api-documentation) area for details. See [Examples](#examples) area for examples.
* `responseQuery` (String) - _optional_ A GraphQL Query compliant query to use for the data desired in the mutation response. Can be left out to use default.
* `mutation` (String) - _optional_ If you'd like complete control of the GraphQL mutation, you can pass in a GraphQL Mutation compliant string that will be used in the mutation call. This string should also include your response query, as the `responseQuery` param is ignored if `mutation` is passed. Example:
```graphql
mutation CreateEtchPacket (
$name: String,
...
) {
createEtchPacket (
name: $name,
...
) {
id
eid
...
}
}
```
##### generateEtchSignUrl(options)
Generates an Etch sign URL for an Etch Packet signer. The Etch Packet and its signers must have already been created.
* `options` (Object) - An object with the following structure:
* `variables` (Object) - Requires `clientUserId` and `signerEid`
* `clientUserId` (String) - your user eid
* `signerEid` (String) - the eid of the Etch Packet signer, found in the response of the `createEtchPacket` instance method
### Class Methods
##### prepareGraphQLFile(pathOrStreamLikeThing[, options])
A nice helper to prepare a Stream-backed or Buffer-backed file upload for use with our GraphQL API.
* `pathOrStreamLikeThing` (String | Stream | Buffer) - An existing `Stream`, `Buffer` or other Stream-like thing supported by [FormData.append](https://github.com/form-data/form-data#void-append-string-field-mixed-value--mixed-options-) OR a string representing a fully resolved path to a file to be read into a new `Stream`.
* `options` (Object) - Anything supported by [FormData.append](https://github.com/form-data/form-data#void-append-string-field-mixed-value--mixed-options-). Likely required when providing a non-common stream. From the `form-data` docs:
> Form-Data can recognize and fetch all the required information from common types of streams (fs.readStream, http.response and mikeal's request), for some other types of streams you'd need to provide "file"-related information manually
* Returns an `Object` that is properly formatted to be coerced by the client for use against our GraphQL API wherever an `Upload` type is required.
### Types
##### Options
Options for the Anvil Client. Defaults are shown after each option key.
```js
{
apiKey: <your_api_key> // Required. Your API key from your Anvil organization settings
}
```
### Rate Limits

@@ -127,6 +172,8 @@

### More Info
## API Documentation
See the [PDF filling API docs](https://useanvil.com/api/fill-pdf) for more information.
Our general API Documentation can be found [here](https://www.useanvil.com/api/). It's the best resource for up-to-date information about our API and its capabilities.
See the [PDF filling API docs](https://useanvil.com/api/fill-pdf) for more information about the `fillPDF` method.
## Examples

@@ -133,0 +180,0 @@

@@ -0,6 +1,28 @@

const fs = require('fs')
const fetch = require('node-fetch')
const RateLimiter = require('limiter').RateLimiter
const FormData = require('form-data')
const AbortController = require('abort-controller')
const { extractFiles } = require('extract-files')
const { RateLimiter } = require('limiter')
const UploadWithOptions = require('./UploadWithOptions')
const { version, description } = require('../package.json')
const {
mutations: {
createEtchPacket: {
getMutation: getCreateEtchPacketMutation,
},
generateEtchSignUrl: {
getMutation: getGenerateEtchSignUrlMutation,
},
},
} = require('./graphql')
const {
isFile,
graphQLUploadSchemaIsValid,
} = require('./validation')
const DATA_TYPE_STREAM = 'stream'

@@ -26,7 +48,11 @@ const DATA_TYPE_BUFFER = 'buffer'

if (!options) throw new Error('options are required')
if (!options.apiKey && !options.accessToken) throw new Error('apiKey or accessToken required')
this.options = Object.assign({}, defaultOptions, options)
this.options = {
...defaultOptions,
...options,
}
const { apiKey, accessToken } = this.options
if (!(apiKey || accessToken)) throw new Error('apiKey or accessToken required')
this.authHeader = accessToken

@@ -42,2 +68,23 @@ ? `Bearer ${Buffer.from(accessToken, 'ascii').toString('base64')}`

/**
* Perform some handy/necessary things for a GraphQL file upload to make it work
* with this client and with our backend
*
* @param {string|Buffer|Stream-like-thing} pathOrStreamLikeThing - Either a string path to a file,
* a Buffer, or a Stream-like thing that is compatible with form-data as an append.
* @param {object} formDataAppendOptions - User can specify options to be passed to the form-data.append
* call. This should be done if a stream-like thing is not one of the common types that
* form-data can figure out on its own.
*
* @return {UploadWithOptions} - A class that wraps the stream-like-thing and any options
* up together nicely in a way that we can also tell that it was us who did it.
*/
static prepareGraphQLFile (pathOrStreamLikeThing, formDataAppendOptions) {
if (typeof pathOrStreamLikeThing === 'string') {
pathOrStreamLikeThing = fs.createReadStream(pathOrStreamLikeThing)
}
return new UploadWithOptions(pathOrStreamLikeThing, formDataAppendOptions)
}
fillPDF (pdfTemplateID, payload, clientOptions = {}) {

@@ -57,3 +104,2 @@ const supportedDataTypes = [DATA_TYPE_STREAM, DATA_TYPE_BUFFER]

'Content-Type': 'application/json',
Authorization: this.authHeader,
},

@@ -68,14 +114,158 @@ },

// Private
createEtchPacket ({ variables, responseQuery, mutation }) {
return this.requestGraphQL(
{
query: mutation || getCreateEtchPacketMutation(responseQuery),
variables,
},
{ dataType: DATA_TYPE_JSON },
)
}
async requestREST (url, options, clientOptions = {}) {
return this.throttle(async (retry) => {
const response = await this.request(url, options)
async generateEtchSignUrl ({ variables }) {
const { statusCode, data, errors } = await this.requestGraphQL(
{
query: getGenerateEtchSignUrlMutation(),
variables,
},
{ dataType: DATA_TYPE_JSON },
)
return {
statusCode,
url: data && data.data && data.data.generateEtchSignURL,
errors,
}
}
async requestGraphQL ({ query, variables = {} }, clientOptions) {
// Some helpful resources on how this came to be:
// https://github.com/jaydenseric/graphql-upload/issues/125#issuecomment-440853538
// https://zach.codes/building-a-file-upload-hook/
// https://github.com/jaydenseric/graphql-react/blob/1b1234de5de46b7a0029903a1446dcc061f37d09/src/universal/graphqlFetchOptions.mjs
// https://www.npmjs.com/package/extract-files
const options = {
method: 'POST',
headers: {},
}
const originalOperation = { query, variables }
const {
clone: augmentedOperation,
files: filesMap,
} = extractFiles(originalOperation, '', isFile)
const operationJSON = JSON.stringify(augmentedOperation)
// Checks for both File uploads and Base64 uploads
if (!graphQLUploadSchemaIsValid(originalOperation)) {
throw new Error('Invalid File schema detected')
}
if (filesMap.size) {
const abortController = new AbortController()
const form = new FormData()
form.append('operations', operationJSON)
const map = {}
let i = 0
filesMap.forEach(paths => {
map[++i] = paths
})
form.append('map', JSON.stringify(map))
i = 0
filesMap.forEach((paths, file) => {
let appendOptions = {}
if (file instanceof UploadWithOptions) {
appendOptions = file.options
file = file.file
}
// If this is a stream-like thing, attach a listener to the 'error' event so that we
// can cancel the API call if something goes wrong
if (typeof file.on === 'function') {
file.on('error', (err) => {
console.warn(err)
abortController.abort()
})
}
// Pass in some things explicitly to the form.append so that we get the
// desired/expected filename and mimetype, etc
form.append(`${++i}`, file, appendOptions)
})
options.signal = abortController.signal
options.body = form
} else {
options.headers['Content-Type'] = 'application/json'
options.body = operationJSON
}
const {
statusCode,
data,
errors,
} = await this._wrapRequest(
() => this._request('/graphql', options),
clientOptions,
)
return {
statusCode,
data,
errors,
}
}
async requestREST (url, fetchOptions, clientOptions) {
const {
response,
statusCode,
data,
errors,
} = await this._wrapRequest(
() => this._request(url, fetchOptions),
clientOptions,
)
return {
response,
statusCode,
data,
errors,
}
}
// ******************************************************************************
// ___ _ __
// / _ \____(_) _____ _/ /____
// / ___/ __/ / |/ / _ `/ __/ -_)
// /_/ /_/ /_/|___/\_,_/\__/\__/
//
// ALL THE BELOW CODE IS CONSIDERED PRIVATE, AND THE API OR INTERNALS MAY CHANGE AT ANY TIME
// USERS OF THIS MODULE SHOULD NOT USE ANY OF THESE METHODS DIRECTLY
// ******************************************************************************
_request (url, options) {
if (!url.startsWith(this.options.baseURL)) {
url = this._url(url)
}
const opts = this._addDefaultHeaders(options)
return fetch(url, opts)
}
_wrapRequest (retryableRequestFn, clientOptions = {}) {
return this._throttle(async (retry) => {
const response = await retryableRequestFn()
const statusCode = response.status
if (statusCode === 429) {
return retry(getRetryMS(response.headers.get('retry-after')))
}
if (statusCode >= 300) {
if (statusCode === 429) {
return retry(getRetryMS(response.headers.get('retry-after')))
}
if (statusCode >= 300) {
const json = await response.json()

@@ -89,6 +279,4 @@ const errors = json.errors || (json.message && [json])

let data
switch (dataType) {
case DATA_TYPE_JSON:
data = await response.json()
break
case DATA_TYPE_STREAM:

@@ -100,12 +288,59 @@ data = response.body

break
case DATA_TYPE_JSON:
data = await response.json()
break
default:
data = await response.buffer()
console.warn('Using default response dataType of "json". Please specifiy a dataType.')
data = await response.json()
break
}
return { statusCode, data }
return {
response,
data,
statusCode,
}
})
}
throttle (fn) {
_url (path) {
return this.options.baseURL + path
}
_addHeaders ({ options: existingOptions, headers: newHeaders }, internalOptions = {}) {
const { headers: existingHeaders = {} } = existingOptions
const { defaults = false } = internalOptions
newHeaders = defaults ? newHeaders : Object.entries(newHeaders).reduce((acc, [key, val]) => {
if (val != null) {
acc[key] = val
}
return acc
}, {})
return {
...existingOptions,
headers: {
...existingHeaders,
...newHeaders,
},
}
}
_addDefaultHeaders (options) {
const { userAgent } = this.options
return this._addHeaders(
{
options,
headers: {
'User-Agent': userAgent,
Authorization: this.authHeader,
},
},
{ defaults: true },
)
}
_throttle (fn) {
return new Promise((resolve, reject) => {

@@ -119,3 +354,3 @@ this.limiter.removeTokens(1, async (err, remainingRequests) => {

await sleep(ms)
return this.throttle(fn)
return this._throttle(fn)
}

@@ -131,34 +366,22 @@ try {

request (url, options) {
if (!url.startsWith(this.options.baseURL)) {
url = this.url(url)
static _prepareGraphQLBase64 (data, options = {}) {
const { filename, mimetype } = options
if (!filename) {
throw new Error('options.filename must be provided for Base64 upload')
}
const opts = this.addDefaultHeaders(options)
return fetch(url, opts)
}
if (!mimetype) {
throw new Error('options.mimetype must be provided for Base64 upload')
}
url (path) {
return this.options.baseURL + path
}
if (options.bufferize) {
const buffer = Buffer.from(data, 'base64')
return this._prepareGraphQLBuffer(buffer, options)
}
addHeaders ({ options: existingOptions, headers: newHeaders }) {
const { headers: existingHeaders = {} } = existingOptions
return {
...existingOptions,
headers: {
...existingHeaders,
...newHeaders,
},
data,
filename,
mimetype,
}
}
addDefaultHeaders (options) {
const { userAgent } = this.options
return this.addHeaders({
options,
headers: {
'User-Agent': userAgent,
},
})
}
}

@@ -165,0 +388,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc