Skip to content

Commit fefd686

Browse files
committed
Fix to STT Recognize with IAM Key
1 parent 47372be commit fefd686

File tree

3 files changed

+230
-5
lines changed

3 files changed

+230
-5
lines changed

services/speech_to_text/stt-utils.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ class STTUtils {
7474
if (endpoint) {
7575
serviceSettings.url = endpoint;
7676
}
77-
77+
7878
return new STTV1(serviceSettings);
7979
}
8080

services/speech_to_text/v1.js

Lines changed: 106 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@ module.exports = function (RED) {
2222
fs = require('fs'),
2323
WebSocket = require('ws'),
2424
fileType = require('file-type'),
25+
pkg = require('../../package.json'),
2526
serviceutils = require('../../utilities/service-utils'),
2627
payloadutils = require('../../utilities/payload-utils'),
28+
iamutils = require('../../utilities/iam-utils'),
2729
sttutils = require('./stt-utils'),
2830
AuthV1 = require('watson-developer-cloud/authorization/v1'),
2931
AuthIAMV1 = require('watson-developer-cloud/iam-token-manager/v1'),
@@ -268,7 +270,21 @@ module.exports = function (RED) {
268270
}
269271

270272
function getService() {
271-
return Promise.resolve(determineService());
273+
var p = new Promise(function resolver(resolve, reject){
274+
let sttService = determineService();
275+
if (apikey) {
276+
sttService.preAuthenticate((ready) => {
277+
if (!ready) {
278+
reject('Service is not ready');
279+
} else {
280+
resolve(sttService);
281+
}
282+
});
283+
} else {
284+
resolve(sttService);
285+
}
286+
});
287+
return p;
272288
}
273289

274290
function determineTokenService(stt) {
@@ -299,6 +315,95 @@ module.exports = function (RED) {
299315
return tokenService;
300316
}
301317

318+
function cloneQS(original) {
319+
// First create an empty object that will receive copies of properties
320+
let clone = {}, i, keys = Object.keys(original);
321+
322+
for (i = 0; i < keys.length; i++) {
323+
// copy each property into the clone
324+
clone[keys[i]] = original[keys[i]];
325+
}
326+
['audio', 'content_type'].forEach((f) => {
327+
if (clone[f]) {
328+
delete clone[f];
329+
}
330+
});
331+
332+
return clone;
333+
}
334+
335+
function buildRequestSettings(params, t) {
336+
let requestSettings = {
337+
qs : cloneQS(params),
338+
method : 'POST',
339+
uri : endpoint + '/recognize',
340+
headers : {
341+
//Authorization: "Bearer " + t,
342+
'Content-Type': params.content_type,
343+
'User-Agent': pkg.name + '-' + pkg.version,
344+
'Accept': 'application/json',
345+
},
346+
iam_apikey: apikey,
347+
auth: {
348+
'bearer': t
349+
},
350+
body : params.audio
351+
};
352+
353+
return Promise.resolve(requestSettings);
354+
}
355+
356+
function executePostRequest(requestSettings) {
357+
var p = new Promise(function resolver(resolve, reject){
358+
request(requestSettings, (error, response, body) => {
359+
console.log('--------- request has been executed ---------------');
360+
361+
if (!error && response.statusCode == 200) {
362+
data = JSON.parse(body);
363+
resolve(data);
364+
} else if (error) {
365+
reject(error);
366+
} else {
367+
let errordata = JSON.parse(body);
368+
console.log(errordata);
369+
if (errordata.errors &&
370+
Array.isArray(errordata.errors) &&
371+
errordata.errors.length &&
372+
errordata.errors[0].message) {
373+
reject('Error ' + response.statusCode + ' ' + errordata.errors[0].message);
374+
} else {
375+
reject('Error performing request ' + response.statusCode);
376+
}
377+
}
378+
379+
});
380+
});
381+
return p;
382+
}
383+
384+
function iamRecognize(params) {
385+
var p = new Promise(function resolver(resolve, reject){
386+
//console.log('qs params look like ', qs);
387+
// The token may have expired so test for it.
388+
//getToken(speech_to_text)
389+
iamutils.getIAMToken(apikey)
390+
.then((t) => {
391+
//console.log('We should now have a token ', token);
392+
return buildRequestSettings(params, t);
393+
})
394+
.then((requestSettings) => {
395+
//console.log('Request parameters look like ', requestSettings);
396+
return executePostRequest(requestSettings);
397+
})
398+
.then((data) => {
399+
resolve(data);
400+
})
401+
.catch((err) => {
402+
reject(err);
403+
})
404+
});
405+
return p;
406+
}
302407

303408
function performSTT(speech_to_text, audioData) {
304409
var p = new Promise(function resolver(resolve, reject){
@@ -640,9 +745,6 @@ module.exports = function (RED) {
640745
if (config['streaming-mode']) {
641746
return performStreamSTT(sttService, audioData);
642747
} else {
643-
if (apikey) {
644-
node.warn('STT Speech Recognition may not work with API Key!');
645-
}
646748
return performSTT(sttService, audioData);
647749
}
648750
})

utilities/iam-utils.js

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/**
2+
* Copyright 2018 IBM Corp.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
**/
16+
const request = require('request');
17+
18+
// We are only looking at the token expiry, then refreshing that if
19+
// needed. An alternative would be to refresh the token, before expiry
20+
// using the refresh token, and checking if the refresh token has expired,
21+
// but the token time has proven to be sufficient so far. If not will need
22+
// to make the change to add refresh token processing.
23+
24+
class IAMUtils {
25+
26+
constructor() {
27+
}
28+
29+
static stashToken(key, tokenInfo) {
30+
if (! IAMUtils.tokenStash) {
31+
IAMUtils.tokenStash = {};
32+
}
33+
IAMUtils.tokenStash[key] = {};
34+
['access_token', 'refresh_token', 'expires_in', 'expiration'].forEach((f) => {
35+
IAMUtils.tokenStash[key][f] = tokenInfo[f] ? tokenInfo[f] : null;
36+
})
37+
}
38+
39+
static checkForToken(key) {
40+
return IAMUtils.tokenStash &&
41+
IAMUtils.tokenStash[key] &&
42+
IAMUtils.tokenStash[key].access_token;
43+
}
44+
45+
static notExpiredToken(key) {
46+
let fractionOfTtl = 0.8,
47+
timeToLive = IAMUtils.tokenStash[key].expires_in,
48+
expireTime = IAMUtils.tokenStash[key].expiration,
49+
currentTime = Math.floor(Date.now() / 1000),
50+
refreshTime = expireTime - (timeToLive * (1.0 - fractionOfTtl));
51+
52+
return refreshTime >= currentTime;
53+
}
54+
55+
static lookInStash(key){
56+
if (IAMUtils.checkForToken(key) && IAMUtils.notExpiredToken(key)) {
57+
return Promise.resolve(IAMUtils.tokenStash[key].access_token);
58+
}
59+
return Promise.reject();
60+
}
61+
62+
static getNewIAMToken(key) {
63+
var p = new Promise(function resolver(resolve, reject){
64+
let iamtoken = null,
65+
uriAddress = 'https://iam.bluemix.net/identity/token';
66+
67+
request({
68+
uri: uriAddress,
69+
method: 'POST',
70+
headers : {
71+
'Content-Type': 'application/x-www-form-urlencoded',
72+
'Accept': 'application/json'
73+
},
74+
form: {
75+
grant_type: 'urn:ibm:params:oauth:grant-type:apikey',
76+
apikey: key,
77+
response_type: 'cloud_iam'
78+
}
79+
}, (error, response, body) => {
80+
if (!error && response.statusCode == 200) {
81+
//console.log('Token body looks like : ', body);
82+
var b = JSON.parse(body);
83+
//console.log('Token body looks like : ', b);
84+
IAMUtils.stashToken(key, b);
85+
if (b.access_token) {
86+
iamtoken = b.access_token;
87+
}
88+
resolve(iamtoken);
89+
} else if (error) {
90+
reject(error);
91+
} else {
92+
//console.log(body);
93+
reject('IAM Access Token Error ' + response.statusCode);
94+
}
95+
});
96+
});
97+
return p;
98+
}
99+
100+
static getIAMToken(key) {
101+
var p = new Promise(function resolver(resolve, reject){
102+
IAMUtils.lookInStash(key)
103+
.then((iamtoken) => {
104+
//console.log('Resolving from stash');
105+
resolve(iamtoken);
106+
})
107+
.catch(() => {
108+
//console.log('Resolving from new');
109+
IAMUtils.getNewIAMToken(key)
110+
.then((iamtoken) => {
111+
resolve(iamtoken)
112+
})
113+
.catch((err) => {
114+
reject(err)
115+
})
116+
});
117+
});
118+
return p;
119+
}
120+
121+
}
122+
123+
module.exports = IAMUtils;

0 commit comments

Comments
 (0)