Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allowing complete control of the axios instance at the request level (so we can use any axios feature) #62

Merged
merged 13 commits into from
Feb 26, 2019
Merged
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ module.exports = {
]
},
"env": {
es6: true,
"node": true
},
"extends": "eslint:recommended"
Expand Down
217 changes: 147 additions & 70 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,39 +1,23 @@
### Backblaze B2 Node.js Library
# Backblaze B2 Node.js Library

[![npm version](https://badge.fury.io/js/backblaze-b2.svg)](https://badge.fury.io/js/backblaze-b2) [![Build Status](https://travis-ci.org/yakovkhalinsky/backblaze-b2.svg?branch=master)](https://travis-ci.org/yakovkhalinsky/backblaze-b2)

A customizable B2 client for Node.js:

* Uses [axios](https://github.com/axios/axios). You can control the axios instance at the request level (see `axios` and `axiosOverride` config arguments) and at the global level (see `axios` config argument at instantiation) so you can use any axios feature.
* Automatically retries on request failure. You can control retry behaviour using the `retries` argument at instantiation.

## Usage

This library uses promises, so all actions on a `B2` instance return a promise in the following pattern:

``` javascript
b2.instanceFunction(arg1, arg2).then(
successFn(response) { ... },
errorFn(err) { ... }
);
```

### Status of project

See the [CHANGELOG](https://github.com/yakovkhalinsky/backblaze-b2/blob/master/CHANGELOG.md) for a history of updates.

### Contributing

Contributions, suggestions, and questions are welcome. Please review the [contributing guidelines](CONTRIBUTING.md) for details.

### Upgrading from 0.9.x to 1.0.x

For this update, we've switched the back end HTTP request library from `request` to `axios` as it has better Promise and progress support built in. However, there are a couple changes that will break your code and ruin your day. Here are the changes:
* The Promise resolution has a different data structure. Where previously, the request response data was the root object in the promise resolution (`res`), this data now resides in `res.data`.
* In v0.9.12, we added request progress reporting via the third parameter to `then()`. Because we are no longer using the same promise library, this functionality has been removed. However, progress reporting is still available by passing a callback function into the `b2.method()` that you're calling. See the documentation below for details.
* In v0.9.x, `b2.downloadFileById()` accepted a `fileId` parameter as a String or Number. As of 1.0.0, the first parameter is now expected to be a plain Object of arguments.

### Response Object

Each request returns an object with:
- `status` - int, html error Status
- `statusText`
- `headers`
- `config`
- `request`
- `data` - actual returned data from backblaze, https://www.backblaze.com/b2/docs/calling.html

### Basic Example

```javascript
Expand All @@ -47,49 +31,34 @@ const b2 = new B2({
async function GetBucket() {
try {
await b2.authorize(); // must authorize first
let response = await b2.getBucket({bucketName: 'my-bucket'});
let response = await b2.getBucket({ bucketName: 'my-bucket' });
console.log(response.data);
} catch (err) {
console.log('Error getting bucket:', err);
}
}
```

### Uploading Large Files

To upload large files, you need to split the file into parts (between 5MB and 5GB) and upload each part seperately.
### Response Object

First, you initiate the large file upload to get the fileId:
```javascript
let response = await b2.startLargeFile({bucketId, fileName});
let fileId = response.data.fileId;
```
Each request returns an object with:

Then for each part you request an `uploadUrl`, and use the response to upload the part:
```javascript
let response = await b2.getUploadPartUrl({fileId});
* `status` - int, html error Status
* `statusText`
* `headers`
* `config`
* `request`
* `data` - actual returned data from backblaze, https://www.backblaze.com/b2/docs/calling.html

let uploadURL = response.data.uploadUrl;
let authToken = response.data.authorizationToken;
### How it works

response = await b2.uploadPart({
partNumber: parNum,
uploadUrl: uploadURL,
uploadAuthToken: authToken,
data: buf
});
// status checks etc.
```
Each action (see reference below) takes arguments and constructs an axios request. You can add additional axios options at the request level using:

Then finish the uploadUrl:
```javascript
let response = await b2.finishLargeFile({
fileId,
partSha1Array: parts.map(buf => sha1(buf))
})
```
* The `axios` argument (object): each property in this object is added to the axios request object *only if it does not conflict* with an existing property.
* The `axiosOverride` argument (object): each property in this object is added to the axios request object by *overriding* conflicting properties, if any. Don't use this unless you know what you're doing!
* Both `axios` and `axiosOverride` work by recursively merging properties, so if you pass ```axios: { headers: { 'your-custom-header': 'header-value' } }```, the entire headers object will not be overridden - each header property (`your-custom-header`) will be compared.

### Usage
### Reference

```javascript
const B2 = require('backblaze-b2');
Expand All @@ -100,35 +69,70 @@ const B2 = require('backblaze-b2');
// create B2 object instance
const b2 = new B2({
accountId: 'applicationKeyId', // or accountId
applicationKey: 'applicationKey' // or masterApplicationKey
applicationKey: 'applicationKey', // or masterApplicationKey
// optional:
axios: {
// overrides the axios instance default config, see https://github.com/axios/axios
},
retry: {
retries: 3 // this is the default
// for additional options, see https://github.com/softonic/axios-retry
}
});

// common arguments - you can use these in any of the functions below
const common_args = {
// axios request level config, see https://github.com/axios/axios#request-config
axios: {
timeout: 30000 // (example)
},
axiosOverride: {
/* Don't use me unless you know what you're doing! */
}
}

// authorize with provided credentials
b2.authorize(); // returns promise
b2.authorize({
// ...common arguments (optional)
}); // returns promise

// create bucket
b2.createBucket(
bucketName,
bucketType // one of `allPublic`, `allPrivate`
); // returns promise
b2.createBucket({
bucketName: 'bucketName',
bucketType: 'bucketType' // one of `allPublic`, `allPrivate`
// ...common arguments (optional)
}); // returns promise

// delete bucket
b2.deleteBucket(bucketId); // returns promise
b2.deleteBucket({
bucketId: 'bucketId'
// ...common arguments (optional)
}); // returns promise

// list buckets
b2.listBuckets(); // returns promise
b2.listBuckets({
// ...common arguments (optional)
}); // returns promise

// get the bucket
b2.getBucket({
bucketName,
bucketId // optional
bucketName: 'bucketName',
bucketId: 'bucketId' // optional
// ...common arguments (optional)
}); // returns promise

// update bucket
b2.updateBucket(bucketId, bucketType); // returns promise
b2.updateBucket({
bucketId: 'bucketId',
bucketType: 'bucketType'
// ...common arguments (optional)
}); // returns promise

// get upload url
b2.getUploadUrl(bucketId); // returns promise
b2.getUploadUrl({
bucketId: 'bucketId'
// ...common arguments (optional)
}); // returns promise

// upload file
b2.uploadFile({
Expand All @@ -145,6 +149,7 @@ b2.uploadFile({
key2: 'value'
},
onUploadProgress: (event) => {} || null // progress monitoring
// ...common arguments (optional)
}); // returns promise

// list file names
Expand All @@ -154,30 +159,37 @@ b2.listFileNames({
maxFileCount: 100,
delimiter: '',
prefix: ''
// ...common arguments (optional)
}); // returns promise

// list file versions
b2.listFileVersions({
bucketId: 'bucketId',
startFileName: 'startFileName',
maxFileCount: 100
// ...common arguments (optional)
}); // returns promise

// hide file
b2.hideFile({
bucketId: 'bucketId',
fileName: 'fileName'
// ...common arguments (optional)
}); // returns promise

// get file info
b2.getFileInfo(fileId); // returns promise
b2.getFileInfo({
fileId: 'fileId'
// ...common arguments (optional)
}); // returns promise

// get download authorization
b2.getDownloadAuthorization({
bucketId: 'bucketId',
fileNamePrefix: 'fileNamePrefix',
validDurationInSeconds: 'validDurationInSeconds', // a number from 0 to 604800
b2ContentDisposition: 'b2ContentDisposition'
// ...common arguments (optional)
}); // returns promise

// download file by name
Expand All @@ -186,30 +198,35 @@ b2.downloadFileByName({
fileName: 'fileName',
responseType: 'arraybuffer', // options are as in axios: 'arraybuffer', 'blob', 'document', 'json', 'text', 'stream'
onDownloadProgress: (event) => {} || null // progress monitoring
// ...common arguments (optional)
}); // returns promise

// download file by fileId
b2.downloadFileById({
fileId: 'fileId',
responseType: 'arraybuffer', // options are as in axios: 'arraybuffer', 'blob', 'document', 'json', 'text', 'stream'
onDownloadProgress: (event) => {} || null // progress monitoring
// ...common arguments (optional)
}); // returns promise

// delete file version
b2.deleteFileVersion({
fileId: 'fileId',
fileName: 'fileName'
// ...common arguments (optional)
}); // returns promise

// start large file
b2.startLargeFile({
bucketId: 'bucketId',
fileName: 'fileName'
// ...common arguments (optional)
}); // returns promise

// get upload part url
b2.getUploadPartUrl({
fileId: 'fileId'
// ...common arguments (optional)
}); // returns promise

// get upload part
Expand All @@ -220,22 +237,82 @@ b2.uploadPart({
data: Buffer // this is expecting a Buffer not an encoded string,
hash: 'sha1-hash', // optional data hash, will use sha1(data) if not provided
onUploadProgress: (event) => {} || null // progress monitoring
// ...common arguments (optional)
}); // returns promise

// finish large file
b2.finishLargeFile({
fileId: 'fileId',
partSha1Array: [partSha1Array] // array of sha1 for each part
// ...common arguments (optional)
}); // returns promise

// cancel large file
b2.cancelLargeFile({
fileId: 'fileId'
// ...common arguments (optional)
}); // returns promise
```

### Authors
### Uploading Large Files Example

To upload large files, you should split the file into parts (between 5MB and 5GB) and upload each part seperately.

First, you initiate the large file upload to get the fileId:

```javascript
let response = await b2.startLargeFile({ bucketId, fileName });
let fileId = response.data.fileId;
```

Then for each part you request an `uploadUrl`, and use the response to upload the part:

```javascript
let response = await b2.getUploadPartUrl({ fileId });

let uploadURL = response.data.uploadUrl;
let authToken = response.data.authorizationToken;

response = await b2.uploadPart({
partNumber: parNum,
uploadUrl: uploadURL,
uploadAuthToken: authToken,
data: buf
});
// status checks etc.
```

Then finish the uploadUrl:

```javascript
let response = await b2.finishLargeFile({
fileId,
partSha1Array: parts.map(buf => sha1(buf))
})
```

## Changes

See the [CHANGELOG](https://github.com/yakovkhalinsky/backblaze-b2/blob/master/CHANGELOG.md) for a history of updates.

### Upgrading from 0.9.x to 1.0.x

For this update, we've switched the back end HTTP request library from `request` to `axios` as it has better Promise and progress support built in. However, there are a couple changes that will break your code and ruin your day. Here are the changes:

* The Promise resolution has a different data structure. Where previously, the request response data was the root object in the promise resolution (`res`), this data now resides in `res.data`.
* In v0.9.12, we added request progress reporting via the third parameter to `then()`. Because we are no longer using the same promise library, this functionality has been removed. However, progress reporting is still available by passing a callback function into the `b2.method()` that you're calling. See the documentation below for details.
* In v0.9.x, `b2.downloadFileById()` accepted a `fileId` parameter as a String or Number. As of 1.0.0, the first parameter is now expected to be a plain Object of arguments.

## Contributing

Contributions, suggestions, and questions are welcome. Please review the [contributing guidelines](CONTRIBUTING.md) for details.

### Authors and Contributors

* Yakov Khalinsky (@yakovkhalinsky)
* Ivan Kalinin (@IvanKalinin) at Isolary
* Brandon Patton (@crazyscience) at Isolary
* C. Bess (@cbess)
* Amit (@Amit-A)
* Zsombor Paróczi (@realhidden)
* Oden (@odensc)
19 changes: 12 additions & 7 deletions lib/actions/auth.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
var conf = require('../../conf');
var request = require('./../request');
var utils = require('./../utils');
const _ = require('lodash');
const conf = require('../../conf');
const request = require('./../request');
const utils = require('./../utils');

exports.authorize = function(b2) {
var options = getRequestOptions(b2.accountId, b2.applicationKey);

var axiosInstance = request.getInstance();
exports.authorize = function(b2, args) {
// merge order matters here: later objects override earlier objects
const options = _.merge({},
_.get(args, 'axios', {}),
getRequestOptions(b2.accountId, b2.applicationKey),
_.get(args, 'axiosOverride', {})
);
const axiosInstance = request.getInstance();
return axiosInstance(options).then(function(res) {
utils.saveAuthContext(b2, res.data);
return res; // For testing and/or Promise chaining
Expand Down
Loading