Skip to content

Commit

Permalink
refactor: change all websocket method parameters to lower camel case (#…
Browse files Browse the repository at this point in the history
…941)

* add types for WebSocket Stream class constructor options
* warn user when a parameter is passed in that matches the service option but not the user option

BREAKING CHANGE: All parameters have been converted to their lower camel case version.
BREAKING CHANGE: Support for the `token` parameter has been removed
BREAKING CHANGE: Support for the `customization_id` parameter has been removed
BREAKING CHANGE: Method `setAuthorizationHeaderToken` has been removed from the WebSocket Stream classes
  • Loading branch information
dpopp07 committed Oct 4, 2019
1 parent a276df4 commit cb6711f
Show file tree
Hide file tree
Showing 10 changed files with 506 additions and 265 deletions.
9 changes: 9 additions & 0 deletions UPGRADE-5.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,12 @@ _Note: If migrating from a version less than 4.0, also see the [v4 migration gui
## Breaking changes
### Support for Node v6 and v8 dropped
The SDK no longer supports Node versions 6 and 8, as reflected in the `engines` property in the package.json file. Version 6 reached end of life in April 2019 and Version 8 reaches end of life on 31 December 2019.

### WebSocket Methods
- All parameters are now lower camel case
- Support for the `token` parameter has been removed
- Support for the `customization_id` parameter has been removed
- Method `setAuthorizationHeaderToken` has been removed from the WebSocket Stream classes. It now exists as a shared function called `setAuthorizationHeader` in `lib/websocket-utils.ts`.

#### RecognizeStream
- `RecognizeStream.readableObjectMode` will always be a Boolean value - before, it could have been `undefined`. This is to align with the new convention in Node 12.
294 changes: 126 additions & 168 deletions lib/recognize-stream.ts

Large diffs are not rendered by default.

132 changes: 66 additions & 66 deletions lib/synthesize-stream.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* (C) Copyright IBM Corp. 2014, 2019.
* (C) Copyright IBM Corp. 2018, 2019.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -14,28 +14,38 @@
* limitations under the License
*/

import extend = require('extend');
import { OutgoingHttpHeaders } from 'http';
import { qs } from 'ibm-cloud-sdk-core';
import pick = require('object.pick');
import { Readable } from 'stream';
import websocket = require ('websocket');

const w3cWebSocket = websocket.w3cwebsocket;

const PAYLOAD_PARAMS_ALLOWED = [
'text',
'accept',
'timings'
];

const QUERY_PARAMS_ALLOWED = [
'watson-token',
'voice',
'customization_id',
'x-watson-learning-opt-out',
'x-watson-metadata',
'access_token'
];
import { Readable, ReadableOptions } from 'stream';
import { w3cwebsocket as w3cWebSocket } from 'websocket';
import { processUserParameters, setAuthorizationHeader } from './websocket-utils';

// these options represent the superset of the base params,
// query params, and opening message params, with the keys
// in lowerCamelCase format so we can expose a consistent style
// to the user. this object should be updated any time either
// payloadParamsAllowed or queryParamsAllowed is changed
interface Options extends ReadableOptions {
/* base options */
url?: string;
headers?: OutgoingHttpHeaders;
tokenManager?: any;
rejectUnauthorized?: boolean;

/* payload options */
text: string;
accept: string;
timings?: string[];

/* query params */
accessToken?: string;
watsonToken?: string;
voice?: string;
customizationId?: string;
xWatsonLearningOptOut?: boolean;
xWatsonMetadata?: string;
}

interface SynthesizeStream extends Readable {
_readableState;
Expand All @@ -55,7 +65,7 @@ class SynthesizeStream extends Readable {
static WEBSOCKET_ERROR: string = 'WebSocket error';
static WEBSOCKET_CONNECTION_ERROR: string = 'WebSocket connection error';

private options;
private options: Options;
private socket;
private initialized: boolean;

Expand All @@ -68,25 +78,23 @@ class SynthesizeStream extends Readable {
*
* Note that the WebSocket connection is not established until the first chunk of data is recieved. This allows for IAM token request management by the SDK.
*
* @param {Object} options
* @param {String} options.text - The text that us to be synthesized. Provide plain text or text that is annotated with SSML. SSML input can include the SSML <mark> element. Pass a maximum of 5 KB of text.
* @param {String} options.accept - The requested audio format (MIME type) of the audio.
* @param {String[]} [options.timings] - An array that specifies whether the service is to return word timing information for all strings of the input text
* @param {String} [options.voice='en-US_MichaelVoice'] - The voice that is to be used for the synthesis.
* @param {String} [options.customization_id] - The customization ID (GUID) of a custom voice model that is to be used for the synthesis.
* @param {String} [options.url='wss://stream.watsonplatform.net/speech-to-text/api'] base URL for service
* @param {String} [options.watson-token] - Auth token
* @param {String} [options.access_token] - IAM auth token
* @param {Object} [options.headers] - Only works in Node.js, not in browsers. Allows for custom headers to be set, including an Authorization header (preventing the need for auth tokens)
* @param {Boolean} [options.x-watson-learning-opt-out=false] - set to true to opt-out of allowing Watson to use this request to improve it's services
* @param {String} [options.x-watson-metadata] - Associates a customer ID with data that is passed over the connection.
* @param {IamTokenManagerV1} [options.token_manager] - Token manager for authenticating with IAM
* @param {Boolean} [options.rejectUnauthorized] - If true, disable SSL verification for the WebSocket connection
* @param {String} [options.agent] - custom http(s) agent, useful for using the sdk behind a proxy (Node only)
*
* @param {Options} options
* @param {string} [options.url] - Base url for service (default='wss://stream.watsonplatform.net/speech-to-text/api')
* @param {OutgoingHttpHeaders} [options.headers] - Only works in Node.js, not in browsers. Allows for custom headers to be set, including an Authorization header (preventing the need for auth tokens)
* @param {any} [options.tokenManager] - Token manager for authenticating with IAM
* @param {boolean} [options.rejectUnauthorized] - If false, disable SSL verification for the WebSocket connection (default=true)
* @param {string} options.text - The text that us to be synthesized
* @param {string} options.accept - The requested format (MIME type) of the audio
* @param {string[]} [options.timings] - An array that specifies whether the service is to return word timing information for all strings of the input text
* @param {string} [options.accessToken] - Bearer token to put in query string
* @param {string} [options.watsonToken] - Valid Watson authentication token (for Cloud Foundry)
* @param {string} [options.voice] - The voice to use for the synthesis (default='en-US_MichaelVoice')
* @param {string} [options.customizationId] - The customization ID (GUID) of a custom voice model that is to be used for the synthesis
* @param {boolean} [options.xWatsonLearningOptOut] - Indicates whether IBM can use data that is sent over the connection to improve the service for future users (default=false)
* @param {string} [options.xWatsonMetadata] - Associates a customer ID with all data that is passed over the connection. The parameter accepts the argument customer_id={id}, where {id} is a random or generic string that is to be associated with the data
* @constructor
*/
constructor(options) {
constructor(options: Options) {
super(options);
this.options = options;
this.initialized = false;
Expand All @@ -95,9 +103,19 @@ class SynthesizeStream extends Readable {
initialize() {
const options = this.options;

const queryParams = pick(options, QUERY_PARAMS_ALLOWED);
// process query params
const queryParamsAllowed = [
'access_token',
'watson-token',
'voice',
'customization_id',
'x-watson-learning-opt-out',
'x-watson-metadata',
];
const queryParams = processUserParameters(options, queryParamsAllowed);
const queryString = qs.stringify(queryParams);

// synthesize the url
const url =
(options.url || 'wss://stream.watsonplatform.net/text-to-speech/api')
.replace(/^http/, 'ws') +
Expand All @@ -122,7 +140,13 @@ class SynthesizeStream extends Readable {
const self = this;

socket.onopen = () => {
const payload = pick(options, PAYLOAD_PARAMS_ALLOWED);
// process the payload params
const payloadParamsAllowed = [
'text',
'accept',
'timings',
];
const payload = processUserParameters(options, payloadParamsAllowed);
socket.send(JSON.stringify(payload));
/**
* emitted once the WebSocket connection has been established
Expand Down Expand Up @@ -202,7 +226,7 @@ class SynthesizeStream extends Readable {
// even though we aren't controlling the read from websocket,
// we can take advantage of the fact that _read is async and hack
// this funtion to retrieve a token if the service is using IAM auth
this.setAuthorizationHeaderToken(err => {
setAuthorizationHeader(this.options, err => {
if (err) {
this.emit('error', err);
this.push(null);
Expand All @@ -214,30 +238,6 @@ class SynthesizeStream extends Readable {
}
});
}

/**
* This function retrieves an IAM access token and stores it in the
* request header before calling the callback function, which will
* execute the next iteration of `_read()`
*
*
* @private
* @param {Function} callback
*/
setAuthorizationHeaderToken(callback) {
if (this.options.token_manager) {
this.options.token_manager.getToken((err, token) => {
if (err) {
callback(err);
}
const authHeader = { authorization: 'Bearer ' + token };
this.options.headers = extend(this.options.headers, authHeader);
callback(null);
});
} else {
callback(null);
}
}
}

export = SynthesizeStream;
69 changes: 69 additions & 0 deletions lib/websocket-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/**
* (C) Copyright IBM Corp. 2019.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/

import camelcase = require('camelcase');
import extend = require('extend');

/**
* To adhere to our Node style guideline, we expose lowerCamelCase parameters to the user. However, the
* service expects different case conventions so we have to serialize the user-provided params. We do this
* by passing in the user params with the allowed params, looking for the camelcase version of each allowed
* param, and creating an object with the correct keys.
*
* @param {object} options - the user-provided options, with lower camel case parameters
* @param {string[]} allowedParams - array of the parameter names that the service allows
* @returns {object}
*/
export function processUserParameters(options: any, allowedParams: string[]): any {
const processedOptions = {};

// look for the camelcase version of each parameter - that is what we expose to the user
allowedParams.forEach(param => {
const keyName = camelcase(param);
if (options[keyName] !== undefined) {
processedOptions[param] = options[keyName];
} else if (options[param] !== undefined) {
// if the user used the service property name, warn them and give them the name to use
console.warn(`Unrecognized parameter: "${param}". Did you mean "${keyName}"?`);
}
});

return processedOptions;
}

/**
* This function retrieves an access token and stores it in the
* request header before calling the callback function.
*
* @param {object} options - the internal options of a websocket stream class
* @param {Function} callback
*/
export function setAuthorizationHeader(options: any, callback: Function): void {
// assuming the token manger would fall under property 'tokenManager'
// this will likely change with the new authenticators anyways
if (options.tokenManager) {
options.tokenManager.getToken((err, token) => {
if (err) {
return callback(err);
}
const authHeader = { authorization: 'Bearer ' + token };
options.headers = extend(options.headers, authHeader);
callback(null);
});
} else {
callback(null);
}
}
11 changes: 8 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
"@types/node": "^11.9.4",
"async": "^2.6.2",
"axios": "^0.18.0",
"camelcase": "^5.3.1",
"dotenv": "^6.2.0",
"extend": "~3.0.2",
"ibm-cloud-sdk-core": "^0.3.5",
Expand Down
2 changes: 1 addition & 1 deletion speech-to-text/v1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ class SpeechToTextV1 extends GeneratedSpeechToTextV1 {

// if using iam, pass the token manager to the RecognizeStream object
if (this.tokenManager) {
params.token_manager = this.tokenManager;
params.tokenManager = this.tokenManager;
}

// if the user configured a custom https client, use it in the websocket method
Expand Down
Loading

0 comments on commit cb6711f

Please sign in to comment.