Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [Unreleased]
### Added
- Adds protection against user-given pollingInterval values [#129](https://github.com/Microsoft/BotFramework-DirectLineJS/pull/129)
- Added protection against user-given pollingInterval values [#129](https://github.com/Microsoft/BotFramework-DirectLineJS/pull/129)
- Added custom user agent and header [#148](https://github.com/Microsoft/BotFramework-DirectLineJS/pull/148)

### Fixed
- `errorConversationEnded` no longer thrown when calling `DirectLine#end`, by [@orgads](https://github.com/orgads), in PR [#133](https://github.com/Microsoft/BotFramework-DirectLineJS/pull/133)
Expand Down
51 changes: 48 additions & 3 deletions src/__tests__/directLine.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
import * as DirectLineExport from '../directLine';
import * as DirectLineExport from "../directLine";

test('#setConnectionStatusFallback', () => {
declare var process: {
arch: string;
env: {
VERSION: string;
};
platform: string;
release: string;
version: string;
};

test("#setConnectionStatusFallback", () => {
const { DirectLine } = DirectLineExport;
expect(typeof DirectLine.prototype.setConnectionStatusFallback).toBe('function')
expect(typeof DirectLine.prototype.setConnectionStatusFallback).toBe("function")
const { setConnectionStatusFallback } = DirectLine.prototype;
const testFallback = setConnectionStatusFallback(0, 1);
let idx = 4;
Expand All @@ -17,3 +27,38 @@ test('#setConnectionStatusFallback', () => {
}
expect(testFallback(0)).toBe(1);
});

describe("#commonHeaders", () => {
const botAgent = "DirectLine/3.0 (directlinejs/test-version; custom-bot-agent)";
let botConnection;

beforeEach(() => {
process.env.VERSION = "test-version";
const { DirectLine } = DirectLineExport;
botConnection = new DirectLine({ token: "secret-token", botAgent: "custom-bot-agent" });
});

test('appends browser user agent when in a browser', () => {
// @ts-ignore
expect(botConnection.commonHeaders()).toEqual({
"Authorization": "Bearer secret-token",
"User-Agent": `${botAgent} (${window.navigator.userAgent})`,
"x-ms-bot-agent": botAgent
});
})

test('appends node environment agent when in node', () => {
// @ts-ignore
delete window.navigator
// @ts-ignore
const os = require('os');
const { arch, platform, version } = process;

// @ts-ignore
expect(botConnection.commonHeaders()).toEqual({
"Authorization": "Bearer secret-token",
"User-Agent": `${botAgent} (Node.js,Version=${version}; ${platform} ${os.release()}; ${arch})`,
"x-ms-bot-agent": botAgent
});
})
});
70 changes: 61 additions & 9 deletions src/directLine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,18 @@ import 'rxjs/add/observable/interval';
import 'rxjs/add/observable/of';
import 'rxjs/add/observable/throw';

const DIRECT_LINE_VERSION = 'DirectLine/3.0';

declare var process: {
arch: string;
env: {
VERSION: string;
};
platform: string;
release: string;
version: string;
};

// Direct Line 3.0 types

export interface Conversation {
Expand Down Expand Up @@ -343,7 +355,9 @@ export interface DirectLineOptions {
domain?: string,
webSocket?: boolean,
pollingInterval?: number,
streamUrl?: string
streamUrl?: string,
// Attached to all requests to identify requesting agent.
botAgent?: string
}

const lifetimeRefreshToken = 30 * 60 * 1000;
Expand Down Expand Up @@ -386,6 +400,8 @@ export class DirectLine implements IBotConnection {
private token: string;
private watermark = '';
private streamUrl: string;
private _botAgent = '';
private _userAgent: string;
public referenceGrammarId: string;

private pollingInterval: number = 1000; //ms
Expand Down Expand Up @@ -417,6 +433,8 @@ export class DirectLine implements IBotConnection {
}
}

this._botAgent = this.getBotAgent(options.botAgent);

const interval = Math.min(~~options.pollingInterval, POLLING_INTERVAL_LOWER_BOUND);

if (options.pollingInterval && interval < POLLING_INTERVAL_LOWER_BOUND) {
Expand Down Expand Up @@ -530,7 +548,7 @@ export class DirectLine implements IBotConnection {
timeout,
headers: {
"Accept": "application/json",
"Authorization": `Bearer ${this.token}`
...this.commonHeaders()
}
})
// .do(ajaxResponse => konsole.log("conversation ajaxResponse", ajaxResponse.response))
Expand Down Expand Up @@ -564,7 +582,7 @@ export class DirectLine implements IBotConnection {
url: `${this.domain}/tokens/refresh`,
timeout,
headers: {
"Authorization": `Bearer ${this.token}`
...this.commonHeaders()
}
})
.map(ajaxResponse => ajaxResponse.response.token as string)
Expand Down Expand Up @@ -619,7 +637,7 @@ export class DirectLine implements IBotConnection {
timeout,
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${this.token}`
...this.commonHeaders()
}
})
.map(ajaxResponse => {
Expand Down Expand Up @@ -656,8 +674,8 @@ export class DirectLine implements IBotConnection {
timeout,
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${this.token}`
}
...this.commonHeaders()
},
})
.map(ajaxResponse => ajaxResponse.response.id as string)
.catch(error => this.catchPostError(error))
Expand Down Expand Up @@ -697,7 +715,7 @@ export class DirectLine implements IBotConnection {
body: formData,
timeout,
headers: {
"Authorization": `Bearer ${this.token}`
...this.commonHeaders()
}
})
.map(ajaxResponse => ajaxResponse.response.id as string)
Expand Down Expand Up @@ -735,7 +753,7 @@ export class DirectLine implements IBotConnection {
Observable.ajax({
headers: {
Accept: 'application/json',
Authorization: `Bearer ${ this.token }`
...this.commonHeaders()
},
method: 'GET',
url: `${ this.domain }/conversations/${ this.conversationId }/activities?watermark=${ this.watermark }`,
Expand Down Expand Up @@ -843,7 +861,7 @@ export class DirectLine implements IBotConnection {
timeout,
headers: {
"Accept": "application/json",
"Authorization": `Bearer ${this.token}`
...this.commonHeaders()
}
})
.do(result => {
Expand All @@ -869,4 +887,38 @@ export class DirectLine implements IBotConnection {
)
)
}

private commonHeaders() {
if (!this._userAgent) {
try {
this._userAgent = window.navigator.userAgent || '';
} catch {
try {
// set node user agent
// @ts-ignore
const os = require('os');
const { arch, platform, version } = process;
this._userAgent = `Node.js,Version=${version}; ${platform} ${os.release()}; ${arch}`
} catch {
// no-op
}
}
}

return {
Comment thread
a-b-r-o-w-n marked this conversation as resolved.
"Authorization": `Bearer ${this.token}`,
"User-Agent": `${this._botAgent} (${this._userAgent})`,
"x-ms-bot-agent": this._botAgent
};
}

private getBotAgent(customAgent: string = ''): string {
let clientAgent = `directlinejs/${process.env.VERSION || '0.0.0'}`

if (customAgent) {
clientAgent += `; ${customAgent}`
}

return `${DIRECT_LINE_VERSION} (${clientAgent})`;
}
}
10 changes: 9 additions & 1 deletion webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,18 @@ module.exports = {
]
},

plugins: [
new webpack.DefinePlugin({
'process.env': {
'VERSION': JSON.stringify(process.env.npm_package_version)
}
})
],

// When importing a module whose path matches one of the following, just
// assume a corresponding global variable exists and use that instead.
// This is important because it allows us to avoid bundling all of our
// dependencies, which allows browsers to cache those libraries between builds.
externals: {
},
};
};
5 changes: 3 additions & 2 deletions webpack.production.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ module.exports = {
plugins: [
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': JSON.stringify('production')
'NODE_ENV': JSON.stringify('production'),
'VERSION': JSON.stringify(process.env.npm_package_version)
}
}),
new webpack.optimize.UglifyJsPlugin({
Expand Down Expand Up @@ -42,4 +43,4 @@ module.exports = {
// dependencies, which allows browsers to cache those libraries between builds.
externals: {
},
};
};