Skip to content

Commit fcaaed8

Browse files
committed
feat: Add more info to user-agent string
1 parent 40cdb91 commit fcaaed8

File tree

9 files changed

+246
-2
lines changed

9 files changed

+246
-2
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77

88
## [Unreleased]
9+
### Added
10+
* Added platform and node version information to the user-agent string that is sent with API calls, along with an opt-out.
11+
* Added method for applications that use this library to identify themselves in API requests they make.
912
### Fixed
1013
* Fixed proxy example code in README
1114

README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,19 @@ for (let i = 0; i < glossaryLanguages.length; i++) {
367367
}
368368
```
369369

370+
### Writing a Plugin
371+
372+
If you use this library in an application, please identify the application with
373+
the `appInfo` field in the `TranslatorOptions`, which takes the name and version of the app:
374+
375+
```javascript
376+
const options = {appInfo: { appName: 'sampleNodeTranslationApp', appVersion: '1.2.3' },};
377+
const deepl = new deepl.Translator('YOUR_AUTH_KEY', options);
378+
```
379+
380+
This information is passed along when the library makes calls to the DeepL API.
381+
Both name and version are required.
382+
370383
### Configuration
371384

372385
The `Translator` constructor accepts configuration options as a second argument,
@@ -422,6 +435,20 @@ const deepl = new deepl.Translator('YOUR_AUTH_KEY', options);
422435
The proxy argument is passed to the underlying `axios` request, see the
423436
[documentation for axios][axios-proxy-docs].
424437

438+
#### Anonymous platform information
439+
440+
By default, we send some basic information about the platform the client library
441+
is running on with each request, see [here for an explanation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent).
442+
This data is completely anonymous and only used to improve our product, not track
443+
any individual users. If you do not wish to send this data, you can opt-out when
444+
creating your `Translator` object by setting the `sendPlatformInfo` flag in
445+
the `TranslatorOptions` to `false` like so:
446+
447+
```javascript
448+
const options = {sendPlatformInfo: false};
449+
const deepl = new deepl.Translator('YOUR_AUTH_KEY', options);
450+
```
451+
425452
### Request retries
426453

427454
Requests to the DeepL API that fail due to transient conditions (for example,

package-lock.json

Lines changed: 55 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"eslint-plugin-promise": "^6.0.0",
3838
"jest": "^27.4.3",
3939
"jest-junit": "^13.0.0",
40+
"nock": "^13.3.0",
4041
"prettier": "^2.5.1",
4142
"ts-jest": "^27.1.1",
4243
"typescript": "^4.5.3",

src/index.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
parseUsage,
2525
} from './parsing';
2626
import {
27+
AppInfo,
2728
DocumentTranslateOptions,
2829
Formality,
2930
GlossaryId,
@@ -44,6 +45,7 @@ import { isString, logInfo, streamToBuffer, streamToString, timeout, toBoolStrin
4445
import * as fs from 'fs';
4546
import { IncomingMessage, STATUS_CODES } from 'http';
4647
import path from 'path';
48+
import * as os from 'os';
4749
import { URLSearchParams } from 'url';
4850
import * as util from 'util';
4951

@@ -469,7 +471,10 @@ export class Translator {
469471
}
470472
const headers = {
471473
Authorization: `DeepL-Auth-Key ${authKey}`,
472-
'User-Agent': 'deepl-node/1.8.0',
474+
'User-Agent': this.constructUserAgentString(
475+
options?.sendPlatformInfo === false ? false : true,
476+
options?.appInfo,
477+
),
473478
...(options?.headers ?? {}),
474479
};
475480

@@ -981,6 +986,23 @@ export class Translator {
981986
return parseGlossaryInfo(content);
982987
}
983988

989+
private constructUserAgentString(
990+
sendPlatformInfo: boolean,
991+
appInfo: AppInfo | undefined,
992+
): string {
993+
let libraryInfoString = 'deepl-node/1.8.0';
994+
if (sendPlatformInfo) {
995+
const systemType = os.type();
996+
const systemVersion = os.version();
997+
const nodeVersion = process.version.substring(1); // Drop the v in the version number
998+
libraryInfoString += ` (${systemType} ${systemVersion}) node/${nodeVersion}`;
999+
}
1000+
if (appInfo) {
1001+
libraryInfoString += ` ${appInfo.appName}/${appInfo.appVersion}`;
1002+
}
1003+
return libraryInfoString;
1004+
}
1005+
9841006
/**
9851007
* HttpClient implements all HTTP requests and retries.
9861008
* @private

src/types.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,16 @@ export interface ProxyConfig {
1616
protocol?: string;
1717
}
1818

19+
/**
20+
* Optional identifier for the app that is using this library, may be specified
21+
* as appInfo in TranslatorOptions.
22+
* @see TranslatorOptions.appInfo
23+
*/
24+
export interface AppInfo {
25+
appName: string;
26+
appVersion: string;
27+
}
28+
1929
/**
2030
* Options that can be specified when constructing a Translator.
2131
*/
@@ -49,6 +59,18 @@ export interface TranslatorOptions {
4959
* Define the host, port and protocol of the proxy server.
5060
*/
5161
proxy?: ProxyConfig;
62+
63+
/**
64+
* Define if the library is allowed to send more detailed information about which platform
65+
* it is running on with each API call. Defaults to true if undefined. Overriden by
66+
* the `customUserAgent` option.
67+
*/
68+
sendPlatformInfo?: boolean;
69+
70+
/**
71+
* Identifies the application using this client library, will be sent in the `User-Agent` header
72+
*/
73+
appInfo?: AppInfo;
5274
}
5375

5476
export type Formality = 'less' | 'more' | 'default' | 'prefer_less' | 'prefer_more';

src/utils.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ export function isString(arg: any): arg is string {
6161
* Returns '1' if the given arg is truthy, '0' otherwise.
6262
* @param arg Argument to check.
6363
*/
64+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
6465
export function toBoolString(arg: any): string {
6566
return arg ? '1' : '0';
6667
}

tests/core.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ export interface TestTranslatorOptions {
9090
maxRetries?: number;
9191
minTimeout?: number;
9292
proxy?: deepl.ProxyConfig;
93+
sendPlatformInfo?: boolean;
94+
appInfo?: deepl.AppInfo;
9395

9496
mockServerNoResponseTimes?: number;
9597
mockServer429ResponseTimes?: number;
@@ -108,7 +110,7 @@ export interface TestTranslatorOptions {
108110
* session settings.
109111
* @param options Options controlling Translator behaviour and mock-server sessions settings.
110112
*/
111-
export function makeTranslator(options?: TestTranslatorOptions) {
113+
export function makeTranslator(options?: TestTranslatorOptions): deepl.Translator {
112114
if (!usingMockServer && process.env.DEEPL_AUTH_KEY === undefined) {
113115
throw Error('DEEPL_AUTH_KEY environment variable must be defined unless using mock-server');
114116
}
@@ -168,6 +170,8 @@ export function makeTranslator(options?: TestTranslatorOptions) {
168170
minTimeout: options?.minTimeout,
169171
maxRetries: options?.maxRetries,
170172
proxy: options?.proxy,
173+
sendPlatformInfo: options?.sendPlatformInfo,
174+
appInfo: options?.appInfo,
171175
});
172176
}
173177

0 commit comments

Comments
 (0)