gcs-resumable-upload
Advanced tools
Comparing version 0.13.0 to 0.14.0
@@ -11,2 +11,5 @@ /** | ||
import * as Pumpify from 'pumpify'; | ||
export interface ErrorWithCode extends Error { | ||
code: number; | ||
} | ||
export declare type CreateUriCallback = (err: Error | null, uri?: string) => void; | ||
@@ -125,3 +128,5 @@ export interface Encryption { | ||
constructor(cfg: UploadConfig); | ||
createURI(): Promise<string>; | ||
createURI(callback: CreateUriCallback): void; | ||
protected createURIAsync(): Promise<string>; | ||
private continueUploading; | ||
@@ -143,2 +148,3 @@ private startUploading; | ||
export declare function upload(cfg: UploadConfig): Upload; | ||
export declare function createURI(cfg: UploadConfig): Promise<string>; | ||
export declare function createURI(cfg: UploadConfig, callback: CreateUriCallback): void; |
@@ -8,2 +8,10 @@ "use strict"; | ||
*/ | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
@@ -22,5 +30,2 @@ const ConfigStore = require("configstore"); | ||
const RETRY_LIMIT = 5; | ||
const wrapError = (message, err) => { | ||
return new Error([message, err.message].join('\n')); | ||
}; | ||
class Upload extends Pumpify { | ||
@@ -31,3 +36,3 @@ constructor(cfg) { | ||
this.numRetries = 0; | ||
streamEvents.call(this); | ||
streamEvents(this); | ||
cfg = cfg || {}; | ||
@@ -88,32 +93,37 @@ if (!cfg.bucket || !cfg.file) { | ||
createURI(callback) { | ||
const metadata = this.metadata; | ||
const reqOpts = { | ||
method: 'POST', | ||
url: [BASE_URI, this.bucket, 'o'].join('/'), | ||
params: { name: this.file, uploadType: 'resumable' }, | ||
data: metadata, | ||
headers: {} | ||
}; | ||
if (metadata.contentLength) { | ||
reqOpts.headers['X-Upload-Content-Length'] = metadata.contentLength; | ||
if (!callback) { | ||
return this.createURIAsync(); | ||
} | ||
if (metadata.contentType) { | ||
reqOpts.headers['X-Upload-Content-Type'] = metadata.contentType; | ||
} | ||
if (typeof this.generation !== 'undefined') { | ||
reqOpts.params.ifGenerationMatch = this.generation; | ||
} | ||
if (this.kmsKeyName) { | ||
reqOpts.params.kmsKeyName = this.kmsKeyName; | ||
} | ||
if (this.predefinedAcl) { | ||
reqOpts.params.predefinedAcl = this.predefinedAcl; | ||
} | ||
if (this.origin) { | ||
reqOpts.headers.Origin = this.origin; | ||
} | ||
this.makeRequest(reqOpts, (err, resp) => { | ||
if (err) { | ||
return callback(err); | ||
this.createURIAsync().then(r => callback(null, r), callback); | ||
} | ||
createURIAsync() { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const metadata = this.metadata; | ||
const reqOpts = { | ||
method: 'POST', | ||
url: [BASE_URI, this.bucket, 'o'].join('/'), | ||
params: { name: this.file, uploadType: 'resumable' }, | ||
data: metadata, | ||
headers: {} | ||
}; | ||
if (metadata.contentLength) { | ||
reqOpts.headers['X-Upload-Content-Length'] = | ||
metadata.contentLength.toString(); | ||
} | ||
if (metadata.contentType) { | ||
reqOpts.headers['X-Upload-Content-Type'] = metadata.contentType; | ||
} | ||
if (typeof this.generation !== 'undefined') { | ||
reqOpts.params.ifGenerationMatch = this.generation; | ||
} | ||
if (this.kmsKeyName) { | ||
reqOpts.params.kmsKeyName = this.kmsKeyName; | ||
} | ||
if (this.predefinedAcl) { | ||
reqOpts.params.predefinedAcl = this.predefinedAcl; | ||
} | ||
if (this.origin) { | ||
reqOpts.headers.Origin = this.origin; | ||
} | ||
const resp = yield this.makeRequest(reqOpts); | ||
const uri = resp.headers.location; | ||
@@ -123,24 +133,29 @@ this.uri = uri; | ||
this.offset = 0; | ||
callback(null, uri); | ||
return uri; | ||
}); | ||
} | ||
continueUploading() { | ||
if (typeof this.offset === 'number') { | ||
return this.startUploading(); | ||
} | ||
this.getAndSetOffset(this.startUploading.bind(this)); | ||
return __awaiter(this, void 0, void 0, function* () { | ||
if (typeof this.offset === 'number') { | ||
this.startUploading(); | ||
return; | ||
} | ||
yield this.getAndSetOffset(); | ||
this.startUploading(); | ||
}); | ||
} | ||
startUploading() { | ||
const reqOpts = { | ||
method: 'PUT', | ||
url: this.uri, | ||
headers: { | ||
'Content-Range': 'bytes ' + this.offset + '-*/' + this.contentLength | ||
} | ||
}; | ||
const bufferStream = this.bufferStream = new stream_1.PassThrough(); | ||
const offsetStream = this.offsetStream = | ||
new stream_1.PassThrough({ transform: this.onChunk.bind(this) }); | ||
const delayStream = new stream_1.PassThrough(); | ||
this.getRequestStream(reqOpts, (requestStream) => { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const reqOpts = { | ||
method: 'PUT', | ||
url: this.uri, | ||
headers: { | ||
'Content-Range': 'bytes ' + this.offset + '-*/' + this.contentLength | ||
} | ||
}; | ||
const bufferStream = this.bufferStream = new stream_1.PassThrough(); | ||
const offsetStream = this.offsetStream = | ||
new stream_1.PassThrough({ transform: this.onChunk.bind(this) }); | ||
const delayStream = new stream_1.PassThrough(); | ||
const requestStream = yield this.getRequestStream(reqOpts); | ||
this.setPipeline(bufferStream, offsetStream, requestStream, delayStream); | ||
@@ -165,2 +180,6 @@ // wait for "complete" from request before letting the stream finish | ||
const numBytesWritten = this.numBytesWritten; | ||
this.emit('progress', { | ||
bytesWritten: this.numBytesWritten, | ||
contentLength: this.contentLength | ||
}); | ||
// check if this is the same content uploaded previously. this caches a | ||
@@ -196,12 +215,24 @@ // slice of the first chunk, then compares it with the first byte of | ||
// only push data from the byte after the one we left off on | ||
next(null, this.numBytesWritten > offset ? chunk : undefined); | ||
next(undefined, this.numBytesWritten > offset ? chunk : undefined); | ||
} | ||
getAndSetOffset(callback) { | ||
const opts = { | ||
method: 'PUT', | ||
url: this.uri, | ||
headers: { 'Content-Length': 0, 'Content-Range': 'bytes */*' } | ||
}; | ||
this.makeRequest(opts, (err, resp) => { | ||
if (err) { | ||
getAndSetOffset() { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const opts = { | ||
method: 'PUT', | ||
url: this.uri, | ||
headers: { 'Content-Length': 0, 'Content-Range': 'bytes */*' } | ||
}; | ||
try { | ||
const resp = yield this.makeRequest(opts); | ||
if (resp.status === RESUMABLE_INCOMPLETE_STATUS_CODE) { | ||
if (resp.headers.range) { | ||
const range = resp.headers.range; | ||
this.offset = Number(range.split('-')[1]) + 1; | ||
return; | ||
} | ||
} | ||
this.offset = 0; | ||
} | ||
catch (err) { | ||
const resp = err.response; | ||
// we don't return a 404 to the user if they provided the resumable | ||
@@ -212,3 +243,4 @@ // URI. if we're just using the configstore file to tell us that this | ||
if (resp && resp.status === 404 && !this.uriProvidedManually) { | ||
return this.restart(); | ||
this.restart(); | ||
return; | ||
} | ||
@@ -221,63 +253,64 @@ // this resumable upload is unrecoverable (bad data or service error). | ||
if (resp && resp.status === TERMINATED_UPLOAD_STATUS_CODE) { | ||
return this.restart(); | ||
} | ||
return this.destroy(err); | ||
} | ||
if (resp && resp.status === RESUMABLE_INCOMPLETE_STATUS_CODE) { | ||
if (resp.headers.range) { | ||
const range = resp.headers.range; | ||
this.offset = Number(range.split('-')[1]) + 1; | ||
callback(); | ||
this.restart(); | ||
return; | ||
} | ||
this.destroy(err); | ||
} | ||
this.offset = 0; | ||
callback(); | ||
}); | ||
} | ||
makeRequest(reqOpts, callback) { | ||
if (this.encryption) { | ||
reqOpts.headers = reqOpts.headers || {}; | ||
reqOpts.headers['x-goog-encryption-algorithm'] = 'AES256'; | ||
reqOpts.headers['x-goog-encryption-key'] = this.encryption.key; | ||
reqOpts.headers['x-goog-encryption-key-sha256'] = this.encryption.hash; | ||
} | ||
if (this.userProject) { | ||
reqOpts.params = reqOpts.params || {}; | ||
reqOpts.params.userProject = this.userProject; | ||
} | ||
reqOpts.validateStatus = (status) => { | ||
return (status >= 200 && status < 300) || | ||
status === RESUMABLE_INCOMPLETE_STATUS_CODE; | ||
}; | ||
this.authClient.request(reqOpts).then(r => { | ||
return callback(null, r, r.data); | ||
}, (err) => { | ||
const body = err.response ? err.response.data : undefined; | ||
const e = (body && body.error) ? body.error : err; | ||
return callback(e, err.response, body); | ||
makeRequest(reqOpts) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
if (this.encryption) { | ||
reqOpts.headers = reqOpts.headers || {}; | ||
reqOpts.headers['x-goog-encryption-algorithm'] = 'AES256'; | ||
reqOpts.headers['x-goog-encryption-key'] = this.encryption.key.toString(); | ||
reqOpts.headers['x-goog-encryption-key-sha256'] = | ||
this.encryption.hash.toString(); | ||
} | ||
if (this.userProject) { | ||
reqOpts.params = reqOpts.params || {}; | ||
reqOpts.params.userProject = this.userProject; | ||
} | ||
reqOpts.validateStatus = () => true; | ||
const res = yield this.authClient.request(reqOpts); | ||
if (res.data && res.data.error) { | ||
const err = new Error(res.data.error); | ||
err.response = res; | ||
throw err; | ||
} | ||
// If no error was returned, but the response had an invalid status | ||
// code, create a new error to be passed to the callback. | ||
if ((res.status < 200 || res.status >= 300) && | ||
res.status !== RESUMABLE_INCOMPLETE_STATUS_CODE) { | ||
const e = new Error(`The request failed with a ${res.status}.`); | ||
e.code = res.status; | ||
} | ||
return res; | ||
}); | ||
} | ||
getRequestStream(reqOpts, callback) { | ||
if (this.userProject) { | ||
reqOpts.params = reqOpts.params || {}; | ||
reqOpts.params.userProject = this.userProject; | ||
} | ||
this.authClient.authorizeRequest(reqOpts) | ||
.then(opts => { | ||
const authorizedReqOpts = axiosToRequest(reqOpts); | ||
const requestStream = request(authorizedReqOpts); | ||
requestStream.on('error', this.destroy.bind(this)); | ||
requestStream.on('response', this.onResponse.bind(this)); | ||
requestStream.on('complete', (resp) => { | ||
const body = resp.body; | ||
if (body && body.error) | ||
this.destroy(body.error); | ||
}); | ||
// this makes the response body come back in the response (weird?) | ||
requestStream.callback = () => { }; | ||
callback(requestStream); | ||
}) | ||
.catch(err => { | ||
return this.destroy(wrapError('Could not authenticate request', err)); | ||
getRequestStream(reqOpts) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
try { | ||
if (this.userProject) { | ||
reqOpts.qs = reqOpts.qs || {}; | ||
reqOpts.qs.userProject = this.userProject; | ||
} | ||
const authHeaders = yield this.authClient.getRequestHeaders(reqOpts.url); | ||
reqOpts.headers = Object.assign({}, reqOpts.headers, authHeaders); | ||
const requestStream = request(reqOpts); | ||
requestStream.on('error', this.destroy.bind(this)); | ||
requestStream.on('response', this.onResponse.bind(this)); | ||
requestStream.on('complete', (resp) => { | ||
const body = resp.body; | ||
if (body && body.error) | ||
this.destroy(body.error); | ||
}); | ||
// this makes the response body come back in the response (weird?) | ||
requestStream.callback = () => { }; | ||
return requestStream; | ||
} | ||
catch (e) { | ||
this.destroy(e); | ||
throw e; | ||
} | ||
}); | ||
@@ -310,3 +343,3 @@ } | ||
onResponse(resp) { | ||
if (resp.status === 404) { | ||
if (resp.statusCode === 404) { | ||
if (this.numRetries < RETRY_LIMIT) { | ||
@@ -321,3 +354,3 @@ this.numRetries++; | ||
} | ||
if (resp.status > 499 && resp.status < 600) { | ||
if (resp.statusCode > 499 && resp.statusCode < 600) { | ||
if (this.numRetries < RETRY_LIMIT) { | ||
@@ -339,9 +372,2 @@ const randomMs = Math.round(Math.random() * 1000); | ||
exports.Upload = Upload; | ||
function axiosToRequest(opts) { | ||
const reqOpts = opts; | ||
reqOpts.qs = opts.params; | ||
reqOpts.json = opts.data; | ||
reqOpts.uri = opts.url; | ||
return reqOpts; | ||
} | ||
function upload(cfg) { | ||
@@ -353,5 +379,8 @@ return new Upload(cfg); | ||
const up = new Upload(cfg); | ||
up.createURI(callback); | ||
if (!callback) { | ||
return up.createURI(); | ||
} | ||
up.createURI().then(r => callback(null, r), callback); | ||
} | ||
exports.createURI = createURI; | ||
//# sourceMappingURL=index.js.map |
@@ -7,2 +7,59 @@ # Changelog | ||
## v0.14.0 | ||
01-23-2019 17:57 PST | ||
### New Features | ||
- feat: support async functions ([#164](https://github.com/googleapis/gcs-resumable-upload/pull/164)) | ||
- fix: use the reject handler for promises ([#144](https://github.com/googleapis/gcs-resumable-upload/pull/144)) | ||
- feat: add progress events ([#135](https://github.com/googleapis/gcs-resumable-upload/pull/135)) | ||
### Dependencies | ||
- fix(deps): update dependency google-auth-library to v3 ([#165](https://github.com/googleapis/gcs-resumable-upload/pull/165)) | ||
- refactor: use teeny-request (part 1) ([#141](https://github.com/googleapis/gcs-resumable-upload/pull/141)) | ||
- chore(deps): update dependency @types/configstore to v4 ([#145](https://github.com/googleapis/gcs-resumable-upload/pull/145)) | ||
- chore(deps): update dependency typescript to ~3.2.0 ([#140](https://github.com/googleapis/gcs-resumable-upload/pull/140)) | ||
- chore(deps): update dependency gts to ^0.9.0 ([#137](https://github.com/googleapis/gcs-resumable-upload/pull/137)) | ||
- chore(deps): update dependency through2 to v3 ([#131](https://github.com/googleapis/gcs-resumable-upload/pull/131)) | ||
- refactor: move from axios back to request ([#123](https://github.com/googleapis/gcs-resumable-upload/pull/123)) | ||
- chore(deps): update dependency nock to v10 ([#113](https://github.com/googleapis/gcs-resumable-upload/pull/113)) | ||
- chore: update the version of typescript ([#106](https://github.com/googleapis/gcs-resumable-upload/pull/106)) | ||
### Documentation | ||
- build: ignore googleapis.com in doc link checker ([#166](https://github.com/googleapis/gcs-resumable-upload/pull/166)) | ||
- build: check broken links in generated docs ([#162](https://github.com/googleapis/gcs-resumable-upload/pull/162)) | ||
### Internal / Testing Changes | ||
- fix: fix the unit tests ([#161](https://github.com/googleapis/gcs-resumable-upload/pull/161)) | ||
- chore(build): inject yoshi automation key ([#160](https://github.com/googleapis/gcs-resumable-upload/pull/160)) | ||
- chore: update nyc and eslint configs ([#159](https://github.com/googleapis/gcs-resumable-upload/pull/159)) | ||
- chore: fix publish.sh permission +x ([#156](https://github.com/googleapis/gcs-resumable-upload/pull/156)) | ||
- fix(build): fix Kokoro release script ([#155](https://github.com/googleapis/gcs-resumable-upload/pull/155)) | ||
- build: add Kokoro configs for autorelease ([#154](https://github.com/googleapis/gcs-resumable-upload/pull/154)) | ||
- chore: always nyc report before calling codecov ([#153](https://github.com/googleapis/gcs-resumable-upload/pull/153)) | ||
- chore: nyc ignore build/test by default ([#152](https://github.com/googleapis/gcs-resumable-upload/pull/152)) | ||
- chore: update synth and common config ([#150](https://github.com/googleapis/gcs-resumable-upload/pull/150)) | ||
- fix(build): fix system key decryption ([#142](https://github.com/googleapis/gcs-resumable-upload/pull/142)) | ||
- chore: add synth.metadata | ||
- chore: update eslintignore config ([#136](https://github.com/googleapis/gcs-resumable-upload/pull/136)) | ||
- chore: use latest npm on Windows ([#134](https://github.com/googleapis/gcs-resumable-upload/pull/134)) | ||
- chore: update CircleCI config ([#129](https://github.com/googleapis/gcs-resumable-upload/pull/129)) | ||
- chore: include build in eslintignore ([#126](https://github.com/googleapis/gcs-resumable-upload/pull/126)) | ||
- chore: update issue templates ([#121](https://github.com/googleapis/gcs-resumable-upload/pull/121)) | ||
- chore: remove old issue template ([#119](https://github.com/googleapis/gcs-resumable-upload/pull/119)) | ||
- build: run tests on node11 ([#118](https://github.com/googleapis/gcs-resumable-upload/pull/118)) | ||
- chores(build): run codecov on continuous builds ([#112](https://github.com/googleapis/gcs-resumable-upload/pull/112)) | ||
- chores(build): do not collect sponge.xml from windows builds ([#114](https://github.com/googleapis/gcs-resumable-upload/pull/114)) | ||
- chore: update new issue template ([#111](https://github.com/googleapis/gcs-resumable-upload/pull/111)) | ||
- build: fix codecov uploading on Kokoro ([#108](https://github.com/googleapis/gcs-resumable-upload/pull/108)) | ||
- Update kokoro config ([#105](https://github.com/googleapis/gcs-resumable-upload/pull/105)) | ||
- Update CI config ([#103](https://github.com/googleapis/gcs-resumable-upload/pull/103)) | ||
- Update kokoro config ([#101](https://github.com/googleapis/gcs-resumable-upload/pull/101)) | ||
- test: remove appveyor config ([#100](https://github.com/googleapis/gcs-resumable-upload/pull/100)) | ||
- Update kokoro config ([#99](https://github.com/googleapis/gcs-resumable-upload/pull/99)) | ||
- Enable prefer-const in the eslint config ([#98](https://github.com/googleapis/gcs-resumable-upload/pull/98)) | ||
- Enable no-var in eslint ([#97](https://github.com/googleapis/gcs-resumable-upload/pull/97)) | ||
- Update to new repo location ([#96](https://github.com/googleapis/gcs-resumable-upload/pull/96)) | ||
- Update CI config ([#95](https://github.com/googleapis/gcs-resumable-upload/pull/95)) | ||
## v0.13.0 | ||
@@ -9,0 +66,0 @@ |
{ | ||
"name": "gcs-resumable-upload", | ||
"version": "0.13.0", | ||
"version": "0.14.0", | ||
"description": "Upload a file to Google Cloud Storage with built-in resumable behavior", | ||
"repository": "GoogleCloudPlatform/gcs-resumable-upload", | ||
"repository": "googleapis/gcs-resumable-upload", | ||
"main": "build/src/index.js", | ||
@@ -12,4 +12,3 @@ "types": "build/src/index.d.ts", | ||
"scripts": { | ||
"test": "npm run test-only", | ||
"test-only": "mocha build/test", | ||
"test": "nyc mocha build/test", | ||
"lint": "gts check", | ||
@@ -25,3 +24,3 @@ "clean": "gts clean", | ||
"presystem-test": "npm run compile", | ||
"docs": "jsdoc -c .jsdoc.js" | ||
"docs": "compodoc src/" | ||
}, | ||
@@ -45,5 +44,4 @@ "keywords": [ | ||
"dependencies": { | ||
"axios": "^0.18.0", | ||
"configstore": "^4.0.0", | ||
"google-auth-library": "^2.0.0", | ||
"google-auth-library": "^3.0.0", | ||
"pumpify": "^1.5.1", | ||
@@ -54,3 +52,4 @@ "request": "^2.87.0", | ||
"devDependencies": { | ||
"@types/configstore": "^2.1.1", | ||
"@compodoc/compodoc": "^1.1.7", | ||
"@types/configstore": "^4.0.0", | ||
"@types/is-stream": "^1.1.0", | ||
@@ -64,16 +63,16 @@ "@types/mocha": "^5.2.1", | ||
"@types/through2": "^2.0.33", | ||
"assert-rejects": "^1.0.0", | ||
"gaxios": "^1.2.2", | ||
"codecov": "^3.0.4", | ||
"gts": "^0.8.0", | ||
"ink-docstrap": "^1.3.2", | ||
"gts": "^0.9.0", | ||
"intelli-espower-loader": "^1.0.1", | ||
"is-stream": "^1.1.0", | ||
"jsdoc": "^3.5.5", | ||
"mocha": "^5.2.0", | ||
"mockery": "^2.1.0", | ||
"nock": "^9.3.0", | ||
"nock": "^10.0.0", | ||
"nyc": "^13.0.0", | ||
"source-map-support": "^0.5.6", | ||
"through2": "^2.0.3", | ||
"typescript": "~3.0.0" | ||
"through2": "^3.0.0", | ||
"typescript": "~3.2.0" | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
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
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
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
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
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
47466
5
538
0
23
10
+ Addedbase64-js@1.5.1(transitive)
+ Addedbignumber.js@9.1.2(transitive)
+ Addeddebug@3.2.7(transitive)
+ Addedfast-text-encoding@1.0.6(transitive)
+ Addedgcp-metadata@1.0.0(transitive)
+ Addedgoogle-auth-library@3.1.2(transitive)
+ Addedjson-bigint@0.3.1(transitive)
+ Addedms@2.1.3(transitive)
- Removedaxios@^0.18.0
- Removedaxios@0.18.1(transitive)
- Removedfollow-redirects@1.5.10(transitive)
- Removedgcp-metadata@0.7.0(transitive)
- Removedgoogle-auth-library@2.0.2(transitive)
- Removedis-buffer@2.0.5(transitive)
- Removedretry-axios@0.3.2(transitive)
Updatedgoogle-auth-library@^3.0.0