Skip to content

Commit

Permalink
feat: add ability to augment the user agent (#76)
Browse files Browse the repository at this point in the history
  • Loading branch information
JustinBeckwith committed Jan 21, 2019
1 parent d10cc70 commit 8ee2bd9
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 10 deletions.
2 changes: 2 additions & 0 deletions package.json
Expand Up @@ -31,6 +31,7 @@
"license": "Apache-2.0",
"devDependencies": {
"@compodoc/compodoc": "^1.1.7",
"@types/chai": "^4.1.7",
"@types/execa": "^0.9.0",
"@types/mocha": "^5.2.5",
"@types/mv": "^2.1.0",
Expand All @@ -41,6 +42,7 @@
"@types/tmp": "0.0.33",
"@types/url-template": "^2.0.28",
"@types/uuid": "^3.4.3",
"chai": "^4.2.0",
"codecov": "^3.0.4",
"gts": "^0.9.0",
"ink-docstrap": "^1.3.2",
Expand Down
18 changes: 17 additions & 1 deletion src/api.ts
Expand Up @@ -18,7 +18,7 @@ import {Endpoint} from './endpoint';

// tslint:disable-next-line no-any
export interface APIRequestParams<T = any> {
options: GaxiosOptions;
options: MethodOptions;
params: T;
requiredParams: string[];
pathParams: string[];
Expand All @@ -45,7 +45,23 @@ export interface GlobalOptions extends GaxiosOptions {

export interface MethodOptions extends GaxiosOptions {
rootUrl?: string;
userAgentDirectives?: UserAgentDirective[];
}

/**
* An additional directive to add to the user agent header.
* Directives come in the form of:
* User-Agent: <product> / <product-version> <comment>
*
* For more information, see:
* https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent
*/
export interface UserAgentDirective {
product: string;
version: string;
comment?: string;
}

export interface ServiceOptions extends GlobalOptions {
version?: string;
}
Expand Down
28 changes: 21 additions & 7 deletions src/apirequest.ts
Expand Up @@ -24,7 +24,6 @@ import {SchemaParameters} from './schema';

// tslint:disable-next-line no-var-requires
const pkg = require('../../package.json');
const USER_AGENT = `google-api-nodejs-client/${pkg.version} (gzip)`;

function isReadableStream(obj: stream.Readable|string) {
return obj instanceof stream.Readable && typeof obj._read === 'function';
Expand Down Expand Up @@ -145,7 +144,7 @@ async function createAPIRequestAsync<T>(parameters: APIRequestParams) {
// values are serialized like this:
// myParams: ['one', 'two'] ---> 'myParams=one&myParams=two'
// This serializer also encodes spaces in the querystring as `%20`,
// whereas the default serializer in axios encodes to a `+`.
// whereas the default serializer in gaxios encodes to a `+`.
options.paramsSerializer = (params) => {
return qs.stringify(params, {arrayFormat: 'repeat'});
};
Expand All @@ -165,7 +164,7 @@ async function createAPIRequestAsync<T>(parameters: APIRequestParams) {
if (parameters.mediaUrl && media.body) {
options.url = parameters.mediaUrl;
if (resource) {
// Axios doesn't support multipart/related uploads, so it has to
// gaxios doesn't support multipart/related uploads, so it has to
// be implemented here.
params.uploadType = 'multipart';
const multipart = [
Expand All @@ -189,7 +188,7 @@ async function createAPIRequestAsync<T>(parameters: APIRequestParams) {
rStream.push(part.body);
rStream.push('\r\n');
} else {
// Axios does not natively support onUploadProgress in node.js.
// Gaxios does not natively support onUploadProgress in node.js.
// Pipe through the pStream first to read the number of bytes read
// for the purpose of tracking progress.
pStream.on('progress', bytesRead => {
Expand Down Expand Up @@ -223,10 +222,25 @@ async function createAPIRequestAsync<T>(parameters: APIRequestParams) {
options.params = params;
if (!isBrowser()) {
options.headers!['Accept-Encoding'] = 'gzip';
options.headers!['User-Agent'] = USER_AGENT;
const directives = options.userAgentDirectives || [];
directives.push({
product: 'google-api-nodejs-client',
version: pkg.version,
comment: 'gzip'
});
const userAgent = directives
.map(d => {
let line = `${d.product}/${d.version}`;
if (d.comment) {
line += ` (${d.comment})`;
}
return line;
})
.join(' ');
options.headers!['User-Agent'] = userAgent;
}

// By default Axios treats any 2xx as valid, and all non 2xx status
// By default gaxios treats any 2xx as valid, and all non 2xx status
// codes as errors. This is a problem for HTTP 304s when used along
// with an eTag.
if (!options.validateStatus) {
Expand All @@ -235,7 +249,7 @@ async function createAPIRequestAsync<T>(parameters: APIRequestParams) {
};
}

// Combine the AxiosRequestConfig options passed with this specific
// Combine the GaxiosOptions options passed with this specific
// API call witht the global options configured at the API Context
// level, or at the global level.
const mergedOptions = Object.assign(
Expand Down
24 changes: 22 additions & 2 deletions test/test.apirequest.ts
Expand Up @@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import * as assert from 'assert';
import {assert} from 'chai';
import * as nock from 'nock';
import {createAPIRequest} from '../src/apirequest';

Expand Down Expand Up @@ -49,6 +49,26 @@ it('should create a valid API request', async () => {
context: fakeContext
});
scope.done();
assert.strictEqual(result.data, fakeResponse);
assert.strictEqual(result.data, fakeResponse as {});
assert(result);
});

it('should include directives in the user agent', async () => {
const scope = nock(url).get('/').reply(200);
const res = await createAPIRequest<FakeParams>({
options: {
url,
userAgentDirectives:
[{product: 'frog', version: '1.0', comment: 'jumps'}]
},
params: {},
requiredParams: [],
pathParams: [],
context: fakeContext
});
scope.done();
// frog/1.0 (jumps) google-api-nodejs-client/0.6.0 (gzip)
const userAgent = res.config.headers!['User-Agent'];
assert.match(userAgent, /frog\/1.0 \(jumps\)/);
assert.match(userAgent, /google-api-nodejs-client\/.* \(gzip\)/);
});

0 comments on commit 8ee2bd9

Please sign in to comment.