gcs-resumable-upload
Advanced tools
Comparing version 0.10.2 to 0.11.0
/// <reference types="request" /> | ||
/// <reference types="node" /> | ||
/// <reference types="pumpify" /> | ||
/// <reference types="configstore" /> | ||
import { AxiosRequestConfig, AxiosResponse } from 'axios'; | ||
import * as ConfigStore from 'configstore'; | ||
import { GoogleAuth, GoogleAuthOptions } from 'google-auth-library'; | ||
import * as Pumpify from 'pumpify'; | ||
import * as r from 'request'; | ||
export declare type RequestBody = any; | ||
export declare type RequestResponse = r.Response; | ||
export declare type RequestResponse = AxiosResponse; | ||
export declare type Request = r.Request; | ||
export declare type RequestOptions = r.OptionsWithUri; | ||
export declare type RequestCallback = (err: Error | null, response?: r.Response, body?: RequestBody) => void; | ||
export declare type RequestOptions = AxiosRequestConfig; | ||
export declare type RequestCallback = (err: Error | null, response?: AxiosResponse, body?: RequestBody) => void; | ||
export declare type AuthorizeRequestCallback = (err: Error | null, authorizedReqOpts: RequestOptions) => void; | ||
export declare type CreateUriCallback = (err: Error | null, uri?: string) => void; | ||
export interface Encryption { | ||
key: {}; | ||
hash: {}; | ||
} | ||
export interface UploadConfig { | ||
/** | ||
* The name of the destination bucket. | ||
*/ | ||
bucket: string; | ||
/** | ||
* The name of the destination file. | ||
*/ | ||
file: string; | ||
authConfig?: GoogleAuthOptions; | ||
/** | ||
* If you want to re-use an auth client from google-auto-auth, pass an | ||
* instance here. | ||
*/ | ||
authClient?: GoogleAuth; | ||
/** | ||
* This will cause the upload to fail if the current generation of the remote | ||
* object does not match the one provided here. | ||
*/ | ||
generation?: number; | ||
/** | ||
* A customer-supplied encryption key. See | ||
* https://cloud.google.com/storage/docs/encryption#customer-supplied. | ||
*/ | ||
key?: string | Buffer; | ||
/** | ||
* Resource name of the Cloud KMS key, of the form | ||
* `projects/my-project/locations/global/keyRings/my-kr/cryptoKeys/my-key`, | ||
* that will be used to encrypt the object. Overrides the object metadata's | ||
* `kms_key_name` value, if any. | ||
*/ | ||
kmsKeyName?: string; | ||
/** | ||
* Any metadata you wish to set on the object. | ||
*/ | ||
metadata?: ConfigMetadata; | ||
/** | ||
* The starting byte of the upload stream, for resuming an interrupted upload. | ||
* See | ||
* https://cloud.google.com/storage/docs/json_api/v1/how-tos/resumable-upload#resume-upload. | ||
*/ | ||
offset?: number; | ||
/** | ||
* Set an Origin header when creating the resumable upload URI. | ||
*/ | ||
origin?: string; | ||
/** | ||
* Apply a predefined set of access controls to the created file. | ||
*/ | ||
predefinedAcl?: 'authenticatedRead' | 'bucketOwnerFullControl' | 'bucketOwnerRead' | 'private' | 'projectPrivate' | 'publicRead'; | ||
/** | ||
* Make the uploaded file private. (Alias for config.predefinedAcl = | ||
* 'private') | ||
*/ | ||
private?: boolean; | ||
/** | ||
* Make the uploaded file public. (Alias for config.predefinedAcl = | ||
* 'publicRead') | ||
*/ | ||
public?: boolean; | ||
/** | ||
* If you already have a resumable URI from a previously-created resumable | ||
* upload, just pass it in here and we'll use that. | ||
*/ | ||
uri?: string; | ||
/** | ||
* If the bucket being accessed has requesterPays functionality enabled, this | ||
* can be set to control which project is billed for the access of this file. | ||
*/ | ||
userProject?: string; | ||
} | ||
export interface ConfigMetadata { | ||
/** | ||
* Set the length of the file being uploaded. | ||
*/ | ||
contentLength?: number; | ||
/** | ||
* Set the content type of the incoming data. | ||
*/ | ||
contentType?: string; | ||
} | ||
export declare class Upload extends Pumpify { | ||
bucket: string; | ||
file: string; | ||
authConfig?: { | ||
scopes?: string[]; | ||
}; | ||
authClient: GoogleAuth; | ||
generation?: number; | ||
key?: string | Buffer; | ||
kmsKeyName?: string; | ||
metadata: ConfigMetadata; | ||
offset?: number; | ||
origin?: string; | ||
predefinedAcl?: 'authenticatedRead' | 'bucketOwnerFullControl' | 'bucketOwnerRead' | 'private' | 'projectPrivate' | 'publicRead'; | ||
private?: boolean; | ||
public?: boolean; | ||
uri?: string; | ||
userProject?: string; | ||
encryption?: Encryption; | ||
configStore: ConfigStore; | ||
uriProvidedManually: boolean; | ||
numBytesWritten: number; | ||
numRetries: number; | ||
contentLength: number | '*'; | ||
private bufferStream?; | ||
private offsetStream?; | ||
constructor(cfg: UploadConfig); | ||
createURI(callback: CreateUriCallback): void; | ||
private continueUploading(); | ||
private startUploading(); | ||
private onChunk(chunk, enc, next); | ||
private getAndSetOffset(callback); | ||
private makeRequest(reqOpts, callback); | ||
private getRequestStream(reqOpts, callback); | ||
private restart(); | ||
private get(prop); | ||
private set(props); | ||
private deleteConfig(); | ||
/** | ||
* @return {bool} is the request good? | ||
*/ | ||
private onResponse(resp); | ||
} | ||
export declare function upload(cfg: UploadConfig): Upload; | ||
export declare function createURI(cfg: UploadConfig, callback: CreateUriCallback): void; |
"use strict"; | ||
var __extends = (this && this.__extends) || (function () { | ||
var extendStatics = Object.setPrototypeOf || | ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || | ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; | ||
return function (d, b) { | ||
extendStatics(d, b); | ||
function __() { this.constructor = d; } | ||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); | ||
}; | ||
})(); | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var ConfigStore = require("configstore"); | ||
var crypto = require("crypto"); | ||
var google_auth_library_1 = require("google-auth-library"); | ||
var Pumpify = require("pumpify"); | ||
var r = require("request"); | ||
var stream_1 = require("stream"); | ||
var util = require("util"); | ||
var streamEvents = require('stream-events'); | ||
var googleAuth = require('google-auto-auth'); | ||
var pumpify = require('pumpify'); | ||
var streamEvents = require("stream-events"); | ||
var request = r.defaults({ json: true, pool: { maxSockets: Infinity } }); | ||
@@ -19,323 +28,330 @@ var BASE_URI = 'https://www.googleapis.com/upload/storage/v1/b'; | ||
}; | ||
function Upload(cfg) { | ||
var _this = this; | ||
pumpify.call(this); | ||
streamEvents.call(this); | ||
cfg = cfg || {}; | ||
if (!cfg.bucket || !cfg.file) { | ||
throw new Error('A bucket and file name are required'); | ||
var Upload = /** @class */ (function (_super) { | ||
__extends(Upload, _super); | ||
function Upload(cfg) { | ||
var _this = _super.call(this) || this; | ||
_this.numBytesWritten = 0; | ||
_this.numRetries = 0; | ||
streamEvents.call(_this); | ||
cfg = cfg || {}; | ||
if (!cfg.bucket || !cfg.file) { | ||
throw new Error('A bucket and file name are required'); | ||
} | ||
cfg.authConfig = cfg.authConfig || {}; | ||
cfg.authConfig.scopes = | ||
['https://www.googleapis.com/auth/devstorage.full_control']; | ||
_this.authClient = cfg.authClient || new google_auth_library_1.GoogleAuth(cfg.authConfig); | ||
_this.bucket = cfg.bucket; | ||
_this.file = cfg.file; | ||
_this.generation = cfg.generation; | ||
_this.kmsKeyName = cfg.kmsKeyName; | ||
_this.metadata = cfg.metadata || {}; | ||
_this.offset = cfg.offset; | ||
_this.origin = cfg.origin; | ||
_this.userProject = cfg.userProject; | ||
if (cfg.key) { | ||
/** | ||
* NOTE: This is `as string` because there appears to be some weird kind | ||
* of TypeScript bug as 2.8. Tracking the issue here: | ||
* https://github.com/Microsoft/TypeScript/issues/23155 | ||
*/ | ||
var base64Key = Buffer.from(cfg.key).toString('base64'); | ||
_this.encryption = { | ||
key: base64Key, | ||
hash: crypto.createHash('sha256').update(cfg.key).digest('base64') | ||
}; | ||
} | ||
_this.predefinedAcl = cfg.predefinedAcl; | ||
if (cfg.private) | ||
_this.predefinedAcl = 'private'; | ||
if (cfg.public) | ||
_this.predefinedAcl = 'publicRead'; | ||
_this.configStore = new ConfigStore('gcs-resumable-upload'); | ||
_this.uriProvidedManually = !!cfg.uri; | ||
_this.uri = cfg.uri || _this.get('uri'); | ||
_this.numBytesWritten = 0; | ||
_this.numRetries = 0; | ||
var contentLength = cfg.metadata ? Number(cfg.metadata.contentLength) : NaN; | ||
_this.contentLength = isNaN(contentLength) ? '*' : contentLength; | ||
_this.once('writing', function () { | ||
if (_this.uri) { | ||
_this.continueUploading(); | ||
} | ||
else { | ||
_this.createURI(function (err) { | ||
if (err) { | ||
return _this.destroy(err); | ||
} | ||
_this.startUploading(); | ||
}); | ||
} | ||
}); | ||
return _this; | ||
} | ||
cfg.authConfig = cfg.authConfig || {}; | ||
cfg.authConfig.scopes = | ||
['https://www.googleapis.com/auth/devstorage.full_control']; | ||
this.authClient = cfg.authClient || googleAuth(cfg.authConfig); | ||
this.bucket = cfg.bucket; | ||
this.file = cfg.file; | ||
this.generation = cfg.generation; | ||
this.kmsKeyName = cfg.kmsKeyName; | ||
this.metadata = cfg.metadata || {}; | ||
this.offset = cfg.offset; | ||
this.origin = cfg.origin; | ||
this.userProject = cfg.userProject; | ||
if (cfg.key) { | ||
/** | ||
* NOTE: This is `as string` because there appears to be some weird kind | ||
* of TypeScript bug as 2.8. Tracking the issue here: | ||
* https://github.com/Microsoft/TypeScript/issues/23155 | ||
*/ | ||
var base64Key = Buffer.from(cfg.key).toString('base64'); | ||
this.encryption = { | ||
key: base64Key, | ||
hash: crypto.createHash('sha256').update(cfg.key).digest('base64') | ||
Upload.prototype.createURI = function (callback) { | ||
var _this = this; | ||
var metadata = this.metadata; | ||
var reqOpts = { | ||
method: 'POST', | ||
url: [BASE_URI, this.bucket, 'o'].join('/'), | ||
params: { name: this.file, uploadType: 'resumable' }, | ||
data: metadata, | ||
headers: {} | ||
}; | ||
} | ||
this.predefinedAcl = cfg.predefinedAcl; | ||
if (cfg.private) | ||
this.predefinedAcl = 'private'; | ||
if (cfg.public) | ||
this.predefinedAcl = 'publicRead'; | ||
this.configStore = new ConfigStore('gcs-resumable-upload'); | ||
this.uriProvidedManually = !!cfg.uri; | ||
this.uri = cfg.uri || this.get('uri'); | ||
this.numBytesWritten = 0; | ||
this.numRetries = 0; | ||
var contentLength = cfg.metadata ? Number(cfg.metadata.contentLength) : NaN; | ||
this.contentLength = isNaN(contentLength) ? '*' : contentLength; | ||
this.once('writing', function () { | ||
if (_this.uri) { | ||
_this.continueUploading(); | ||
if (metadata.contentLength) { | ||
reqOpts.headers['X-Upload-Content-Length'] = metadata.contentLength; | ||
} | ||
else { | ||
_this.createURI(function (err) { | ||
if (err) { | ||
return _this.destroy(err); | ||
} | ||
_this.startUploading(); | ||
}); | ||
if (metadata.contentType) { | ||
reqOpts.headers['X-Upload-Content-Type'] = metadata.contentType; | ||
} | ||
}); | ||
} | ||
util.inherits(Upload, pumpify); | ||
Upload.prototype.createURI = function (callback) { | ||
var _this = this; | ||
var metadata = this.metadata; | ||
var reqOpts = { | ||
method: 'POST', | ||
uri: [BASE_URI, this.bucket, 'o'].join('/'), | ||
qs: { name: this.file, uploadType: 'resumable' }, | ||
json: metadata, | ||
headers: {} | ||
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, function (err, resp) { | ||
if (err) { | ||
return callback(err); | ||
} | ||
var uri = resp.headers.location; | ||
_this.uri = uri; | ||
_this.set({ uri: uri }); | ||
_this.offset = 0; | ||
callback(null, uri); | ||
}); | ||
}; | ||
if (metadata.contentLength) { | ||
reqOpts.headers['X-Upload-Content-Length'] = metadata.contentLength; | ||
} | ||
if (metadata.contentType) { | ||
reqOpts.headers['X-Upload-Content-Type'] = metadata.contentType; | ||
} | ||
if (typeof this.generation !== 'undefined') { | ||
reqOpts.qs.ifGenerationMatch = this.generation; | ||
} | ||
if (this.kmsKeyName) { | ||
reqOpts.qs.kmsKeyName = this.kmsKeyName; | ||
} | ||
if (this.predefinedAcl) { | ||
reqOpts.qs.predefinedAcl = this.predefinedAcl; | ||
} | ||
if (this.origin) { | ||
reqOpts.headers.Origin = this.origin; | ||
} | ||
this.makeRequest(reqOpts, function (err, resp) { | ||
if (err) { | ||
return callback(err); | ||
Upload.prototype.continueUploading = function () { | ||
if (typeof this.offset === 'number') { | ||
return this.startUploading(); | ||
} | ||
var uri = resp.headers.location; | ||
_this.uri = uri; | ||
_this.set({ uri: uri }); | ||
_this.offset = 0; | ||
callback(null, uri); | ||
}); | ||
}; | ||
Upload.prototype.continueUploading = function () { | ||
if (typeof this.offset === 'number') { | ||
return this.startUploading(); | ||
} | ||
this.getAndSetOffset(this.startUploading.bind(this)); | ||
}; | ||
Upload.prototype.startUploading = function () { | ||
var _this = this; | ||
var reqOpts = { | ||
method: 'PUT', | ||
uri: this.uri, | ||
headers: { 'Content-Range': 'bytes ' + this.offset + '-*/' + this.contentLength } | ||
this.getAndSetOffset(this.startUploading.bind(this)); | ||
}; | ||
var bufferStream = this.bufferStream = new stream_1.PassThrough(); | ||
var offsetStream = this.offsetStream = | ||
new stream_1.PassThrough({ transform: this.onChunk.bind(this) }); | ||
var delayStream = new stream_1.PassThrough(); | ||
this.getRequestStream(reqOpts, function (requestStream) { | ||
_this.setPipeline(bufferStream, offsetStream, requestStream, delayStream); | ||
// wait for "complete" from request before letting the stream finish | ||
delayStream.on('prefinish', function () { | ||
_this.cork(); | ||
}); | ||
requestStream.on('complete', function (resp) { | ||
if (resp.statusCode < 200 || resp.statusCode > 299) { | ||
_this.destroy(new Error('Upload failed')); | ||
return; | ||
Upload.prototype.startUploading = function () { | ||
var _this = this; | ||
var reqOpts = { | ||
method: 'PUT', | ||
url: this.uri, | ||
headers: { | ||
'Content-Range': 'bytes ' + this.offset + '-*/' + this.contentLength | ||
} | ||
_this.emit('metadata', resp.body); | ||
_this.deleteConfig(); | ||
_this.uncork(); | ||
}; | ||
var bufferStream = this.bufferStream = new stream_1.PassThrough(); | ||
var offsetStream = this.offsetStream = | ||
new stream_1.PassThrough({ transform: this.onChunk.bind(this) }); | ||
var delayStream = new stream_1.PassThrough(); | ||
this.getRequestStream(reqOpts, function (requestStream) { | ||
_this.setPipeline(bufferStream, offsetStream, requestStream, delayStream); | ||
// wait for "complete" from request before letting the stream finish | ||
delayStream.on('prefinish', function () { | ||
_this.cork(); | ||
}); | ||
requestStream.on('complete', function (resp) { | ||
if (resp.statusCode < 200 || resp.statusCode > 299) { | ||
_this.destroy(new Error('Upload failed')); | ||
return; | ||
} | ||
_this.emit('metadata', resp.body); | ||
_this.deleteConfig(); | ||
_this.uncork(); | ||
}); | ||
}); | ||
}); | ||
}; | ||
Upload.prototype.onChunk = function (chunk, enc, next) { | ||
var offset = this.offset; | ||
var numBytesWritten = this.numBytesWritten; | ||
// check if this is the same content uploaded previously. this caches a slice | ||
// of the first chunk, then compares it with the first byte of incoming data | ||
if (numBytesWritten === 0) { | ||
var cachedFirstChunk = this.get('firstChunk'); | ||
var firstChunk = chunk.slice(0, 16).valueOf(); | ||
if (!cachedFirstChunk) { | ||
// This is a new upload. Cache the first chunk. | ||
this.set({ uri: this.uri, firstChunk: firstChunk }); | ||
} | ||
else { | ||
// this continues an upload in progress. check if the bytes are the same | ||
cachedFirstChunk = Buffer.from(cachedFirstChunk); | ||
var nextChunk = Buffer.from(firstChunk); | ||
if (Buffer.compare(cachedFirstChunk, nextChunk) !== 0) { | ||
// this data is not the same. start a new upload | ||
this.bufferStream.unshift(chunk); | ||
this.bufferStream.unpipe(this.offsetStream); | ||
this.restart(); | ||
return; | ||
}; | ||
Upload.prototype.onChunk = function (chunk, enc, next) { | ||
var offset = this.offset; | ||
var numBytesWritten = this.numBytesWritten; | ||
// check if this is the same content uploaded previously. this caches a | ||
// slice of the first chunk, then compares it with the first byte of | ||
// incoming data | ||
if (numBytesWritten === 0) { | ||
var cachedFirstChunk = this.get('firstChunk'); | ||
var firstChunk = chunk.slice(0, 16).valueOf(); | ||
if (!cachedFirstChunk) { | ||
// This is a new upload. Cache the first chunk. | ||
this.set({ uri: this.uri, firstChunk: firstChunk }); | ||
} | ||
else { | ||
// this continues an upload in progress. check if the bytes are the same | ||
cachedFirstChunk = Buffer.from(cachedFirstChunk); | ||
var nextChunk = Buffer.from(firstChunk); | ||
if (Buffer.compare(cachedFirstChunk, nextChunk) !== 0) { | ||
// this data is not the same. start a new upload | ||
this.bufferStream.unshift(chunk); | ||
this.bufferStream.unpipe(this.offsetStream); | ||
this.restart(); | ||
return; | ||
} | ||
} | ||
} | ||
} | ||
var length = chunk.length; | ||
if (typeof chunk === 'string') | ||
length = Buffer.byteLength(chunk, enc); | ||
if (numBytesWritten < offset) | ||
chunk = chunk.slice(offset - numBytesWritten); | ||
this.numBytesWritten += length; | ||
// only push data from the byte after the one we left off on | ||
next(null, this.numBytesWritten > offset ? chunk : undefined); | ||
}; | ||
Upload.prototype.getAndSetOffset = function (callback) { | ||
var _this = this; | ||
this.makeRequest({ | ||
method: 'PUT', | ||
uri: this.uri, | ||
headers: { 'Content-Length': 0, 'Content-Range': 'bytes */*' } | ||
}, function (err, resp) { | ||
if (err) { | ||
// we don't return a 404 to the user if they provided the resumable | ||
// URI. if we're just using the configstore file to tell us that this | ||
// file exists, and it turns out that it doesn't (the 404), that's | ||
// probably stale config data. | ||
if (resp && resp.statusCode === 404 && !_this.uriProvidedManually) { | ||
return _this.restart(); | ||
var length = chunk.length; | ||
if (typeof chunk === 'string') | ||
length = Buffer.byteLength(chunk, enc); | ||
if (numBytesWritten < offset) | ||
chunk = chunk.slice(offset - numBytesWritten); | ||
this.numBytesWritten += length; | ||
// only push data from the byte after the one we left off on | ||
next(null, this.numBytesWritten > offset ? chunk : undefined); | ||
}; | ||
Upload.prototype.getAndSetOffset = function (callback) { | ||
var _this = this; | ||
var opts = { | ||
method: 'PUT', | ||
url: this.uri, | ||
headers: { 'Content-Length': 0, 'Content-Range': 'bytes */*' } | ||
}; | ||
this.makeRequest(opts, function (err, resp) { | ||
if (err) { | ||
// we don't return a 404 to the user if they provided the resumable | ||
// URI. if we're just using the configstore file to tell us that this | ||
// file exists, and it turns out that it doesn't (the 404), that's | ||
// probably stale config data. | ||
if (resp && resp.status === 404 && !_this.uriProvidedManually) { | ||
return _this.restart(); | ||
} | ||
// this resumable upload is unrecoverable (bad data or service error). | ||
// - | ||
// https://github.com/stephenplusplus/gcs-resumable-upload/issues/15 | ||
// - | ||
// https://github.com/stephenplusplus/gcs-resumable-upload/pull/16#discussion_r80363774 | ||
if (resp && resp.status === TERMINATED_UPLOAD_STATUS_CODE) { | ||
return _this.restart(); | ||
} | ||
return _this.destroy(err); | ||
} | ||
// this resumable upload is unrecoverable (bad data or service error). | ||
// - | ||
// https://github.com/stephenplusplus/gcs-resumable-upload/issues/15 | ||
// - | ||
// https://github.com/stephenplusplus/gcs-resumable-upload/pull/16#discussion_r80363774 | ||
if (resp && resp.statusCode === TERMINATED_UPLOAD_STATUS_CODE) { | ||
return _this.restart(); | ||
if (resp && resp.status === RESUMABLE_INCOMPLETE_STATUS_CODE) { | ||
if (resp.headers.range) { | ||
var range = resp.headers.range; | ||
_this.offset = Number(range.split('-')[1]) + 1; | ||
callback(); | ||
return; | ||
} | ||
} | ||
return _this.destroy(err); | ||
_this.offset = 0; | ||
callback(); | ||
}); | ||
}; | ||
Upload.prototype.makeRequest = function (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 (resp.statusCode === RESUMABLE_INCOMPLETE_STATUS_CODE) { | ||
if (resp.headers.range) { | ||
var range = resp.headers.range; | ||
_this.offset = Number(range.split('-')[1]) + 1; | ||
callback(); | ||
return; | ||
} | ||
if (this.userProject) { | ||
reqOpts.params = reqOpts.params || {}; | ||
reqOpts.params.userProject = this.userProject; | ||
} | ||
_this.offset = 0; | ||
callback(); | ||
}); | ||
}; | ||
Upload.prototype.makeRequest = function (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.qs = reqOpts.qs || {}; | ||
reqOpts.qs.userProject = this.userProject; | ||
} | ||
this.authClient.authorizeRequest(reqOpts, function (err, authorizedReqOpts) { | ||
if (err) { | ||
err = wrapError('Could not authenticate request', err); | ||
return callback(err, null, null); | ||
reqOpts.validateStatus = function (status) { | ||
return (status >= 200 && status < 300) || | ||
status === RESUMABLE_INCOMPLETE_STATUS_CODE; | ||
}; | ||
this.authClient.request(reqOpts).then(function (r) { | ||
return callback(null, r, r.data); | ||
}, function (err) { | ||
var body = err.response ? err.response.data : undefined; | ||
var e = (body && body.error) ? body.error : err; | ||
return callback(e, err.response, body); | ||
}); | ||
}; | ||
Upload.prototype.getRequestStream = function (reqOpts, callback) { | ||
var _this = this; | ||
if (this.userProject) { | ||
reqOpts.params = reqOpts.params || {}; | ||
reqOpts.params.userProject = this.userProject; | ||
} | ||
request(authorizedReqOpts, function (err, resp, body) { | ||
this.authClient.authorizeRequest(reqOpts) | ||
.then(function (opts) { | ||
var authorizedReqOpts = axiosToRequest(reqOpts); | ||
var requestStream = request(authorizedReqOpts); | ||
requestStream.on('error', _this.destroy.bind(_this)); | ||
requestStream.on('response', _this.onResponse.bind(_this)); | ||
requestStream.on('complete', function (resp) { | ||
var body = resp.body; | ||
if (body && body.error) | ||
_this.destroy(body.error); | ||
}); | ||
// this makes the response body come back in the response (weird?) | ||
requestStream.callback = function () { }; | ||
callback(requestStream); | ||
}) | ||
.catch(function (err) { | ||
return _this.destroy(wrapError('Could not authenticate request', err)); | ||
}); | ||
}; | ||
Upload.prototype.restart = function () { | ||
var _this = this; | ||
this.numBytesWritten = 0; | ||
this.deleteConfig(); | ||
this.createURI(function (err) { | ||
if (err) { | ||
return callback(err, resp, body); | ||
return _this.destroy(err); | ||
} | ||
if (body && body.error) { | ||
return callback(body.error, resp, body); | ||
_this.startUploading(); | ||
}); | ||
}; | ||
Upload.prototype.get = function (prop) { | ||
var store = this.configStore.get([this.bucket, this.file].join('/')); | ||
return store && store[prop]; | ||
}; | ||
// tslint:disable-next-line no-any | ||
Upload.prototype.set = function (props) { | ||
this.configStore.set([this.bucket, this.file].join('/'), props); | ||
}; | ||
Upload.prototype.deleteConfig = function () { | ||
this.configStore.delete([this.bucket, this.file].join('/')); | ||
}; | ||
/** | ||
* @return {bool} is the request good? | ||
*/ | ||
Upload.prototype.onResponse = function (resp) { | ||
if (resp.status === 404) { | ||
if (this.numRetries < RETRY_LIMIT) { | ||
this.numRetries++; | ||
this.startUploading(); | ||
} | ||
var nonSuccess = Math.floor(resp.statusCode / 100) !== 2; // 200-299 status code | ||
if (nonSuccess && | ||
resp.statusCode !== RESUMABLE_INCOMPLETE_STATUS_CODE) { | ||
return callback(new Error(body), resp, body); | ||
else { | ||
this.destroy(new Error('Retry limit exceeded')); | ||
} | ||
callback(null, resp, body); | ||
}); | ||
}); | ||
}; | ||
Upload.prototype.getRequestStream = function (reqOpts, callback) { | ||
var _this = this; | ||
if (this.userProject) { | ||
reqOpts.qs = reqOpts.qs || {}; | ||
reqOpts.qs.userProject = this.userProject; | ||
} | ||
this.authClient.authorizeRequest(reqOpts, function (err, authorizedReqOpts) { | ||
if (err) { | ||
return _this.destroy(wrapError('Could not authenticate request', err)); | ||
return false; | ||
} | ||
var requestStream = request(authorizedReqOpts); | ||
requestStream.on('error', _this.destroy.bind(_this)); | ||
requestStream.on('response', _this.onResponse.bind(_this)); | ||
requestStream.on('complete', function (resp) { | ||
var body = resp.body; | ||
if (body && body.error) | ||
_this.destroy(body.error); | ||
}); | ||
// this makes the response body come back in the response (weird?) | ||
requestStream.callback = function () { }; | ||
callback(requestStream); | ||
}); | ||
}; | ||
Upload.prototype.restart = function () { | ||
var _this = this; | ||
this.numBytesWritten = 0; | ||
this.deleteConfig(); | ||
this.createURI(function (err) { | ||
if (err) { | ||
return _this.destroy(err); | ||
if (resp.status > 499 && resp.status < 600) { | ||
if (this.numRetries < RETRY_LIMIT) { | ||
var randomMs = Math.round(Math.random() * 1000); | ||
var waitTime = Math.pow(2, this.numRetries) * 1000 + randomMs; | ||
this.numRetries++; | ||
setTimeout(this.continueUploading.bind(this), waitTime); | ||
} | ||
else { | ||
this.destroy(new Error('Retry limit exceeded')); | ||
} | ||
return false; | ||
} | ||
_this.startUploading(); | ||
}); | ||
}; | ||
Upload.prototype.get = function (prop) { | ||
var store = this.configStore.get([this.bucket, this.file].join('/')); | ||
return store && store[prop]; | ||
}; | ||
// tslint:disable-next-line no-any | ||
Upload.prototype.set = function (props) { | ||
this.configStore.set([this.bucket, this.file].join('/'), props); | ||
}; | ||
Upload.prototype.deleteConfig = function () { | ||
this.configStore.delete([this.bucket, this.file].join('/')); | ||
}; | ||
/** | ||
* @return {bool} is the request good? | ||
*/ | ||
Upload.prototype.onResponse = function (resp) { | ||
if (resp.statusCode === 404) { | ||
if (this.numRetries < RETRY_LIMIT) { | ||
this.numRetries++; | ||
this.startUploading(); | ||
} | ||
else { | ||
this.destroy(new Error('Retry limit exceeded')); | ||
} | ||
return false; | ||
} | ||
if (resp.statusCode > 499 && resp.statusCode < 600) { | ||
if (this.numRetries < RETRY_LIMIT) { | ||
var randomMs = Math.round(Math.random() * 1000); | ||
var waitTime = Math.pow(2, this.numRetries) * 1000 + randomMs; | ||
this.numRetries++; | ||
setTimeout(this.continueUploading.bind(this), waitTime); | ||
} | ||
else { | ||
this.destroy(new Error('Retry limit exceeded')); | ||
} | ||
return false; | ||
} | ||
this.emit('response', resp); | ||
return true; | ||
}; | ||
this.emit('response', resp); | ||
return true; | ||
}; | ||
return Upload; | ||
}(Pumpify)); | ||
exports.Upload = Upload; | ||
function axiosToRequest(opts) { | ||
var reqOpts = opts; | ||
reqOpts.qs = opts.params; | ||
reqOpts.json = opts.data; | ||
reqOpts.uri = opts.url; | ||
return reqOpts; | ||
} | ||
function upload(cfg) { | ||
// tslint:disable-next-line no-any | ||
return new Upload(cfg); | ||
} | ||
// tslint:disable-next-line no-any | ||
upload.createURI = | ||
function (cfg, callback) { | ||
// tslint:disable-next-line no-any | ||
var up = new Upload(cfg); | ||
up.createURI(callback); | ||
}; | ||
module.exports = upload; | ||
exports.upload = upload; | ||
function createURI(cfg, callback) { | ||
var up = new Upload(cfg); | ||
up.createURI(callback); | ||
} | ||
exports.createURI = createURI; | ||
//# sourceMappingURL=index.js.map |
{ | ||
"name": "gcs-resumable-upload", | ||
"version": "0.10.2", | ||
"version": "0.11.0", | ||
"description": "Upload a file to Google Cloud Storage with built-in resumable behavior", | ||
@@ -12,3 +12,3 @@ "repository": "stephenplusplus/gcs-resumable-upload", | ||
"scripts": { | ||
"test": "mocha build/test -r source-map-support/register", | ||
"test": "mocha build/test -r source-map-support/register --timeout 4000", | ||
"check": "gts check", | ||
@@ -37,4 +37,5 @@ "clean": "gts clean", | ||
"dependencies": { | ||
"axios": "^0.18.0", | ||
"configstore": "^3.1.2", | ||
"google-auto-auth": "^0.10.0", | ||
"google-auth-library": "^1.4.0", | ||
"pumpify": "^1.4.0", | ||
@@ -51,5 +52,5 @@ "request": "^2.85.0", | ||
"@types/node": "^9.6.1", | ||
"@types/pumpify": "^1.4.0", | ||
"@types/request": "^2.47.0", | ||
"@types/through2": "^2.0.33", | ||
"through2": "^2.0.3", | ||
"gts": "^0.5.4", | ||
@@ -61,4 +62,5 @@ "is-stream": "^1.1.0", | ||
"source-map-support": "^0.5.4", | ||
"through2": "^2.0.3", | ||
"typescript": "~2.8.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
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
41334
519
6
17
+ Addedaxios@^0.18.0
+ Addedgoogle-auth-library@^1.4.0
- Removedgoogle-auto-auth@^0.10.0
- Removedasync@2.6.4(transitive)
- Removedgoogle-auto-auth@0.10.1(transitive)
- Removedlodash@4.17.21(transitive)