balena-request
Advanced tools
Comparing version 13.0.0 to 13.1.0-build-otaviojacobi-does-multipart-form-when-blob-is-present-d807c01905068f81d9d353364742947142c9c272-1
@@ -20,3 +20,3 @@ "use strict"; | ||
const tslib_1 = require("tslib"); | ||
const utils = require("./utils"); | ||
const utils = tslib_1.__importStar(require("./utils")); | ||
/** | ||
@@ -23,0 +23,0 @@ * @module progress |
@@ -42,2 +42,5 @@ /// <reference types="node" /> | ||
} | ||
export interface WebResourceFile extends Blob { | ||
name: string; | ||
} | ||
export interface Interceptor { | ||
@@ -57,2 +60,6 @@ request?(response: any): Promise<any>; | ||
export type BalenaRequest = ReturnType<typeof getRequest>; | ||
export declare class BalenaFile extends Blob implements WebResourceFile { | ||
name: string; | ||
constructor(name: string, blobParts?: BlobPart[], options?: BlobPropertyBag); | ||
} | ||
/** | ||
@@ -59,0 +66,0 @@ * @module request |
@@ -18,7 +18,17 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.getRequest = void 0; | ||
exports.getRequest = exports.BalenaFile = void 0; | ||
const tslib_1 = require("tslib"); | ||
const urlLib = require("url"); | ||
const errors = require("balena-errors"); | ||
const utils = require("./utils"); | ||
const urlLib = tslib_1.__importStar(require("url")); | ||
const errors = tslib_1.__importStar(require("balena-errors")); | ||
const mime = tslib_1.__importStar(require("mime")); | ||
const utils = tslib_1.__importStar(require("./utils")); | ||
class BalenaFile extends Blob { | ||
constructor(name, blobParts, options) { | ||
var _a, _b; | ||
options = Object.assign(Object.assign({}, options), { type: (_b = (_a = options === null || options === void 0 ? void 0 : options.type) !== null && _a !== void 0 ? _a : mime.getType(name)) !== null && _b !== void 0 ? _b : undefined }); | ||
super(blobParts, options); | ||
this.name = name; | ||
} | ||
} | ||
exports.BalenaFile = BalenaFile; | ||
/** | ||
@@ -25,0 +35,0 @@ * @module request |
@@ -21,6 +21,7 @@ "use strict"; | ||
const { fetch: normalFetch, Headers: HeadersPonyfill } = require('fetch-ponyfill')({ Promise }); | ||
const urlLib = require("url"); | ||
const qs = require("qs"); | ||
const errors = require("balena-errors"); | ||
const urlLib = tslib_1.__importStar(require("url")); | ||
const qs = tslib_1.__importStar(require("qs")); | ||
const errors = tslib_1.__importStar(require("balena-errors")); | ||
const balena_auth_1 = require("balena-auth"); | ||
const stream_1 = require("stream"); | ||
const IS_BROWSER = typeof window !== 'undefined' && window !== null; | ||
@@ -318,4 +319,11 @@ /** | ||
exports.getBody = getBody; | ||
const isFile = (value) => { | ||
return (value && | ||
value.name != null && | ||
typeof value.name === 'string' && | ||
value instanceof Blob); | ||
}; | ||
// This is the actual implementation that hides the internal `retriesRemaining` parameter | ||
function requestAsync(fetch, options, retriesRemaining) { | ||
var _a; | ||
return tslib_1.__awaiter(this, void 0, void 0, function* () { | ||
@@ -336,2 +344,29 @@ const [url, opts] = processRequestOptions(options); | ||
} | ||
const NodeFormData = (yield import('formdata-node')).FormData; | ||
const FormDataEncoder = (yield import('form-data-encoder')).FormDataEncoder; | ||
const nodeForm = new NodeFormData(); | ||
const form = IS_BROWSER ? new FormData() : nodeForm; | ||
let hasBlob = false; | ||
for (const [k, v] of Object.entries((_a = options.body) !== null && _a !== void 0 ? _a : {})) { | ||
if (isFile(v)) { | ||
hasBlob = true; | ||
} | ||
form.append(k, v); | ||
} | ||
if (hasBlob) { | ||
if (IS_BROWSER) { | ||
// Browsers will handle set form data header and boundaries | ||
// Given the correct header | ||
opts.headers.delete('Content-Type'); | ||
// @ts-expect-error | ||
opts.body = form; | ||
} | ||
else { | ||
const encoder = new FormDataEncoder(form); | ||
opts.headers.set('Content-Type', encoder.headers['Content-Type']); | ||
opts.headers.set('Content-Length', encoder.headers['Content-Length']); | ||
// @ts-expect-error | ||
opts.body = stream_1.Readable.from(encoder); | ||
} | ||
} | ||
try { | ||
@@ -338,0 +373,0 @@ const requestTime = Date.now(); |
@@ -7,2 +7,6 @@ # Change Log | ||
## 13.1.0 - 2023-10-11 | ||
* Add multi part request support when File-like is present [Otávio Jacobi] | ||
## 13.0.0 - 2023-10-11 | ||
@@ -9,0 +13,0 @@ |
@@ -22,2 +22,3 @@ /* | ||
import * as errors from 'balena-errors'; | ||
import * as mime from 'mime'; | ||
import * as utils from './utils'; | ||
@@ -65,2 +66,6 @@ | ||
export interface WebResourceFile extends Blob { | ||
name: string; | ||
} | ||
export interface Interceptor { | ||
@@ -83,2 +88,16 @@ request?(response: any): Promise<any>; | ||
export class BalenaFile extends Blob implements WebResourceFile { | ||
constructor( | ||
public name: string, | ||
blobParts?: BlobPart[], | ||
options?: BlobPropertyBag, | ||
) { | ||
options = { | ||
...options, | ||
type: options?.type ?? mime.getType(name) ?? undefined, | ||
}; | ||
super(blobParts, options); | ||
} | ||
} | ||
/** | ||
@@ -85,0 +104,0 @@ * @module request |
@@ -26,3 +26,8 @@ /* | ||
import { TokenType } from 'balena-auth'; | ||
import type { BalenaRequestOptions, BalenaRequestResponse } from './request'; | ||
import type { | ||
BalenaRequestOptions, | ||
BalenaRequestResponse, | ||
WebResourceFile, | ||
} from './request'; | ||
import { Readable } from 'stream'; | ||
@@ -376,4 +381,12 @@ const IS_BROWSER = typeof window !== 'undefined' && window !== null; | ||
const isFile = (value: any): value is WebResourceFile => { | ||
return ( | ||
value && | ||
value.name != null && | ||
typeof value.name === 'string' && | ||
value instanceof Blob | ||
); | ||
}; | ||
// This is the actual implementation that hides the internal `retriesRemaining` parameter | ||
async function requestAsync( | ||
@@ -399,3 +412,33 @@ fetch: typeof normalFetch, | ||
} | ||
const NodeFormData = (await import('formdata-node')).FormData; | ||
const FormDataEncoder = (await import('form-data-encoder')).FormDataEncoder; | ||
const nodeForm = new NodeFormData(); | ||
const form = IS_BROWSER ? new FormData() : nodeForm; | ||
let hasBlob = false; | ||
for (const [k, v] of Object.entries(options.body ?? {})) { | ||
if (isFile(v)) { | ||
hasBlob = true; | ||
} | ||
form.append(k, v as string | WebResourceFile); | ||
} | ||
if (hasBlob) { | ||
if (IS_BROWSER) { | ||
// Browsers will handle set form data header and boundaries | ||
// Given the correct header | ||
opts.headers.delete('Content-Type'); | ||
// @ts-expect-error | ||
opts.body = form; | ||
} else { | ||
const encoder = new FormDataEncoder(form as typeof nodeForm); | ||
opts.headers.set('Content-Type', encoder.headers['Content-Type']); | ||
opts.headers.set('Content-Length', encoder.headers['Content-Length']!); | ||
// @ts-expect-error | ||
opts.body = Readable.from(encoder); | ||
} | ||
} | ||
try { | ||
@@ -402,0 +445,0 @@ const requestTime = Date.now(); |
{ | ||
"name": "balena-request", | ||
"version": "13.0.0", | ||
"version": "13.1.0-build-otaviojacobi-does-multipart-form-when-blob-is-present-d807c01905068f81d9d353364742947142c9c272-1", | ||
"description": "Balena HTTP client", | ||
@@ -42,3 +42,5 @@ "main": "build/request.js", | ||
"@types/chai-as-promised": "^7.1.5", | ||
"@types/mime": "^3.0.2", | ||
"@types/mocha": "^9.1.1", | ||
"@types/node": "^18.0.0", | ||
"@types/progress-stream": "^2.0.0", | ||
@@ -75,2 +77,5 @@ "@types/qs": "^6.9.3", | ||
"fetch-readablestream": "^0.2.0", | ||
"form-data-encoder": "^3.0.0", | ||
"formdata-node": "^5.0.0", | ||
"mime": "^3.0.0", | ||
"progress-stream": "^2.0.0", | ||
@@ -85,4 +90,4 @@ "qs": "^6.9.4", | ||
"versionist": { | ||
"publishedAt": "2023-10-11T17:31:39.709Z" | ||
"publishedAt": "2023-10-11T20:07:35.004Z" | ||
} | ||
} |
const Bluebird = require('bluebird'); | ||
const { expect } = require('chai'); | ||
const sinon = require('sinon'); | ||
const fs = require('fs'); | ||
const mockServer = require('mockttp').getLocal(); | ||
const { auth, request, getCustomRequest, IS_BROWSER } = require('./setup')(); | ||
const { auth, request, getCustomRequest, IS_BROWSER, BalenaFile } = | ||
require('./setup')(); | ||
@@ -221,2 +223,35 @@ // Grab setTimeout before we replace it with a fake later, so | ||
describe('given a body with a Blob data', function () { | ||
this.beforeEach(() => { | ||
mockServer.forPost('/multipart-endpoint').thenCallback(async (req) => { | ||
return { statusCode: 200, body: req.headers['content-type'] }; | ||
}); | ||
}); | ||
it('should send the request as multipart/form-data with boundary', async function () { | ||
let body; | ||
const fileName = 'testfile.txt'; | ||
if (IS_BROWSER) { | ||
body = { | ||
content: new File(['a', 'test', 'blob'], fileName), | ||
}; | ||
} else { | ||
body = { | ||
content: new BalenaFile(fileName, [ | ||
new Blob(['a', 'test', 'blob']), | ||
]), | ||
}; | ||
} | ||
const res = await request.send({ | ||
method: 'POST', | ||
baseUrl: mockServer.url, | ||
url: '/multipart-endpoint', | ||
body: body, | ||
}); | ||
return expect(res.body.startsWith('multipart/form-data; boundary=')).to | ||
.be.true; | ||
}); | ||
}); | ||
describe('given an endpoint that fails the first two times', function () { | ||
@@ -223,0 +258,0 @@ beforeEach(() => |
@@ -19,3 +19,4 @@ const IS_BROWSER = typeof window !== 'undefined' && window !== null; | ||
const { getRequest } = require('../build/request'); | ||
const { BalenaFile, getRequest } = require('../build/request'); | ||
const chai = require('chai'); | ||
@@ -35,2 +36,3 @@ const chaiAsPromised = require('chai-as-promised'); | ||
getCustomRequest, | ||
BalenaFile, | ||
}); |
{ | ||
"compilerOptions": { | ||
"module": "commonjs", | ||
"module": "Node16", | ||
"declaration": true, | ||
@@ -13,3 +13,3 @@ "outDir": "build", | ||
"noUnusedLocals": true, | ||
"moduleResolution": "node", | ||
"moduleResolution": "Node16", | ||
"allowJs": true, | ||
@@ -16,0 +16,0 @@ "checkJs": true, |
Sorry, the diff of this file is not supported yet
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
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
217608
4177
12
31
1
5
+ Addedform-data-encoder@^3.0.0
+ Addedformdata-node@^5.0.0
+ Addedmime@^3.0.0
+ Addedform-data-encoder@3.0.1(transitive)
+ Addedformdata-node@5.0.1(transitive)
+ Addedmime@3.0.0(transitive)
+ Addednode-domexception@1.0.0(transitive)
+ Addedweb-streams-polyfill@4.0.0-beta.3(transitive)