From 48dd64c625f05169954ff863b69277bcf1132d7e Mon Sep 17 00:00:00 2001 From: AlexanderKanakis Date: Thu, 27 Jan 2022 17:41:06 +0200 Subject: [PATCH 1/9] Added partial responses streaming. --- app.json | 2 +- lib/Dialogflow.ts | 93 ++++++- package-lock.json | 646 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 4 +- 4 files changed, 731 insertions(+), 14 deletions(-) diff --git a/app.json b/app.json index e6a91e4..e70aab8 100644 --- a/app.json +++ b/app.json @@ -19,5 +19,5 @@ "IPostLivechatRoomClosed", "IUIKitLivechatInteractionHandler" ], - "commitHash": "a87bab42cfaa751b3103c711607df9506983d5dc" + "commitHash": "10d0018a73485b5cf35aedc2c10756f3afec9cfd" } \ No newline at end of file diff --git a/lib/Dialogflow.ts b/lib/Dialogflow.ts index d504557..c14686f 100644 --- a/lib/Dialogflow.ts +++ b/lib/Dialogflow.ts @@ -1,4 +1,5 @@ import { IHttp, IHttpRequest, IModify, IPersistence, IRead } from '@rocket.chat/apps-engine/definition/accessors'; +import { ApiEndpoint } from '@rocket.chat/apps-engine/definition/api'; import { ILivechatRoom } from '@rocket.chat/apps-engine/definition/livechat/ILivechatRoom'; import { IRoom } from '@rocket.chat/apps-engine/definition/rooms'; import { createSign } from 'crypto'; @@ -11,6 +12,8 @@ import { createHttpRequest } from './Http'; import { getRoomAssoc, retrieveDataByAssociation } from './Persistence'; import { updateRoomCustomFields } from './Room'; import { getLivechatAgentConfig } from './Settings'; +// tslint:disable-next-line: no-var-requires +const SessionsClient = require('@rocket.chat/apps-engine/node_modules/@google-cloud/dialogflow-cx'); class DialogflowClass { private jwtExpiration: Date; @@ -62,8 +65,8 @@ class DialogflowClass { ); try { - const response = await http.post(serverURL, httpRequestContent); - return await this.parseCXRequest(read, response.data, sessionId); + const response = await this.detectStreamingIntent(read, sessionId, requestType, request, data); + return await this.parseCXRequest(read, response, sessionId); } catch (error) { const errorContent = `${Logs.HTTP_REQUEST_ERROR}: { roomID: ${sessionId} } ${getError(error)}`; console.error(errorContent); @@ -447,15 +450,21 @@ class DialogflowClass { } private getSourceType(text: any, info: any) { - const executionStep = info['Execution Sequence'][2] ? info['Execution Sequence'][2]['Step 3'] : info['Execution Sequence'][1]['Step 2']; + // const executionStep = info['Execution Sequence'][2] ? info['Execution Sequence'][2]['Step 3'] : info['Execution Sequence'][1]['Step 2']; + const executionStep = info.fields['Execution Sequence'].listValue.values[2] ? + info.fields['Execution Sequence'].listValue.values[2].structValue.fields['Step 3'] : + info.fields['Execution Sequence'].listValue.values[1].structValue.fields['Step 2']; if (executionStep) { - const intentResponses = executionStep.FunctionExecution ? executionStep.FunctionExecution.Responses : null; + const intentResponses = executionStep.structValue.fields.FunctionExecution ? + executionStep.structValue.fields.FunctionExecution.structValue.fields.Responses : null; if (intentResponses) { - for (const response of intentResponses) { - if (response.text && response.text.text[0] === text.text[0] && response.responseType === 'HANDLER_PROMPT') { + for (const response of intentResponses.listValue.values) { + if (response.structValue.fields.text && + response.structValue.fields.text.structValue.fields.text.listValue.values[0].stringValue === text.text[0] && + response.structValue.fields.responseType === 'HANDLER_PROMPT') { return 'intent'; } } @@ -464,6 +473,78 @@ class DialogflowClass { return 'page'; } + + private async detectStreamingIntent( + read: IRead, + sessionId: string, + requestType: DialogflowRequestType, + request: IDialogflowEvent | string, + data: any) { + const projectId = await getLivechatAgentConfig(read, sessionId, AppSetting.DialogflowAgentProjectId); + const environmentId = await getLivechatAgentConfig(read, sessionId, AppSetting.DialogflowAgentEnvironmentId); + const defaultLanguageCode = await getLivechatAgentConfig(read, sessionId, AppSetting.DialogflowAgentDefaultLanguage); + const regionId = await getLivechatAgentConfig(read, sessionId, AppSetting.DialogflowAgentRegion); + const agentId = await getLivechatAgentConfig(read, sessionId, AppSetting.DialogflowAgentId); + const privateKey = await getLivechatAgentConfig(read, sessionId, AppSetting.DialogflowAgentPrivateKey); + const clientEmail = await getLivechatAgentConfig(read, sessionId, AppSetting.DialogflowAgentClientEmail); + const room = await read.getRoomReader().getById(sessionId) as ILivechatRoom; + const { id: rid, visitor: { livechatData, token: visitorToken } } = room; + + const client = new SessionsClient({ + apiEndpoint: `${regionId}-dialogflow.googleapis.com`, + credentials: { private_key: privateKey, client_email: clientEmail }, + }); + const sessionPath = `projects/${projectId}/locations/${regionId}/agents/${agentId}/environments/${environmentId || 'draft'}/sessions/${sessionId}`; + const queryInput = { + ...requestType === DialogflowRequestType.EVENT && { event: { event: typeof request === 'string' ? request : request.name} }, + ...requestType === DialogflowRequestType.MESSAGE && { text: { text: request }}, + languageCode: data.custom_languageCode || defaultLanguageCode || LanguageCode.EN, + }; + + const queryParams = { + timeZone: 'America/Los_Angeles', + parameters: { + username: room.visitor.username, + roomId: rid, + visitorToken, + ...(livechatData || {}), + }, + }; + + return new Promise((resolve, reject) => { + try { + const detectStream = client + .streamingDetectIntent() + .on('error', console.error) + .on('data', async (streamData: any) => { + if (streamData.recognitionResult) { + console.log( + `Intermediate Transcript: ${streamData.recognitionResult.transcript}`, + ); + } else { + const response = streamData.detectIntentResponse; + if (response) { + try { + resolve(response); + client.streamingDetectIntent().end(); + } catch (e) { + console.error(e); + } + } + } + }); + const initialStreamRequest = { + session: sessionPath, + queryInput, + enable_partial_response: true, + queryParams, + }; + detectStream.write(initialStreamRequest); + } catch (e) { + reject(e); + } + }); + } } export const Dialogflow = new DialogflowClass(); diff --git a/package-lock.json b/package-lock.json index a9156cf..68f22c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,11 +28,116 @@ "js-tokens": "^4.0.0" } }, + "@google-cloud/dialogflow-cx": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/@google-cloud/dialogflow-cx/-/dialogflow-cx-2.15.0.tgz", + "integrity": "sha512-uZoX2AdpU9SuysNzvvSzk7iV2HhNxttvfTYeDkRgMMJSv/xf0tyiujcKybK/b628RVVNsAQhEFDR0NDykFs/4w==", + "dev": true, + "requires": { + "google-gax": "^2.24.1" + } + }, + "@grpc/grpc-js": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.5.3.tgz", + "integrity": "sha512-q0xgaZ3ymUM+ZOhe1hdocVSdKHCnJ6llLSXcP+MqMXMyYPUZ3mzQOCxZ3Zkg+QZ7sZ950sn7hvueQrIJZumPZg==", + "dev": true, + "requires": { + "@grpc/proto-loader": "^0.6.4", + "@types/node": ">=12.12.47" + }, + "dependencies": { + "@types/node": { + "version": "17.0.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.12.tgz", + "integrity": "sha512-4YpbAsnJXWYK/fpTVFlMIcUIho2AYCi4wg5aNPrG1ng7fn/1/RZfCIpRCiBX+12RVa34RluilnvCqD+g3KiSiA==", + "dev": true + } + } + }, + "@grpc/proto-loader": { + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.9.tgz", + "integrity": "sha512-UlcCS8VbsU9d3XTXGiEVFonN7hXk+oMXZtoHHG2oSA1/GcDP1q6OUgs20PzHDGizzyi8ufGSUDlk3O2NyY7leg==", + "dev": true, + "requires": { + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^6.10.0", + "yargs": "^16.2.0" + } + }, + "@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=", + "dev": true + }, + "@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "dev": true + }, + "@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "dev": true + }, + "@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=", + "dev": true + }, + "@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", + "dev": true, + "requires": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=", + "dev": true + }, + "@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=", + "dev": true + }, + "@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=", + "dev": true + }, + "@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=", + "dev": true + }, + "@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=", + "dev": true + }, "@rocket.chat/apps-engine": { - "version": "git+https://github.com/WideChat/Rocket.Chat.Apps-engine.git#7777d4b7702500a3c2ff28aaae9fa8963dba109f", - "from": "git+https://github.com/WideChat/Rocket.Chat.Apps-engine.git#master", + "version": "git+https://github.com/WideChat/Rocket.Chat.Apps-engine.git#35877292b61dea5345685fe2879906b4f1df2977", + "from": "git+https://github.com/WideChat/Rocket.Chat.Apps-engine.git#dialogflow_branch", "dev": true, "requires": { + "@google-cloud/dialogflow-cx": "^2.15.0", "adm-zip": "^0.4.9", "cryptiles": "^4.1.3", "lodash.clonedeep": "^4.5.0", @@ -41,18 +146,48 @@ "uuid": "^3.2.1" } }, + "@types/long": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", + "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==", + "dev": true + }, "@types/node": { "version": "10.17.5", "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.5.tgz", "integrity": "sha512-RElZIr/7JreF1eY6oD5RF3kpmdcreuQPjg5ri4oQ5g9sq7YWU8HkfB3eH8GwAwxf5OaCh0VPi7r4N/yoTGelrA==", "dev": true }, + "abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dev": true, + "requires": { + "event-target-shim": "^5.0.0" + } + }, "adm-zip": { "version": "0.4.16", "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.16.tgz", "integrity": "sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg==", "dev": true }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "requires": { + "debug": "4" + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -71,12 +206,30 @@ "sprintf-js": "~1.0.2" } }, + "arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "dev": true + }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true + }, + "bignumber.js": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.2.tgz", + "integrity": "sha512-GAcQvbpsM0pUb0zw1EI0KhQEZ+lRwR5fYaAp3vPOYuP7aDvGy6cVN6XHLauvF8SOga2y0dcLcjt3iQDTSEliyw==", + "dev": true + }, "boom": { "version": "7.3.0", "resolved": "https://registry.npmjs.org/boom/-/boom-7.3.0.tgz", @@ -96,6 +249,12 @@ "concat-map": "0.0.1" } }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=", + "dev": true + }, "builtin-modules": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", @@ -113,6 +272,17 @@ "supports-color": "^5.3.0" } }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -149,12 +319,63 @@ "boom": "7.x.x" } }, + "debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, "diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true }, + "duplexify": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz", + "integrity": "sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==", + "dev": true, + "requires": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.0" + } + }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -167,6 +388,24 @@ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, + "event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "dev": true + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "fast-text-encoding": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz", + "integrity": "sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig==", + "dev": true + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -179,6 +418,35 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, + "gaxios": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.3.2.tgz", + "integrity": "sha512-T+ap6GM6UZ0c4E6yb1y/hy2UB6hTrqhglp3XfmU9qbLCGRYhLVV5aRPpC4EmoG8N8zOnkYCgoBz+ScvGAARY6Q==", + "dev": true, + "requires": { + "abort-controller": "^3.0.0", + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.1" + } + }, + "gcp-metadata": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.3.1.tgz", + "integrity": "sha512-x850LS5N7V1F3UcV7PoupzGsyD6iVwTVvsh3tbXfkctZnBnjW5yu5z1/3k3SehF7TyoTIe78rJs02GMMy+LF+A==", + "dev": true, + "requires": { + "gaxios": "^4.0.0", + "json-bigint": "^1.0.0" + } + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, "glob": { "version": "7.1.7", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", @@ -193,6 +461,64 @@ "path-is-absolute": "^1.0.0" } }, + "google-auth-library": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.11.0.tgz", + "integrity": "sha512-3S5jn2quRumvh9F/Ubf7GFrIq71HZ5a6vqosgdIu105kkk0WtSqc2jGCRqtWWOLRS8SX3AHACMOEDxhyWAQIcg==", + "dev": true, + "requires": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^4.0.0", + "gcp-metadata": "^4.2.0", + "gtoken": "^5.0.4", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + } + }, + "google-gax": { + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-2.29.4.tgz", + "integrity": "sha512-3o6cByD2fE1yIc6i1gpKMQlJStqlvu8Sa/Ly/HCQ6GPHpltpVfkTT4KVj2YLVa7WTSDoGbsLBDmJ1KfN1uNiRw==", + "dev": true, + "requires": { + "@grpc/grpc-js": "~1.5.0", + "@grpc/proto-loader": "^0.6.1", + "@types/long": "^4.0.0", + "abort-controller": "^3.0.0", + "duplexify": "^4.0.0", + "fast-text-encoding": "^1.0.3", + "google-auth-library": "^7.6.1", + "is-stream-ended": "^0.1.4", + "node-fetch": "^2.6.1", + "object-hash": "^2.1.1", + "proto3-json-serializer": "^0.1.7", + "protobufjs": "6.11.2", + "retry-request": "^4.0.0" + } + }, + "google-p12-pem": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.1.3.tgz", + "integrity": "sha512-MC0jISvzymxePDVembypNefkAQp+DRP7dBE+zNUPaIjEspIlYg0++OrsNr248V9tPbz6iqtZ7rX1hxWA5B8qBQ==", + "dev": true, + "requires": { + "node-forge": "^1.0.0" + } + }, + "gtoken": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.3.1.tgz", + "integrity": "sha512-yqOREjzLHcbzz1UrQoxhBtpk8KjrVhuqPE7od1K2uhyxG2BHjKZetlbLw/SPZak/QqTIQW+addS+EcjqQsZbwQ==", + "dev": true, + "requires": { + "gaxios": "^4.0.0", + "google-p12-pem": "^3.0.3", + "jws": "^4.0.0" + } + }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -214,6 +540,16 @@ "integrity": "sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ==", "dev": true }, + "https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "dev": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -239,6 +575,24 @@ "has": "^1.0.3" } }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, + "is-stream-ended": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-stream-ended/-/is-stream-ended-0.1.4.tgz", + "integrity": "sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==", + "dev": true + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -255,12 +609,63 @@ "esprima": "^4.0.0" } }, + "json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "dev": true, + "requires": { + "bignumber.js": "^9.0.0" + } + }, + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "dev": true, + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "dev": true, + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", + "dev": true + }, "lodash.clonedeep": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", "dev": true }, + "long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", + "dev": true + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -285,6 +690,33 @@ "minimist": "^1.2.5" } }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dev": true, + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "node-forge": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.2.1.tgz", + "integrity": "sha512-Fcvtbb+zBcZXbTTVwqGA5W+MKBj56UjVRevvchv5XrcyXbmNdesfZL37nlcWOfpgHhgmxApw3tQbTr4CqNmX4w==", + "dev": true + }, + "object-hash": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", + "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", + "dev": true + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -312,6 +744,61 @@ "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==", "dev": true }, + "proto3-json-serializer": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-0.1.8.tgz", + "integrity": "sha512-ACilkB6s1U1gWnl5jtICpnDai4VCxmI9GFxuEaYdxtDG2oVI3sVFIUsvUZcQbJgtPM6p+zqKbjTKQZp6Y4FpQw==", + "dev": true, + "requires": { + "protobufjs": "^6.11.2" + } + }, + "protobufjs": { + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.2.tgz", + "integrity": "sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw==", + "dev": true, + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + }, + "dependencies": { + "@types/node": { + "version": "17.0.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.12.tgz", + "integrity": "sha512-4YpbAsnJXWYK/fpTVFlMIcUIho2AYCi4wg5aNPrG1ng7fn/1/RZfCIpRCiBX+12RVa34RluilnvCqD+g3KiSiA==", + "dev": true + } + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, "resolve": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", @@ -322,6 +809,22 @@ "path-parse": "^1.0.6" } }, + "retry-request": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.2.2.tgz", + "integrity": "sha512-xA93uxUD/rogV7BV59agW/JHPGXeREMWiZc9jhcwY4YdZ7QOtC7qbomYg0n4wyk2lJhggjvKvhNX8wln/Aldhg==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "extend": "^3.0.2" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", @@ -340,6 +843,41 @@ "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=", "dev": true }, + "stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", + "dev": true + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -349,6 +887,12 @@ "has-flag": "^3.0.0" } }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", + "dev": true + }, "tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -386,9 +930,15 @@ } }, "typescript": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.2.tgz", - "integrity": "sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.1.tgz", + "integrity": "sha512-64HkdiRv1yYZsSe4xC1WVgamNigVYjlssIoaH2HcZF0+ijsk5YK2g0G34w9wJkze8+5ow4STd22AynfO6ZYYLw==", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true }, "uuid": { @@ -397,11 +947,97 @@ "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", "dev": true }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=", + "dev": true + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "dev": true, + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + } + } + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true } } } diff --git a/package.json b/package.json index 7d78086..ffb35d8 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "devDependencies": { - "@rocket.chat/apps-engine": "git+https://github.com/WideChat/Rocket.Chat.Apps-engine.git#master", + "@rocket.chat/apps-engine": "git+https://github.com/WideChat/Rocket.Chat.Apps-engine.git#dialogflow_branch", "@types/node": "10.17.5", "prettier": "^2.5.1", "tslint": "^5.20.1", - "typescript": "^2.9.1" + "typescript": "^3.5.1" }, "scripts": { "tslint": "tslint --project ." From d13e5a78e543696447310cc0abb8a4957d69d44f Mon Sep 17 00:00:00 2001 From: AlexanderKanakis Date: Fri, 11 Feb 2022 19:41:24 +0200 Subject: [PATCH 2/9] Fixed apps-engine dependencies --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index cc39b1b..de3873e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -134,7 +134,7 @@ }, "@rocket.chat/apps-engine": { "version": "git+https://github.com/WideChat/Rocket.Chat.Apps-engine.git#35877292b61dea5345685fe2879906b4f1df2977", - "from": "git+https://github.com/WideChat/Rocket.Chat.Apps-engine.git#dialogflow_branch", + "from": "git+https://github.com/WideChat/Rocket.Chat.Apps-engine.git#master", "dev": true, "requires": { "@google-cloud/dialogflow-cx": "^2.15.0", diff --git a/package.json b/package.json index cdbcb6b..75639ab 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "devDependencies": { - "@rocket.chat/apps-engine": "git+https://github.com/WideChat/Rocket.Chat.Apps-engine.git#dialogflow_branch", + "@rocket.chat/apps-engine": "git+https://github.com/WideChat/Rocket.Chat.Apps-engine.git#master", "@types/node": "10.17.5", "prettier": "^2.5.1", "tslint": "^5.20.1", From 4f9df84b3dc0f0e513f53a59dc3ac00a1deed877 Mon Sep 17 00:00:00 2001 From: AlexanderKanakis Date: Fri, 11 Feb 2022 19:42:03 +0200 Subject: [PATCH 3/9] Fixed apps-engine version --- package-lock.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index de3873e..aaa247a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -133,7 +133,7 @@ "dev": true }, "@rocket.chat/apps-engine": { - "version": "git+https://github.com/WideChat/Rocket.Chat.Apps-engine.git#35877292b61dea5345685fe2879906b4f1df2977", + "version": "git+https://github.com/WideChat/Rocket.Chat.Apps-engine.git#3e5da1c2c996a27197f9de7ca2292e1d1545e9d5", "from": "git+https://github.com/WideChat/Rocket.Chat.Apps-engine.git#master", "dev": true, "requires": { From edc93973e24a68be9d016cd38be2179166d8709d Mon Sep 17 00:00:00 2001 From: AlexanderKanakis Date: Tue, 15 Feb 2022 14:52:38 +0200 Subject: [PATCH 4/9] Fixed DF request for partial responses --- DialogflowApp.ts | 7 ++ app.json | 2 +- endpoints/IncomingEndpoint.ts | 14 ++- handler/PostMessageSentHandler.ts | 18 ++-- lib/Dialogflow.ts | 99 ++++++++++--------- lib/EventTimeoutProcessor.ts | 4 +- lib/responseParameters.ts | 2 +- lib/sendEventToDialogFlow.ts | 2 +- lib/sendWelcomeEvent.ts | 2 +- lib/sentEventToDialogFlow.ts | 29 ------ .../SessionMaintenanceProcessor.ts | 2 +- package-lock.json | 50 +++++----- package.json | 2 +- 13 files changed, 121 insertions(+), 112 deletions(-) delete mode 100644 lib/sentEventToDialogFlow.ts diff --git a/DialogflowApp.ts b/DialogflowApp.ts index 7eb3806..11246cb 100644 --- a/DialogflowApp.ts +++ b/DialogflowApp.ts @@ -2,6 +2,7 @@ import { IAppAccessors, IConfigurationExtend, IConfigurationModify, + IEnvironmentRead, IHttp, ILogger, IModify, @@ -19,6 +20,7 @@ import { settings } from './config/Settings'; import { FulfillmentsEndpoint } from './endpoints/FulfillmentsEndpoint'; import { IncomingEndpoint } from './endpoints/IncomingEndpoint'; import { JobName } from './enum/Scheduler'; +import { Global } from './Global'; import { ExecuteLivechatBlockActionHandler } from './handler/ExecuteLivechatBlockActionHandler'; import { LivechatRoomClosedHandler } from './handler/LivechatRoomClosedHandler'; import { OnAgentAssignedHandler } from './handler/OnAgentAssignedHandler'; @@ -33,6 +35,11 @@ export class DialogflowApp extends App implements IPostMessageSent, IPostLivecha super(info, logger, accessors); } + public async onEnable(environment: IEnvironmentRead, configurationModify: IConfigurationModify): Promise { + Global.app = this; + return Promise.resolve(true); + } + public async executeLivechatBlockActionHandler(context: UIKitLivechatBlockInteractionContext, read: IRead, http: IHttp, diff --git a/app.json b/app.json index 3849468..4ef927e 100644 --- a/app.json +++ b/app.json @@ -19,5 +19,5 @@ "IPostLivechatRoomClosed", "IUIKitLivechatInteractionHandler" ], - "commitHash": "f6d978e0fa23574ebf646ac0b1c371b15494127f" + "commitHash": "365b41481e35f48b6e888de3379cde12120999f3" } \ No newline at end of file diff --git a/endpoints/IncomingEndpoint.ts b/endpoints/IncomingEndpoint.ts index d51b7a7..d3365c7 100644 --- a/endpoints/IncomingEndpoint.ts +++ b/endpoints/IncomingEndpoint.ts @@ -1,5 +1,6 @@ import { HttpStatusCode, IHttp, IModify, IPersistence, IRead } from '@rocket.chat/apps-engine/definition/accessors'; import { ApiEndpoint, IApiEndpointInfo, IApiRequest, IApiResponse } from '@rocket.chat/apps-engine/definition/api'; +import { IApp } from '@rocket.chat/apps-engine/definition/IApp'; import { ILivechatRoom } from '@rocket.chat/apps-engine/definition/livechat'; import { DialogflowRequestType, IDialogflowMessage } from '../enum/Dialogflow'; import { EndpointActionNames, IActionsEndpointContent } from '../enum/Endpoints'; @@ -26,7 +27,7 @@ export class IncomingEndpoint extends ApiEndpoint { this.app.getLogger().info(Logs.ENDPOINT_RECEIVED_REQUEST); try { - const { statusCode = HttpStatusCode.OK, data = null } = await this.processRequest(read, modify, persis, http, request.content); + const { statusCode = HttpStatusCode.OK, data = null } = await this.processRequest(this.app, read, modify, persis, http, request.content); return createHttpResponse(statusCode, { 'Content-Type': Headers.CONTENT_TYPE_JSON }, { ...data ? { ...data } : { result: Response.SUCCESS } }); } catch (error) { this.app.getLogger().error(Logs.ENDPOINT_REQUEST_PROCESSING_ERROR, error); @@ -34,7 +35,8 @@ export class IncomingEndpoint extends ApiEndpoint { } } - private async processRequest(read: IRead, + private async processRequest(app: IApp, + read: IRead, modify: IModify, persistence: IPersistence, http: IHttp, @@ -75,7 +77,13 @@ export class IncomingEndpoint extends ApiEndpoint { } try { - const response: IDialogflowMessage = await Dialogflow.sendRequest(http, read, modify, sessionId, event, DialogflowRequestType.EVENT); + const response: IDialogflowMessage = await Dialogflow.sendRequest(http, + read, + modify, + persistence, + sessionId, + event, + DialogflowRequestType.EVENT); // widechat specific change // await createDialogflowMessage(sessionId, read, modify, response, this.app); // await handlePayloadActions(this.app, read, modify, http, persistence, sessionId, vToken, response); diff --git a/handler/PostMessageSentHandler.ts b/handler/PostMessageSentHandler.ts index ebda50a..a87c5ee 100644 --- a/handler/PostMessageSentHandler.ts +++ b/handler/PostMessageSentHandler.ts @@ -15,7 +15,7 @@ import { getIsProcessingMessage, getRoomAssoc, retrieveDataByAssociation, setIsP import { handleParameters } from '../lib/responseParameters'; import { closeChat, performHandover, updateRoomCustomFields } from '../lib/Room'; import { cancelAllEventSchedulerJobForSession, cancelAllSessionMaintenanceJobForSession } from '../lib/Scheduler'; -import { sendEventToDialogFlow } from '../lib/sentEventToDialogFlow'; +import { sendEventToDialogFlow } from '../lib/sendEventToDialogFlow'; import { agentConfigExists, getLivechatAgentConfig } from '../lib/Settings'; import { incFallbackIntentAndSendResponse, resetFallbackIntent } from '../lib/SynchronousHandover'; import { handleTimeout } from '../lib/Timeout'; @@ -118,7 +118,7 @@ export class PostMessageSentHandler { await setIsProcessingMessage(this.persistence, rid, true); await cancelAllEventSchedulerJobForSession(this.modify, rid); await botTypingListener(this.modify, rid, servedBy.username); - response = (await Dialogflow.sendRequest(this.http, this.read, this.modify, rid, messageText, DialogflowRequestType.MESSAGE)); + response = (await Dialogflow.sendRequest(this.http, this.read, this.modify, this.persistence, rid, messageText, DialogflowRequestType.MESSAGE)); await setIsProcessingMessage(this.persistence, rid, false); } catch (error) { await setIsProcessingMessage(this.persistence, rid, false); @@ -190,10 +190,16 @@ export class PostMessageSentHandler { const defaultLanguageCode = await getLivechatAgentConfig(read, rid, AppSetting.DialogflowAgentDefaultLanguage); let res: IDialogflowMessage; - res = (await Dialogflow.sendRequest(this.http, this.read, this.modify, rid, { - name: DialogflowChatClosedByVisitorEventName, - languageCode: data.custom_languageCode || defaultLanguageCode || LanguageCode.EN, - }, DialogflowRequestType.EVENT)); + res = (await Dialogflow.sendRequest(this.http, + this.read, + this.modify, + this.persistence, + rid, + { + name: DialogflowChatClosedByVisitorEventName, + languageCode: data.custom_languageCode || defaultLanguageCode || LanguageCode.EN, + }, + DialogflowRequestType.EVENT)); } catch (error) { const errorContent = `${Logs.DIALOGFLOW_REST_API_ERROR}: { roomID: ${rid} } ${getErrorMessage(error)}`; this.app.getLogger().error(errorContent); diff --git a/lib/Dialogflow.ts b/lib/Dialogflow.ts index 8bb6af3..98a4003 100644 --- a/lib/Dialogflow.ts +++ b/lib/Dialogflow.ts @@ -4,22 +4,27 @@ import { ILivechatRoom } from '@rocket.chat/apps-engine/definition/livechat/ILiv import { IRoom } from '@rocket.chat/apps-engine/definition/rooms'; import { createSign } from 'crypto'; import { AppSetting } from '../config/Settings'; -import { DialogflowJWT, DialogflowRequestType, DialogflowUrl, IDialogflowAccessToken, IDialogflowCustomFields, IDialogflowEvent, IDialogflowMessage, IDialogflowPayload, IDialogflowQuickReplies, LanguageCode } from '../enum/Dialogflow'; +import { DialogflowJWT, DialogflowRequestType, DialogflowUrl, IDialogflowAccessToken, IDialogflowCustomFields, IDialogflowEvent, IDialogflowImageCard, IDialogflowMessage, IDialogflowPayload, IDialogflowQuickReplies, LanguageCode } from '../enum/Dialogflow'; import { Headers } from '../enum/Http'; import { Logs } from '../enum/Logs'; +import { Global } from '../Global'; +import { removeBotTypingListener } from './BotTyping'; import { base64urlEncode, getError } from './Helper'; import { createHttpRequest } from './Http'; +import { createDialogflowMessage } from './Message'; +import { handlePayloadActions, handleResponse, testMessage } from './payloadAction'; import { getRoomAssoc, retrieveDataByAssociation } from './Persistence'; import { updateRoomCustomFields } from './Room'; import { getLivechatAgentConfig } from './Settings'; // tslint:disable-next-line: no-var-requires -const SessionsClient = require('@rocket.chat/apps-engine/node_modules/@google-cloud/dialogflow-cx'); +const DialogflowApi = require('@rocket.chat/apps-engine/node_modules/@google-cloud/dialogflow-cx'); class DialogflowClass { private jwtExpiration: Date; public async sendRequest(http: IHttp, read: IRead, modify: IModify, + persistence: IPersistence, sessionId: string, request: IDialogflowEvent | string, requestType: DialogflowRequestType): Promise { @@ -36,37 +41,12 @@ class DialogflowClass { if (dialogFlowVersion === 'CX') { - const queryInput = { - ...requestType === DialogflowRequestType.EVENT && { event: { event: typeof request === 'string' ? request : request.name} }, - ...requestType === DialogflowRequestType.MESSAGE && { text: { text: request }}, - languageCode: data.custom_languageCode || defaultLanguageCode || LanguageCode.EN, - }; - - const queryParams = { - timeZone: 'America/Los_Angeles', - parameters: { - username: room.visitor.username, - roomId: rid, - visitorToken, - ...(livechatData || {}), - }, - }; - - if (requestType === DialogflowRequestType.EVENT && typeof request !== 'string') { - queryParams.parameters = {...queryParams.parameters, ...(request.parameters ? request.parameters : {})}; - } - const accessToken = await this.getAccessToken(read, modify, http, sessionId); if (!accessToken) { throw Error(Logs.ACCESS_TOKEN_ERROR); } - const httpRequestContent: IHttpRequest = createHttpRequest( - { 'Content-Type': Headers.CONTENT_TYPE_JSON, 'Accept': Headers.ACCEPT_JSON, 'Authorization': 'Bearer ' + accessToken }, - { queryInput, queryParams }, - ); - try { - const response = await this.detectStreamingIntent(read, sessionId, requestType, request, data); - return await this.parseCXRequest(read, response, sessionId); + return await this.detectStreamingIntent(read, modify, http, persistence, sessionId, requestType, request, data); + // return await this.parseCXRequest(read, response, sessionId); } catch (error) { const errorContent = `${Logs.HTTP_REQUEST_ERROR}: { roomID: ${sessionId} } ${getError(error)}`; console.error(errorContent); @@ -480,6 +460,9 @@ class DialogflowClass { private async detectStreamingIntent( read: IRead, + modify: IModify, + http: IHttp, + persistence: IPersistence, sessionId: string, requestType: DialogflowRequestType, request: IDialogflowEvent | string, @@ -494,7 +477,7 @@ class DialogflowClass { const room = await read.getRoomReader().getById(sessionId) as ILivechatRoom; const { id: rid, visitor: { livechatData, token: visitorToken } } = room; - const client = new SessionsClient({ + const client = new DialogflowApi.SessionsClient({ apiEndpoint: `${regionId}-dialogflow.googleapis.com`, credentials: { private_key: privateKey, client_email: clientEmail }, }); @@ -515,32 +498,56 @@ class DialogflowClass { }, }; + if (requestType === DialogflowRequestType.EVENT && typeof request !== 'string') { + queryParams.parameters = {...queryParams.parameters, ...(request.parameters ? request.parameters : {})}; + } + return new Promise((resolve, reject) => { try { const detectStream = client .streamingDetectIntent() .on('error', console.error) .on('data', async (streamData: any) => { - if (streamData.recognitionResult) { - console.log( - `Intermediate Transcript: ${streamData.recognitionResult.transcript}`, - ); - } else { - const response = streamData.detectIntentResponse; - if (response) { - try { - resolve(response); - client.streamingDetectIntent().end(); - } catch (e) { - console.error(e); + if (streamData.recognitionResult) { + console.log( + `Intermediate Transcript: ${streamData.recognitionResult.transcript}`, + ); + } else { + const response = streamData.detectIntentResponse; + if (response) { + if (response.responseType === 'PARTIAL') { + const parsedResponse = await this.parseCXRequest(read, response, sessionId); + const { messages = [], isFallback = false } = parsedResponse; + for (const message of messages) { + const { action = null } = message as IDialogflowPayload; + const textMessages: Array = []; + textMessages.push(message); + const messagesToProcess: IDialogflowMessage = { + messages: textMessages, + isFallback, + }; + await createDialogflowMessage(rid, read, modify, messagesToProcess, Global.app); + } + } else { + try { + detectStream.end(); + resolve(await this.parseCXRequest(read, response, sessionId)); + } catch (e) { + console.error(e); + } + } + } } + }) + .on('end', async (streamData: any) => { + if (room.servedBy) { + await removeBotTypingListener(modify, rid, room.servedBy.username); } - } }); const initialStreamRequest = { session: sessionPath, queryInput, - enable_partial_response: true, + enablePartialResponse: true, queryParams, }; detectStream.write(initialStreamRequest); @@ -549,6 +556,10 @@ class DialogflowClass { } }); } + + private async testfunction() { + return; + } } export const Dialogflow = new DialogflowClass(); diff --git a/lib/EventTimeoutProcessor.ts b/lib/EventTimeoutProcessor.ts index 8939436..66c2df9 100644 --- a/lib/EventTimeoutProcessor.ts +++ b/lib/EventTimeoutProcessor.ts @@ -16,14 +16,14 @@ export class EventScheduler implements IProcessor { this.id = id; } - public async processor(jobContext: IJobContext, read: IRead, modify: IModify, http: IHttp, persis: IPersistence): Promise { + public async processor(jobContext: IJobContext, read: IRead, modify: IModify, http: IHttp, persistence: IPersistence): Promise { const sessionId = jobContext.rid; try { const data = await retrieveDataByAssociation(read, getRoomAssoc(sessionId)); const defaultLanguageCode = await getLivechatAgentConfig(read, sessionId, AppSetting.DialogflowAgentDefaultLanguage); const event = { name: jobContext.eventName, languageCode: data.custom_languageCode || defaultLanguageCode || LanguageCode.EN, parameters: {} }; - const response = await Dialogflow.sendRequest(http, read, modify, sessionId, event, DialogflowRequestType.EVENT); + const response = await Dialogflow.sendRequest(http, read, modify, persistence, sessionId, event, DialogflowRequestType.EVENT); await createDialogflowMessage(sessionId, read, modify, response); } catch (error) { diff --git a/lib/responseParameters.ts b/lib/responseParameters.ts index d584c28..d0016bb 100644 --- a/lib/responseParameters.ts +++ b/lib/responseParameters.ts @@ -34,7 +34,7 @@ const sendChangeLanguageEvent = async (app: IApp, read: IRead, modify: IModify, try { const event = { name: 'ChangeLanguage', languageCode, parameters: {} }; - const response: IDialogflowMessage = await Dialogflow.sendRequest(http, read, modify, rid, event, DialogflowRequestType.EVENT); + const response: IDialogflowMessage = await Dialogflow.sendRequest(http, read, modify, persis, rid, event, DialogflowRequestType.EVENT); await createDialogflowMessage(rid, read, modify, response, app); } catch (error) { diff --git a/lib/sendEventToDialogFlow.ts b/lib/sendEventToDialogFlow.ts index 60f974b..f952ce6 100644 --- a/lib/sendEventToDialogFlow.ts +++ b/lib/sendEventToDialogFlow.ts @@ -18,7 +18,7 @@ export const sendEventToDialogFlow = async (app: IApp, read: IRead, modify: IMo languageCode: data.custom_languageCode || defaultLanguageCode || LanguageCode.EN, parameters: parameters || {}, }; - const response: IDialogflowMessage = await Dialogflow.sendRequest(http, read, modify, rid, event, DialogflowRequestType.EVENT); + const response: IDialogflowMessage = await Dialogflow.sendRequest(http, read, modify, persistence, rid, event, DialogflowRequestType.EVENT); await createDialogflowMessage(rid, read, modify, response, app); } catch (error) { console.error(`${Logs.DIALOGFLOW_REST_API_ERROR}: { roomID: ${rid} } ${getError(error)}`); diff --git a/lib/sendWelcomeEvent.ts b/lib/sendWelcomeEvent.ts index 5dc0e49..c8e4b1a 100644 --- a/lib/sendWelcomeEvent.ts +++ b/lib/sendWelcomeEvent.ts @@ -27,7 +27,7 @@ export const sendWelcomeEventToDialogFlow = async (app: IApp, read: IRead, modi }; await createMessage(rid, read, modify, { customFields: disableInput }, app); - const response: IDialogflowMessage = await Dialogflow.sendRequest(http, read, modify, rid, event, DialogflowRequestType.EVENT); + const response: IDialogflowMessage = await Dialogflow.sendRequest(http, read, modify, persistence, rid, event, DialogflowRequestType.EVENT); await createDialogflowMessage(rid, read, modify, response, app); } catch (error) { console.error(`${Logs.DIALOGFLOW_REST_API_ERROR}: { roomID: ${rid} } ${getError(error)}`); diff --git a/lib/sentEventToDialogFlow.ts b/lib/sentEventToDialogFlow.ts deleted file mode 100644 index 60f974b..0000000 --- a/lib/sentEventToDialogFlow.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { IHttp, IModify, IPersistence, IRead } from '@rocket.chat/apps-engine/definition/accessors'; -import { IApp } from '@rocket.chat/apps-engine/definition/IApp'; -import { AppSetting } from '../config/Settings'; -import { DialogflowRequestType, IDialogflowCustomFields, IDialogflowMessage, LanguageCode } from '../enum/Dialogflow'; -import { Logs } from '../enum/Logs'; -import { getError } from '../lib/Helper'; -import { Dialogflow } from './Dialogflow'; -import { createDialogflowMessage, createMessage } from './Message'; -import { getRoomAssoc, retrieveDataByAssociation } from './Persistence'; -import { getLivechatAgentConfig } from './Settings'; - -export const sendEventToDialogFlow = async (app: IApp, read: IRead, modify: IModify, persistence: IPersistence, http: IHttp, rid: string, eventName: string, parameters: any = {}) => { - try { - const data = await retrieveDataByAssociation(read, getRoomAssoc(rid)); - const defaultLanguageCode = await getLivechatAgentConfig(read, rid, AppSetting.DialogflowAgentDefaultLanguage); - const event = { - name: eventName, - languageCode: data.custom_languageCode || defaultLanguageCode || LanguageCode.EN, - parameters: parameters || {}, - }; - const response: IDialogflowMessage = await Dialogflow.sendRequest(http, read, modify, rid, event, DialogflowRequestType.EVENT); - await createDialogflowMessage(rid, read, modify, response, app); - } catch (error) { - console.error(`${Logs.DIALOGFLOW_REST_API_ERROR}: { roomID: ${rid} } ${getError(error)}`); - const serviceUnavailable: string = await getLivechatAgentConfig(read, rid, AppSetting.DialogflowServiceUnavailableMessage); - await createMessage(rid, read, modify, { text: serviceUnavailable }, app); - return; - } -}; diff --git a/lib/sessionMaintenance/SessionMaintenanceProcessor.ts b/lib/sessionMaintenance/SessionMaintenanceProcessor.ts index c4ab9a7..4afea2f 100644 --- a/lib/sessionMaintenance/SessionMaintenanceProcessor.ts +++ b/lib/sessionMaintenance/SessionMaintenanceProcessor.ts @@ -49,7 +49,7 @@ export class SessionMaintenanceProcessor implements IProcessor { name: sessionMaintenanceEventName, languageCode: data.custom_languageCode || defaultLanguageCode || LanguageCode.EN, }; - await Dialogflow.sendRequest(http, read, modify, sessionId, eventData, DialogflowRequestType.EVENT); + await Dialogflow.sendRequest(http, read, modify, persis, sessionId, eventData, DialogflowRequestType.EVENT); } catch (error) { console.error(`${Logs.DIALOGFLOW_REST_API_ERROR}: { roomID: ${sessionId} } ${getError(error)}`); } diff --git a/package-lock.json b/package-lock.json index aaa247a..0480bda 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,9 +38,9 @@ } }, "@grpc/grpc-js": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.5.3.tgz", - "integrity": "sha512-q0xgaZ3ymUM+ZOhe1hdocVSdKHCnJ6llLSXcP+MqMXMyYPUZ3mzQOCxZ3Zkg+QZ7sZ950sn7hvueQrIJZumPZg==", + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.5.5.tgz", + "integrity": "sha512-FTd27ItHlsSG/7hp62xgI9YnqSwRbHRSVmDVR8DwOoC+6t8JhHRXe2JL0U8N9GLc0jS0HrtEbO/KP5+G0ebjLQ==", "dev": true, "requires": { "@grpc/proto-loader": "^0.6.4", @@ -48,9 +48,9 @@ }, "dependencies": { "@types/node": { - "version": "17.0.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.12.tgz", - "integrity": "sha512-4YpbAsnJXWYK/fpTVFlMIcUIho2AYCi4wg5aNPrG1ng7fn/1/RZfCIpRCiBX+12RVa34RluilnvCqD+g3KiSiA==", + "version": "17.0.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.18.tgz", + "integrity": "sha512-eKj4f/BsN/qcculZiRSujogjvp5O/k4lOW5m35NopjZM/QwLOR075a8pJW5hD+Rtdm2DaCVPENS6KtSQnUD6BA==", "dev": true } } @@ -133,8 +133,8 @@ "dev": true }, "@rocket.chat/apps-engine": { - "version": "git+https://github.com/WideChat/Rocket.Chat.Apps-engine.git#3e5da1c2c996a27197f9de7ca2292e1d1545e9d5", - "from": "git+https://github.com/WideChat/Rocket.Chat.Apps-engine.git#master", + "version": "git+https://github.com/WideChat/Rocket.Chat.Apps-engine.git#aa64cbd8a7863d79d0f5eb0d85bcde7ab31b9b70", + "from": "git+https://github.com/WideChat/Rocket.Chat.Apps-engine.git#dialogflow_branch", "dev": true, "requires": { "@google-cloud/dialogflow-cx": "^2.15.0", @@ -462,9 +462,9 @@ } }, "google-auth-library": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.11.0.tgz", - "integrity": "sha512-3S5jn2quRumvh9F/Ubf7GFrIq71HZ5a6vqosgdIu105kkk0WtSqc2jGCRqtWWOLRS8SX3AHACMOEDxhyWAQIcg==", + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.12.0.tgz", + "integrity": "sha512-RS/whvFPMoF1hQNxnoVET3DWKPBt1Xgqe2rY0k+Jn7TNhoHlwdnSe7Rlcbo2Nub3Mt2lUVz26X65aDQrWp6x8w==", "dev": true, "requires": { "arrify": "^2.0.0", @@ -479,9 +479,9 @@ } }, "google-gax": { - "version": "2.29.4", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-2.29.4.tgz", - "integrity": "sha512-3o6cByD2fE1yIc6i1gpKMQlJStqlvu8Sa/Ly/HCQ6GPHpltpVfkTT4KVj2YLVa7WTSDoGbsLBDmJ1KfN1uNiRw==", + "version": "2.29.7", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-2.29.7.tgz", + "integrity": "sha512-yC0Q4OqX6s081jV72Idt9sxZnuRHOjg0SR0FFH15gT3LDE2u/78xqLjZwa3VuprBvaOLM29jzU19Vv4I7E8ETQ==", "dev": true, "requires": { "@grpc/grpc-js": "~1.5.0", @@ -494,7 +494,7 @@ "is-stream-ended": "^0.1.4", "node-fetch": "^2.6.1", "object-hash": "^2.1.1", - "proto3-json-serializer": "^0.1.7", + "proto3-json-serializer": "^0.1.8", "protobufjs": "6.11.2", "retry-request": "^4.0.0" } @@ -509,13 +509,13 @@ } }, "gtoken": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.3.1.tgz", - "integrity": "sha512-yqOREjzLHcbzz1UrQoxhBtpk8KjrVhuqPE7od1K2uhyxG2BHjKZetlbLw/SPZak/QqTIQW+addS+EcjqQsZbwQ==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.3.2.tgz", + "integrity": "sha512-gkvEKREW7dXWF8NV8pVrKfW7WqReAmjjkMBh6lNCCGOM4ucS0r0YyXXl0r/9Yj8wcW/32ISkfc8h5mPTDbtifQ==", "dev": true, "requires": { "gaxios": "^4.0.0", - "google-p12-pem": "^3.0.3", + "google-p12-pem": "^3.1.3", "jws": "^4.0.0" } }, @@ -775,9 +775,9 @@ }, "dependencies": { "@types/node": { - "version": "17.0.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.12.tgz", - "integrity": "sha512-4YpbAsnJXWYK/fpTVFlMIcUIho2AYCi4wg5aNPrG1ng7fn/1/RZfCIpRCiBX+12RVa34RluilnvCqD+g3KiSiA==", + "version": "17.0.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.18.tgz", + "integrity": "sha512-eKj4f/BsN/qcculZiRSujogjvp5O/k4lOW5m35NopjZM/QwLOR075a8pJW5hD+Rtdm2DaCVPENS6KtSQnUD6BA==", "dev": true } } @@ -935,6 +935,12 @@ "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", "dev": true }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, "uuid": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", diff --git a/package.json b/package.json index 75639ab..cdbcb6b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "devDependencies": { - "@rocket.chat/apps-engine": "git+https://github.com/WideChat/Rocket.Chat.Apps-engine.git#master", + "@rocket.chat/apps-engine": "git+https://github.com/WideChat/Rocket.Chat.Apps-engine.git#dialogflow_branch", "@types/node": "10.17.5", "prettier": "^2.5.1", "tslint": "^5.20.1", From ef00d318ebea3dc958eb8254b8059075f6ac3d2f Mon Sep 17 00:00:00 2001 From: AlexanderKanakis Date: Tue, 15 Feb 2022 14:55:26 +0200 Subject: [PATCH 5/9] Removed redundant code and added global.ts --- Global.ts | 13 +++++++++++++ lib/Dialogflow.ts | 5 ----- lib/payloadAction.ts | 2 +- 3 files changed, 14 insertions(+), 6 deletions(-) create mode 100644 Global.ts diff --git a/Global.ts b/Global.ts new file mode 100644 index 0000000..8f2eb53 --- /dev/null +++ b/Global.ts @@ -0,0 +1,13 @@ +import { IApp } from '@rocket.chat/apps-engine/definition/IApp'; + +export class Global { + /* + * TODO: We are only using app for logger(Which shows up in Admin -> App Info -> Logs). + * It is being used in many core function of this app i.e, createMessage. Which makes these functions not usable where the app object is not available + * This Globle App object can be used in these functions for now. And also it's not a good idea to Global whole App object. + * So in future we will make only necessary object Global. i.e. Logger + * The idea is to remove dependecy of this App object from every core functions. and use Global logger instead. + * This is big refactor and will be done as separate task + */ + public static app: IApp; +} diff --git a/lib/Dialogflow.ts b/lib/Dialogflow.ts index 98a4003..dbb4d88 100644 --- a/lib/Dialogflow.ts +++ b/lib/Dialogflow.ts @@ -12,7 +12,6 @@ import { removeBotTypingListener } from './BotTyping'; import { base64urlEncode, getError } from './Helper'; import { createHttpRequest } from './Http'; import { createDialogflowMessage } from './Message'; -import { handlePayloadActions, handleResponse, testMessage } from './payloadAction'; import { getRoomAssoc, retrieveDataByAssociation } from './Persistence'; import { updateRoomCustomFields } from './Room'; import { getLivechatAgentConfig } from './Settings'; @@ -556,10 +555,6 @@ class DialogflowClass { } }); } - - private async testfunction() { - return; - } } export const Dialogflow = new DialogflowClass(); diff --git a/lib/payloadAction.ts b/lib/payloadAction.ts index 5c7c478..f1a010d 100644 --- a/lib/payloadAction.ts +++ b/lib/payloadAction.ts @@ -96,7 +96,7 @@ const sendChangeLanguageEvent = async (app: IApp, read: IRead, modify: IModify, try { const event = { name: 'ChangeLanguage', languageCode, parameters: {} }; - const response: IDialogflowMessage = await Dialogflow.sendRequest(http, read, modify, rid, event, DialogflowRequestType.EVENT); + const response: IDialogflowMessage = await Dialogflow.sendRequest(http, read, modify, persis, rid, event, DialogflowRequestType.EVENT); await createDialogflowMessage(rid, read, modify, response, app); } catch (error) { From 0378cb1c7164f0b3becdd4a9cee27e06a3e177c0 Mon Sep 17 00:00:00 2001 From: AlexanderKanakis Date: Tue, 1 Mar 2022 14:41:02 +0200 Subject: [PATCH 6/9] fixes --- app.json | 2 +- lib/Dialogflow.ts | 215 +++++++++++++++++++++++++------------------ lib/handleStreams.ts | 59 ++++++++++++ lib/payloadAction.ts | 16 +++- 4 files changed, 196 insertions(+), 96 deletions(-) create mode 100644 lib/handleStreams.ts diff --git a/app.json b/app.json index 4ef927e..124f7ae 100644 --- a/app.json +++ b/app.json @@ -19,5 +19,5 @@ "IPostLivechatRoomClosed", "IUIKitLivechatInteractionHandler" ], - "commitHash": "365b41481e35f48b6e888de3379cde12120999f3" + "commitHash": "ef00d318ebea3dc958eb8254b8059075f6ac3d2f" } \ No newline at end of file diff --git a/lib/Dialogflow.ts b/lib/Dialogflow.ts index dbb4d88..800510b 100644 --- a/lib/Dialogflow.ts +++ b/lib/Dialogflow.ts @@ -1,18 +1,21 @@ import { IHttp, IHttpRequest, IModify, IPersistence, IRead } from '@rocket.chat/apps-engine/definition/accessors'; -import { ApiEndpoint } from '@rocket.chat/apps-engine/definition/api'; import { ILivechatRoom } from '@rocket.chat/apps-engine/definition/livechat/ILivechatRoom'; import { IRoom } from '@rocket.chat/apps-engine/definition/rooms'; import { createSign } from 'crypto'; import { AppSetting } from '../config/Settings'; -import { DialogflowJWT, DialogflowRequestType, DialogflowUrl, IDialogflowAccessToken, IDialogflowCustomFields, IDialogflowEvent, IDialogflowImageCard, IDialogflowMessage, IDialogflowPayload, IDialogflowQuickReplies, LanguageCode } from '../enum/Dialogflow'; +import { DialogflowJWT, DialogflowRequestType, DialogflowUrl, IDialogflowAccessToken, IDialogflowAction, IDialogflowCustomFields, IDialogflowEvent, IDialogflowImageCard, IDialogflowMessage, IDialogflowPayload, IDialogflowQuickReplies, LanguageCode } from '../enum/Dialogflow'; import { Headers } from '../enum/Http'; import { Logs } from '../enum/Logs'; +import { JobName } from '../enum/Scheduler'; import { Global } from '../Global'; import { removeBotTypingListener } from './BotTyping'; +import { handlePostPartialResponse } from './handleStreams'; import { base64urlEncode, getError } from './Helper'; import { createHttpRequest } from './Http'; -import { createDialogflowMessage } from './Message'; -import { getRoomAssoc, retrieveDataByAssociation } from './Persistence'; +import { createDialogflowMessage, createMessage } from './Message'; +import { handleResponse } from './payloadAction'; +import { getRoomAssoc, retrieveDataByAssociation, setIsProcessingMessage, updatePersistentData } from './Persistence'; +import { handleParameters } from './responseParameters'; import { updateRoomCustomFields } from './Room'; import { getLivechatAgentConfig } from './Settings'; // tslint:disable-next-line: no-var-requires @@ -28,53 +31,26 @@ class DialogflowClass { request: IDialogflowEvent | string, requestType: DialogflowRequestType): Promise { - const room = await read.getRoomReader().getById(sessionId) as ILivechatRoom; - const { id: rid, visitor: { livechatData, token: visitorToken } } = room; - - const serverURL = await this.getServerURL(read, modify, http, sessionId); - const data = await retrieveDataByAssociation(read, getRoomAssoc(sessionId)); - - const defaultLanguageCode = await getLivechatAgentConfig(read, rid, AppSetting.DialogflowAgentDefaultLanguage); - const dialogFlowVersion = await getLivechatAgentConfig(read, sessionId, AppSetting.DialogflowAgentVersion); - - if (dialogFlowVersion === 'CX') { - - const accessToken = await this.getAccessToken(read, modify, http, sessionId); - if (!accessToken) { throw Error(Logs.ACCESS_TOKEN_ERROR); } - - try { - return await this.detectStreamingIntent(read, modify, http, persistence, sessionId, requestType, request, data); - // return await this.parseCXRequest(read, response, sessionId); - } catch (error) { - const errorContent = `${Logs.HTTP_REQUEST_ERROR}: { roomID: ${sessionId} } ${getError(error)}`; - console.error(errorContent); - throw new Error(error); - } - } else { - - const queryInput = { - ...requestType === DialogflowRequestType.EVENT && { event: request }, - ...requestType === DialogflowRequestType.MESSAGE && { text: - { - languageCode: data.custom_languageCode || defaultLanguageCode || LanguageCode.EN, - text: request, - }, - }, - }; - - const httpRequestContent: IHttpRequest = createHttpRequest( - { 'Content-Type': Headers.CONTENT_TYPE_JSON, 'Accept': Headers.ACCEPT_JSON}, - { queryInput }, - ); - - try { - const response = await http.post(serverURL, httpRequestContent); - return this.parseRequest(response.data, sessionId); - } catch (error) { - const errorContent = `${Logs.HTTP_REQUEST_ERROR}: { roomID: ${sessionId} } ${getError(error)}`; - console.error(errorContent); - throw new Error(error); + if (!data.handlingStream) { + const dialogFlowVersion = await getLivechatAgentConfig(read, sessionId, AppSetting.DialogflowAgentVersion); + + if (dialogFlowVersion === 'CX') { + try { + return await this.detectStreamingIntent(read, modify, http, persistence, sessionId, requestType, request); + } catch (error) { + const errorContent = `${Logs.HTTP_REQUEST_ERROR}: { roomID: ${sessionId} } ${getError(error)}`; + console.error(errorContent); + throw new Error(error); + } + } else { + try { + return this.detectESIntent(read, modify, http, sessionId, request, requestType); + } catch (error) { + const errorContent = `${Logs.HTTP_REQUEST_ERROR}: { roomID: ${sessionId} } ${getError(error)}`; + console.error(errorContent); + throw new Error(error); + } } } } @@ -245,7 +221,7 @@ class DialogflowClass { currentMessageType = 'text'; const { text: textMessageArray } = text; - const sourceType = this.getSourceType(text, diagnosticInfo); + const sourceType = this.getSourceType(response.responseType, text, diagnosticInfo); if (sourceType === 'intent') { if (intentConcatText !== '') { @@ -323,22 +299,42 @@ class DialogflowClass { } } - private async getServerURL(read: IRead, modify: IModify, http: IHttp, sessionId: string) { - const projectId = await getLivechatAgentConfig(read, sessionId, AppSetting.DialogflowAgentProjectId); - const environmentId = await getLivechatAgentConfig(read, sessionId, AppSetting.DialogflowAgentEnvironmentId); - const dialogFlowVersion = await getLivechatAgentConfig(read, sessionId, AppSetting.DialogflowAgentVersion); + public async detectESIntent( + read: IRead, + modify: IModify, + http: IHttp, + sessionId: string, + request: IDialogflowEvent | string, + requestType: DialogflowRequestType) { + const room = await read.getRoomReader().getById(sessionId) as ILivechatRoom; + const { id: rid, visitor: { livechatData, token: visitorToken } } = room; + const serverURL = await this.getServerURL(read, modify, http, sessionId); + const defaultLanguageCode = await getLivechatAgentConfig(read, sessionId, AppSetting.DialogflowAgentDefaultLanguage); + const data = await retrieveDataByAssociation(read, getRoomAssoc(sessionId)); - if (dialogFlowVersion === 'CX') { + const queryInput = { + ...requestType === DialogflowRequestType.EVENT && { event: request }, + ...requestType === DialogflowRequestType.MESSAGE && { text: + { + languageCode: data.custom_languageCode || defaultLanguageCode || LanguageCode.EN, + text: request, + }, + }, + }; - const regionId = await getLivechatAgentConfig(read, sessionId, AppSetting.DialogflowAgentRegion); - const agentId = await getLivechatAgentConfig(read, sessionId, AppSetting.DialogflowAgentId); + const httpRequestContent: IHttpRequest = createHttpRequest( + { 'Content-Type': Headers.CONTENT_TYPE_JSON, 'Accept': Headers.ACCEPT_JSON}, + { queryInput }, + ); - return `https://${regionId}-dialogflow.googleapis.com/v3/projects/${projectId}/locations/${regionId}/agents/${agentId}/environments/${environmentId || 'draft'}/sessions/${sessionId}:detectIntent`; + try { + const response = await http.post(serverURL, httpRequestContent); + return this.parseRequest(response.data, sessionId); + } catch (error) { + const errorContent = `${Logs.HTTP_REQUEST_ERROR}: { roomID: ${sessionId} } ${getError(error)}`; + console.error(errorContent); + throw new Error(error); } - - const accessToken = await this.getAccessToken(read, modify, http, sessionId); - if (!accessToken) { throw Error(Logs.ACCESS_TOKEN_ERROR); } - return `https://dialogflow.googleapis.com/v2/projects/${projectId}/agent/environments/${environmentId || 'draft'}/users/-/sessions/${sessionId}:detectIntent?access_token=${accessToken}`; } private async getAccessToken(read: IRead, modify: IModify, http: IHttp, sessionId: string) { @@ -377,6 +373,24 @@ class DialogflowClass { } } + private async getServerURL(read: IRead, modify: IModify, http: IHttp, sessionId: string) { + const projectId = await getLivechatAgentConfig(read, sessionId, AppSetting.DialogflowAgentProjectId); + const environmentId = await getLivechatAgentConfig(read, sessionId, AppSetting.DialogflowAgentEnvironmentId); + const dialogFlowVersion = await getLivechatAgentConfig(read, sessionId, AppSetting.DialogflowAgentVersion); + + if (dialogFlowVersion === 'CX') { + + const regionId = await getLivechatAgentConfig(read, sessionId, AppSetting.DialogflowAgentRegion); + const agentId = await getLivechatAgentConfig(read, sessionId, AppSetting.DialogflowAgentId); + + return `https://${regionId}-dialogflow.googleapis.com/v3/projects/${projectId}/locations/${regionId}/agents/${agentId}/environments/${environmentId || 'draft'}/sessions/${sessionId}:detectIntent`; + } + + const accessToken = await this.getAccessToken(read, modify, http, sessionId); + if (!accessToken) { throw Error(Logs.ACCESS_TOKEN_ERROR); } + return `https://dialogflow.googleapis.com/v2/projects/${projectId}/agent/environments/${environmentId || 'draft'}/users/-/sessions/${sessionId}:detectIntent?access_token=${accessToken}`; + } + private hasExpired(expiration: Date): boolean { if (!expiration) { return true; } return Date.now() >= expiration.getTime(); @@ -432,23 +446,39 @@ class DialogflowClass { return sign.sign(privateKey, DialogflowJWT.BASE_64).replace(/\+/g, '-').replace(/\//g, '_'); } - private getSourceType(text: any, info: any) { - // const executionStep = info['Execution Sequence'][2] ? info['Execution Sequence'][2]['Step 3'] : info['Execution Sequence'][1]['Step 2']; - const executionStep = info.fields['Execution Sequence'].listValue.values[2] ? - info.fields['Execution Sequence'].listValue.values[2].structValue.fields['Step 3'] : - info.fields['Execution Sequence'].listValue.values[1].structValue.fields['Step 2']; + private getSourceType(responseType: string, text: any, info: any) { + if (responseType === 'PARTIAL' || responseType === 'FINAL') { + const executionStep = info.fields['Execution Sequence'].listValue.values[2] ? + info.fields['Execution Sequence'].listValue.values[2].structValue.fields['Step 3'] : + info.fields['Execution Sequence'].listValue.values[1].structValue.fields['Step 2']; - if (executionStep) { + if (executionStep) { - const intentResponses = executionStep.structValue.fields.FunctionExecution ? - executionStep.structValue.fields.FunctionExecution.structValue.fields.Responses : null; + const intentResponses = executionStep.structValue.fields.FunctionExecution ? + executionStep.structValue.fields.FunctionExecution.structValue.fields.Responses : null; - if (intentResponses) { - for (const response of intentResponses.listValue.values) { - if (response.structValue.fields.text && - response.structValue.fields.text.structValue.fields.text.listValue.values[0].stringValue === text.text[0] && - response.structValue.fields.responseType === 'HANDLER_PROMPT') { - return 'intent'; + if (intentResponses) { + for (const response of intentResponses.listValue.values) { + if (response.structValue.fields.text && + response.structValue.fields.text.structValue.fields.text.listValue.values[0].stringValue === text.text[0] && + response.structValue.fields.responseType === 'HANDLER_PROMPT') { + return 'intent'; + } + } + } + } + } else { + const executionStep = info['Execution Sequence'][2] ? info['Execution Sequence'][2]['Step 3'] : info['Execution Sequence'][1]['Step 2']; + + if (executionStep) { + + const intentResponses = executionStep.FunctionExecution ? executionStep.FunctionExecution.Responses : null; + + if (intentResponses) { + for (const response of intentResponses) { + if (response.text && response.text.text[0] === text.text[0] && response.responseType === 'HANDLER_PROMPT') { + return 'intent'; + } } } } @@ -464,8 +494,7 @@ class DialogflowClass { persistence: IPersistence, sessionId: string, requestType: DialogflowRequestType, - request: IDialogflowEvent | string, - data: any) { + request: IDialogflowEvent | string) { const projectId = await getLivechatAgentConfig(read, sessionId, AppSetting.DialogflowAgentProjectId); const environmentId = await getLivechatAgentConfig(read, sessionId, AppSetting.DialogflowAgentEnvironmentId); const defaultLanguageCode = await getLivechatAgentConfig(read, sessionId, AppSetting.DialogflowAgentDefaultLanguage); @@ -475,6 +504,11 @@ class DialogflowClass { const clientEmail = await getLivechatAgentConfig(read, sessionId, AppSetting.DialogflowAgentClientEmail); const room = await read.getRoomReader().getById(sessionId) as ILivechatRoom; const { id: rid, visitor: { livechatData, token: visitorToken } } = room; + const assoc = getRoomAssoc(sessionId); + const data = await retrieveDataByAssociation(read, assoc); + + const accessToken = await this.getAccessToken(read, modify, http, sessionId); + if (!accessToken) { throw Error(Logs.ACCESS_TOKEN_ERROR); } const client = new DialogflowApi.SessionsClient({ apiEndpoint: `${regionId}-dialogflow.googleapis.com`, @@ -516,19 +550,20 @@ class DialogflowClass { if (response) { if (response.responseType === 'PARTIAL') { const parsedResponse = await this.parseCXRequest(read, response, sessionId); - const { messages = [], isFallback = false } = parsedResponse; - for (const message of messages) { - const { action = null } = message as IDialogflowPayload; - const textMessages: Array = []; - textMessages.push(message); - const messagesToProcess: IDialogflowMessage = { - messages: textMessages, - isFallback, - }; - await createDialogflowMessage(rid, read, modify, messagesToProcess, Global.app); + if (!data.handlingStream) { + await persistence.createWithAssociation({handlingStream: true}, assoc); + } else { + await updatePersistentData(read, persistence, assoc, {handlingStream: true}); } + await handleResponse(Global.app, read, modify, http, persistence, rid, visitorToken, parsedResponse, true); + await handleParameters(Global.app, read, modify, persistence, http, rid, visitorToken, parsedResponse, true); } else { - try { + try { + if (!data.hasOwnProperty('handlingStream')) { + await persistence.createWithAssociation({handlingStream: false}, assoc); + } else { + await updatePersistentData(read, persistence, assoc, {handlingStream: false}); + } detectStream.end(); resolve(await this.parseCXRequest(read, response, sessionId)); } catch (e) { diff --git a/lib/handleStreams.ts b/lib/handleStreams.ts new file mode 100644 index 0000000..ea7ca3b --- /dev/null +++ b/lib/handleStreams.ts @@ -0,0 +1,59 @@ +import { IHttp, IHttpRequest, IModify, IPersistence, IRead } from '@rocket.chat/apps-engine/definition/accessors'; +import { IApp } from '@rocket.chat/apps-engine/definition/IApp'; +import { ILivechatRoom } from '@rocket.chat/apps-engine/definition/livechat'; +import { AppSetting } from '../config/Settings'; +import { ActionIds } from '../enum/ActionIds'; +import { DialogflowRequestType, IDialogflowAction, IDialogflowEvent, IDialogflowImageCard, IDialogflowMessage, IDialogflowPayload, IDialogflowQuickReplies, LanguageCode } from '../enum/Dialogflow'; +import { Headers } from '../enum/Http'; +import { Logs } from '../enum/Logs'; +import { JobName } from '../enum/Scheduler'; +import { Global } from '../Global'; +import { removeBotTypingListener } from './BotTyping'; +import { Dialogflow } from './Dialogflow'; +import { getError } from './Helper'; +import { createHttpRequest } from './Http'; +import { createDialogflowMessage, createMessage } from './Message'; +import { handlePayloadActions, handleResponse } from './payloadAction'; +import { getRoomAssoc, retrieveDataByAssociation, setIsProcessingMessage, updatePersistentData } from './Persistence'; +import { handleParameters } from './responseParameters'; +import { closeChat , performHandover, updateRoomCustomFields } from './Room'; +import { sendWelcomeEventToDialogFlow } from './sendWelcomeEvent'; +import { getLivechatAgentConfig } from './Settings'; +import { incFallbackIntentAndSendResponse } from './SynchronousHandover'; + +export const handlePostPartialResponse = async (app: IApp, read: IRead, modify: IModify, http: IHttp, persistence: IPersistence, rid: string, visitorToken: string, response: any) => { + const room = await read.getRoomReader().getById(rid) as ILivechatRoom; + const { servedBy } = room; + + if (!servedBy) { + return; + } + + await setIsProcessingMessage(persistence, rid, false); + const createResponseMessage = async () => await createDialogflowMessage(rid, read, modify, response, Global.app); + + // synchronous handover check + + // await handleResponse(Global.app, read, modify, http, persistence, rid, visitorToken, response); + // await handleParameters(app, read, modify, persistence, http, rid, visitorToken, response); + + const { messages = [], isFallback = false } = response; + if (isFallback) { + await removeBotTypingListener(modify, rid, servedBy.username); + return incFallbackIntentAndSendResponse(Global.app, read, modify, rid, createResponseMessage); + } + for (const message of messages) { + const { action = null } = message as IDialogflowPayload; + const textMessages: Array = []; + textMessages.push(message); + const messagesToProcess: IDialogflowMessage = { + messages: textMessages, + isFallback, + }; + if (action) { + // await handlePayloadActions(app, read, modify, http, persistence, rid, visitorToken, messagesToProcess); + } else { + await createDialogflowMessage(rid, read, modify, messagesToProcess, app); + } + } +}; diff --git a/lib/payloadAction.ts b/lib/payloadAction.ts index f1a010d..a92e4ba 100644 --- a/lib/payloadAction.ts +++ b/lib/payloadAction.ts @@ -7,7 +7,7 @@ import { DialogflowRequestType, IDialogflowAction, IDialogflowImageCard, IDialo import { Logs } from '../enum/Logs'; import { JobName } from '../enum/Scheduler'; import { getError } from '../lib/Helper'; -import { getRoomAssoc, retrieveDataByAssociation } from '../lib/Persistence'; +import { getRoomAssoc, retrieveDataByAssociation, setIsProcessingMessage, updatePersistentData } from '../lib/Persistence'; import { closeChat, performHandover, updateRoomCustomFields } from '../lib/Room'; import { sendWelcomeEventToDialogFlow } from '../lib/sendWelcomeEvent'; import { Dialogflow } from './Dialogflow'; @@ -43,13 +43,13 @@ export const handlePayloadActions = async (app: IApp, read: IRead, modify: IMo } await performHandover(app, modify, read, rid, visitorToken, targetDepartment); } else if (actionName === ActionIds.CLOSE_CHAT) { - const room = await read.getRoomReader().getById(rid) as ILivechatRoom; - if (room && room.isOpen) { + const livechatRoom = await read.getRoomReader().getById(rid) as ILivechatRoom; + if (livechatRoom && livechatRoom.isOpen) { await closeChat(modify, read, rid); } } else if (actionName === ActionIds.NEW_WELCOME_EVENT) { const livechatRoom = await read.getRoomReader().getById(rid) as ILivechatRoom; - if (!livechatRoom) { throw new Error(); } + if (!livechatRoom) { throw new Error(Logs.INVALID_ROOM_ID); } const { visitor: { livechatData } } = livechatRoom; await sendWelcomeEventToDialogFlow(app, read, modify, persistence, http, rid, visitorToken, livechatData); } else if (actionName === ActionIds.SET_TIMEOUT) { @@ -67,7 +67,13 @@ export const handlePayloadActions = async (app: IApp, read: IRead, modify: IMo try { await modify.getScheduler().scheduleOnce(task); + + // Start blackout window + if (params.continue_blackout) { + await setIsProcessingMessage(persistence, rid, true); + } } catch (error) { + console.error(error); const serviceUnavailable: string = await getLivechatAgentConfig(read, rid, AppSetting.DialogflowServiceUnavailableMessage); await createMessage(rid, read, modify, { text: serviceUnavailable }, app); return; @@ -79,7 +85,7 @@ export const handlePayloadActions = async (app: IApp, read: IRead, modify: IMo if (data && data.custom_languageCode) { if (data.custom_languageCode !== params.newLanguageCode) { - await persistence.updateByAssociation(assoc, {custom_languageCode: params.newLanguageCode}); + await updatePersistentData(read, persistence, assoc, {custom_languageCode: params.newLanguageCode}); sendChangeLanguageEvent(app, read, modify, persistence, rid, http, params.newLanguageCode); } } else { From f4d0cf7722de2234270e015bc9ce31c73a23026c Mon Sep 17 00:00:00 2001 From: AlexanderKanakis Date: Thu, 10 Mar 2022 17:14:07 +0200 Subject: [PATCH 7/9] Finalize partial response handling --- app.json | 2 +- lib/Dialogflow.ts | 153 ++++++++++++++++++++++--------------------- lib/handleStreams.ts | 92 ++++++++++++++++++++++---- package-lock.json | 42 ++++++------ 4 files changed, 181 insertions(+), 108 deletions(-) diff --git a/app.json b/app.json index 124f7ae..d1c77af 100644 --- a/app.json +++ b/app.json @@ -19,5 +19,5 @@ "IPostLivechatRoomClosed", "IUIKitLivechatInteractionHandler" ], - "commitHash": "ef00d318ebea3dc958eb8254b8059075f6ac3d2f" + "commitHash": "0378cb1c7164f0b3becdd4a9cee27e06a3e177c0" } \ No newline at end of file diff --git a/lib/Dialogflow.ts b/lib/Dialogflow.ts index 800510b..c86bfb8 100644 --- a/lib/Dialogflow.ts +++ b/lib/Dialogflow.ts @@ -1,28 +1,33 @@ import { IHttp, IHttpRequest, IModify, IPersistence, IRead } from '@rocket.chat/apps-engine/definition/accessors'; import { ILivechatRoom } from '@rocket.chat/apps-engine/definition/livechat/ILivechatRoom'; import { IRoom } from '@rocket.chat/apps-engine/definition/rooms'; +// import { DialogFlowApi } from '@rocket.chat/apps-engine/lib/dialogflow-cx'; import { createSign } from 'crypto'; import { AppSetting } from '../config/Settings'; import { DialogflowJWT, DialogflowRequestType, DialogflowUrl, IDialogflowAccessToken, IDialogflowAction, IDialogflowCustomFields, IDialogflowEvent, IDialogflowImageCard, IDialogflowMessage, IDialogflowPayload, IDialogflowQuickReplies, LanguageCode } from '../enum/Dialogflow'; import { Headers } from '../enum/Http'; import { Logs } from '../enum/Logs'; -import { JobName } from '../enum/Scheduler'; import { Global } from '../Global'; import { removeBotTypingListener } from './BotTyping'; import { handlePostPartialResponse } from './handleStreams'; import { base64urlEncode, getError } from './Helper'; import { createHttpRequest } from './Http'; -import { createDialogflowMessage, createMessage } from './Message'; -import { handleResponse } from './payloadAction'; import { getRoomAssoc, retrieveDataByAssociation, setIsProcessingMessage, updatePersistentData } from './Persistence'; -import { handleParameters } from './responseParameters'; import { updateRoomCustomFields } from './Room'; import { getLivechatAgentConfig } from './Settings'; + // tslint:disable-next-line: no-var-requires -const DialogflowApi = require('@rocket.chat/apps-engine/node_modules/@google-cloud/dialogflow-cx'); +const DialogFlowApi = require('@rocket.chat/apps-engine/lib/dialogflow-cx'); class DialogflowClass { private jwtExpiration: Date; + + private JSON_SIMPLE_VALUE_KINDS = new Set([ + 'numberValue', + 'stringValue', + 'boolValue', + ]); + public async sendRequest(http: IHttp, read: IRead, modify: IModify, @@ -31,26 +36,23 @@ class DialogflowClass { request: IDialogflowEvent | string, requestType: DialogflowRequestType): Promise { - const data = await retrieveDataByAssociation(read, getRoomAssoc(sessionId)); - if (!data.handlingStream) { - const dialogFlowVersion = await getLivechatAgentConfig(read, sessionId, AppSetting.DialogflowAgentVersion); - - if (dialogFlowVersion === 'CX') { - try { - return await this.detectStreamingIntent(read, modify, http, persistence, sessionId, requestType, request); - } catch (error) { - const errorContent = `${Logs.HTTP_REQUEST_ERROR}: { roomID: ${sessionId} } ${getError(error)}`; - console.error(errorContent); - throw new Error(error); - } - } else { - try { - return this.detectESIntent(read, modify, http, sessionId, request, requestType); - } catch (error) { - const errorContent = `${Logs.HTTP_REQUEST_ERROR}: { roomID: ${sessionId} } ${getError(error)}`; - console.error(errorContent); - throw new Error(error); - } + const dialogFlowVersion = await getLivechatAgentConfig(read, sessionId, AppSetting.DialogflowAgentVersion); + + if (dialogFlowVersion === 'CX') { + try { + return await this.detectStreamingIntent(read, modify, http, persistence, sessionId, requestType, request); + } catch (error) { + const errorContent = `${Logs.HTTP_REQUEST_ERROR}: { roomID: ${sessionId} } ${getError(error)}`; + console.error(errorContent); + throw new Error(error); + } + } else { + try { + return this.detectESIntent(read, modify, http, sessionId, request, requestType); + } catch (error) { + const errorContent = `${Logs.HTTP_REQUEST_ERROR}: { roomID: ${sessionId} } ${getError(error)}`; + console.error(errorContent); + throw new Error(error); } } } @@ -183,7 +185,6 @@ class DialogflowClass { } const { session, queryResult } = response; - if (queryResult) { const { responseMessages, match: { matchType }, diagnosticInfo } = queryResult; @@ -204,8 +205,10 @@ class DialogflowClass { if (responseMessages) { responseMessages.forEach((message) => { - const { text, payload: { quickReplies = null, customFields = null, action = null, isFallback = false } = {} } = message; - + const { text } = message; + const { payload: structPayload = {} } = message; + const payload = this.structProtoToJson(structPayload) as any; + const { quickReplies = null, customFields = null, action = null, isFallback = false } = payload; if (!text && previousMessageType === 'text') { if (intentConcatText !== '') { messages.push({ text: intentConcatText }); @@ -221,7 +224,7 @@ class DialogflowClass { currentMessageType = 'text'; const { text: textMessageArray } = text; - const sourceType = this.getSourceType(response.responseType, text, diagnosticInfo); + const sourceType = this.getSourceType(text, diagnosticInfo); if (sourceType === 'intent') { if (intentConcatText !== '') { @@ -446,39 +449,18 @@ class DialogflowClass { return sign.sign(privateKey, DialogflowJWT.BASE_64).replace(/\+/g, '-').replace(/\//g, '_'); } - private getSourceType(responseType: string, text: any, info: any) { - if (responseType === 'PARTIAL' || responseType === 'FINAL') { - const executionStep = info.fields['Execution Sequence'].listValue.values[2] ? - info.fields['Execution Sequence'].listValue.values[2].structValue.fields['Step 3'] : - info.fields['Execution Sequence'].listValue.values[1].structValue.fields['Step 2']; + private getSourceType(text: any, structInfo: any) { + const info = this.structProtoToJson(structInfo); + const executionStep = info['Execution Sequence'][2] ? info['Execution Sequence'][2]['Step 3'] : info['Execution Sequence'][1]['Step 2']; - if (executionStep) { + if (executionStep) { - const intentResponses = executionStep.structValue.fields.FunctionExecution ? - executionStep.structValue.fields.FunctionExecution.structValue.fields.Responses : null; + const intentResponses = executionStep.FunctionExecution ? executionStep.FunctionExecution.Responses : null; - if (intentResponses) { - for (const response of intentResponses.listValue.values) { - if (response.structValue.fields.text && - response.structValue.fields.text.structValue.fields.text.listValue.values[0].stringValue === text.text[0] && - response.structValue.fields.responseType === 'HANDLER_PROMPT') { - return 'intent'; - } - } - } - } - } else { - const executionStep = info['Execution Sequence'][2] ? info['Execution Sequence'][2]['Step 3'] : info['Execution Sequence'][1]['Step 2']; - - if (executionStep) { - - const intentResponses = executionStep.FunctionExecution ? executionStep.FunctionExecution.Responses : null; - - if (intentResponses) { - for (const response of intentResponses) { - if (response.text && response.text.text[0] === text.text[0] && response.responseType === 'HANDLER_PROMPT') { - return 'intent'; - } + if (intentResponses) { + for (const response of intentResponses) { + if (response.text && response.text.text[0] === text.text[0] && response.responseType === 'HANDLER_PROMPT') { + return 'intent'; } } } @@ -510,7 +492,7 @@ class DialogflowClass { const accessToken = await this.getAccessToken(read, modify, http, sessionId); if (!accessToken) { throw Error(Logs.ACCESS_TOKEN_ERROR); } - const client = new DialogflowApi.SessionsClient({ + const client = new DialogFlowApi.SessionsClient({ apiEndpoint: `${regionId}-dialogflow.googleapis.com`, credentials: { private_key: privateKey, client_email: clientEmail }, }); @@ -550,20 +532,9 @@ class DialogflowClass { if (response) { if (response.responseType === 'PARTIAL') { const parsedResponse = await this.parseCXRequest(read, response, sessionId); - if (!data.handlingStream) { - await persistence.createWithAssociation({handlingStream: true}, assoc); - } else { - await updatePersistentData(read, persistence, assoc, {handlingStream: true}); - } - await handleResponse(Global.app, read, modify, http, persistence, rid, visitorToken, parsedResponse, true); - await handleParameters(Global.app, read, modify, persistence, http, rid, visitorToken, parsedResponse, true); + await handlePostPartialResponse(Global.app, read, modify, http, persistence, rid, visitorToken, parsedResponse); } else { - try { - if (!data.hasOwnProperty('handlingStream')) { - await persistence.createWithAssociation({handlingStream: false}, assoc); - } else { - await updatePersistentData(read, persistence, assoc, {handlingStream: false}); - } + try { detectStream.end(); resolve(await this.parseCXRequest(read, response, sessionId)); } catch (e) { @@ -573,7 +544,7 @@ class DialogflowClass { } } }) - .on('end', async (streamData: any) => { + .on('end', async () => { if (room.servedBy) { await removeBotTypingListener(modify, rid, room.servedBy.username); } @@ -590,6 +561,40 @@ class DialogflowClass { } }); } + + private structProtoToJson(proto) { + if (!proto || !proto.fields) { + return {}; + } + const json = {}; + for (const k in proto.fields) { + if (proto.fields[k]) { + json[k] = this.valueProtoToJson(proto.fields[k]); + } + } + return json; + } + + private valueProtoToJson(proto) { + if (!proto || !proto.kind) { + return null; + } + if (this.JSON_SIMPLE_VALUE_KINDS.has(proto.kind)) { + return proto[proto.kind]; + } else if (proto.kind === 'nullValue') { + return null; + } else if (proto.kind === 'listValue') { + if (!proto.listValue || !proto.listValue.values) { + console.warn('Invalid JSON list value proto: ', JSON.stringify(proto)); + } + return proto.listValue.values.map(this.valueProtoToJson); + } else if (proto.kind === 'structValue') { + return this.structProtoToJson(proto.structValue); + } else { + console.warn('Unsupported JSON value proto kind: ', proto.kind); + return null; + } + } } export const Dialogflow = new DialogflowClass(); diff --git a/lib/handleStreams.ts b/lib/handleStreams.ts index ea7ca3b..50356aa 100644 --- a/lib/handleStreams.ts +++ b/lib/handleStreams.ts @@ -1,23 +1,14 @@ -import { IHttp, IHttpRequest, IModify, IPersistence, IRead } from '@rocket.chat/apps-engine/definition/accessors'; +import { IHttp, IModify, IPersistence, IRead } from '@rocket.chat/apps-engine/definition/accessors'; import { IApp } from '@rocket.chat/apps-engine/definition/IApp'; import { ILivechatRoom } from '@rocket.chat/apps-engine/definition/livechat'; import { AppSetting } from '../config/Settings'; import { ActionIds } from '../enum/ActionIds'; -import { DialogflowRequestType, IDialogflowAction, IDialogflowEvent, IDialogflowImageCard, IDialogflowMessage, IDialogflowPayload, IDialogflowQuickReplies, LanguageCode } from '../enum/Dialogflow'; -import { Headers } from '../enum/Http'; -import { Logs } from '../enum/Logs'; +import { IDialogflowAction, IDialogflowImageCard, IDialogflowMessage, IDialogflowPayload, IDialogflowQuickReplies } from '../enum/Dialogflow'; import { JobName } from '../enum/Scheduler'; import { Global } from '../Global'; import { removeBotTypingListener } from './BotTyping'; -import { Dialogflow } from './Dialogflow'; -import { getError } from './Helper'; -import { createHttpRequest } from './Http'; import { createDialogflowMessage, createMessage } from './Message'; -import { handlePayloadActions, handleResponse } from './payloadAction'; import { getRoomAssoc, retrieveDataByAssociation, setIsProcessingMessage, updatePersistentData } from './Persistence'; -import { handleParameters } from './responseParameters'; -import { closeChat , performHandover, updateRoomCustomFields } from './Room'; -import { sendWelcomeEventToDialogFlow } from './sendWelcomeEvent'; import { getLivechatAgentConfig } from './Settings'; import { incFallbackIntentAndSendResponse } from './SynchronousHandover'; @@ -51,9 +42,86 @@ export const handlePostPartialResponse = async (app: IApp, read: IRead, modify isFallback, }; if (action) { - // await handlePayloadActions(app, read, modify, http, persistence, rid, visitorToken, messagesToProcess); + await handlePartialPayloadActions(app, read, modify, http, persistence, rid, visitorToken, messagesToProcess); } else { await createDialogflowMessage(rid, read, modify, messagesToProcess, app); } } + + await handlePartialParameters(Global.app, read, modify, persistence, http, rid, visitorToken, response); +}; + +export const handlePartialPayloadActions = async (app: IApp, read: IRead, modify: IModify, http: IHttp, persistence: IPersistence, rid: string, visitorToken: string, dialogflowMessage: IDialogflowMessage) => { + const { messages = [] } = dialogflowMessage; + for (const message of messages) { + const { action = null } = message as IDialogflowPayload; + console.log(action); + if (action) { + const { name: actionName, params } = action as IDialogflowAction; + if (actionName) { + if (actionName === ActionIds.SET_TIMEOUT) { + + const task = { + id: JobName.EVENT_SCHEDULER, + when: `${Number(params.time)} seconds`, + data: { + eventName: params.eventName , + rid, + sessionId: rid, + jobName: JobName.EVENT_SCHEDULER, + }, + }; + + try { + await modify.getScheduler().scheduleOnce(task); + + // Start blackout window + if (params.continue_blackout) { + await setIsProcessingMessage(persistence, rid, true); + } + } catch (error) { + console.error(error); + const serviceUnavailable: string = await getLivechatAgentConfig(read, rid, AppSetting.DialogflowServiceUnavailableMessage); + await createMessage(rid, read, modify, { text: serviceUnavailable }, app); + return; + } + + } else if (actionName === ActionIds.CHANGE_LANGUAGE_CODE) { + const assoc = getRoomAssoc(rid); + const data = await retrieveDataByAssociation(read, assoc); + + if (data && data.custom_languageCode) { + if (data.custom_languageCode !== params.newLanguageCode) { + await updatePersistentData(read, persistence, assoc, {custom_languageCode: params.newLanguageCode}); + } + } else { + await persistence.createWithAssociation({custom_languageCode: params.newLanguageCode}, assoc); + } + } + } + } + } +}; + +export const handlePartialParameters = async (app: IApp, read: IRead, modify: IModify, persistence: IPersistence, http: IHttp, rid: string, visitorToken: string, dialogflowMessage: IDialogflowMessage) => { + const { parameters = [] } = dialogflowMessage; + try { + if (parameters && parameters.custom_languagecode) { + + const assoc = getRoomAssoc(rid); + const data = await retrieveDataByAssociation(read, assoc); + + if (data && data.custom_languageCode) { + if (data.custom_languageCode !== parameters.custom_languagecode) { + await persistence.updateByAssociation(assoc, {custom_languageCode: parameters.custom_languagecode}); + } + } else { + await persistence.createWithAssociation({custom_languageCode: parameters.custom_languagecode}, assoc); + } + + } + } catch (e: any) { + console.error(e); + } + }; diff --git a/package-lock.json b/package-lock.json index 0480bda..9b06eec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,9 +38,9 @@ } }, "@grpc/grpc-js": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.5.5.tgz", - "integrity": "sha512-FTd27ItHlsSG/7hp62xgI9YnqSwRbHRSVmDVR8DwOoC+6t8JhHRXe2JL0U8N9GLc0jS0HrtEbO/KP5+G0ebjLQ==", + "version": "1.5.7", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.5.7.tgz", + "integrity": "sha512-RAlSbZ9LXo0wNoHKeUlwP9dtGgVBDUbnBKFpfAv5iSqMG4qWz9um2yLH215+Wow1I48etIa1QMS+WAGmsE/7HQ==", "dev": true, "requires": { "@grpc/proto-loader": "^0.6.4", @@ -48,9 +48,9 @@ }, "dependencies": { "@types/node": { - "version": "17.0.18", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.18.tgz", - "integrity": "sha512-eKj4f/BsN/qcculZiRSujogjvp5O/k4lOW5m35NopjZM/QwLOR075a8pJW5hD+Rtdm2DaCVPENS6KtSQnUD6BA==", + "version": "17.0.21", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz", + "integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==", "dev": true } } @@ -133,7 +133,7 @@ "dev": true }, "@rocket.chat/apps-engine": { - "version": "git+https://github.com/WideChat/Rocket.Chat.Apps-engine.git#aa64cbd8a7863d79d0f5eb0d85bcde7ab31b9b70", + "version": "git+https://github.com/WideChat/Rocket.Chat.Apps-engine.git#100783321beba256003c1745f51463a0ffa08549", "from": "git+https://github.com/WideChat/Rocket.Chat.Apps-engine.git#dialogflow_branch", "dev": true, "requires": { @@ -462,9 +462,9 @@ } }, "google-auth-library": { - "version": "7.12.0", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.12.0.tgz", - "integrity": "sha512-RS/whvFPMoF1hQNxnoVET3DWKPBt1Xgqe2rY0k+Jn7TNhoHlwdnSe7Rlcbo2Nub3Mt2lUVz26X65aDQrWp6x8w==", + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.14.0.tgz", + "integrity": "sha512-or8r7qUqGVI3W8lVSdPh0ZpeFyQHeE73g5c0p+bLNTTUFXJ+GSeDQmZRZ2p4H8cF/RJYa4PNvi/A1ar1uVNLFA==", "dev": true, "requires": { "arrify": "^2.0.0", @@ -479,9 +479,9 @@ } }, "google-gax": { - "version": "2.29.7", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-2.29.7.tgz", - "integrity": "sha512-yC0Q4OqX6s081jV72Idt9sxZnuRHOjg0SR0FFH15gT3LDE2u/78xqLjZwa3VuprBvaOLM29jzU19Vv4I7E8ETQ==", + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-2.30.0.tgz", + "integrity": "sha512-JcZGDuSOzhPwOJfbK80cyyGLZkrlLBTiwfqrW46sC0I9h3FtFmbN7FwIQ3PHreYiE6iVK4InfEZiTp4laOmPfA==", "dev": true, "requires": { "@grpc/grpc-js": "~1.5.0", @@ -490,10 +490,10 @@ "abort-controller": "^3.0.0", "duplexify": "^4.0.0", "fast-text-encoding": "^1.0.3", - "google-auth-library": "^7.6.1", + "google-auth-library": "^7.14.0", "is-stream-ended": "^0.1.4", "node-fetch": "^2.6.1", - "object-hash": "^2.1.1", + "object-hash": "^3.0.0", "proto3-json-serializer": "^0.1.8", "protobufjs": "6.11.2", "retry-request": "^4.0.0" @@ -712,9 +712,9 @@ "dev": true }, "object-hash": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", - "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", "dev": true }, "once": { @@ -775,9 +775,9 @@ }, "dependencies": { "@types/node": { - "version": "17.0.18", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.18.tgz", - "integrity": "sha512-eKj4f/BsN/qcculZiRSujogjvp5O/k4lOW5m35NopjZM/QwLOR075a8pJW5hD+Rtdm2DaCVPENS6KtSQnUD6BA==", + "version": "17.0.21", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz", + "integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==", "dev": true } } From 2d9251aac33f266aae10eb8444b7da3d7030f1a5 Mon Sep 17 00:00:00 2001 From: AlexanderKanakis Date: Thu, 24 Mar 2022 17:35:03 +0200 Subject: [PATCH 8/9] merged blackout functionality --- app.json | 2 +- handler/PostMessageSentHandler.ts | 21 +++++++++++++------- lib/Dialogflow.ts | 32 ++++++++++++++++++------------- lib/EventTimeoutProcessor.ts | 10 ++++++++-- lib/handleStreams.ts | 5 ++--- lib/payloadAction.ts | 4 ++-- 6 files changed, 46 insertions(+), 28 deletions(-) diff --git a/app.json b/app.json index d1c77af..1622ff8 100644 --- a/app.json +++ b/app.json @@ -19,5 +19,5 @@ "IPostLivechatRoomClosed", "IUIKitLivechatInteractionHandler" ], - "commitHash": "0378cb1c7164f0b3becdd4a9cee27e06a3e177c0" + "commitHash": "2332c0c9abada4f27b9a3c3666f99c65883f1f99" } \ No newline at end of file diff --git a/handler/PostMessageSentHandler.ts b/handler/PostMessageSentHandler.ts index 7ca8208..86a1d94 100644 --- a/handler/PostMessageSentHandler.ts +++ b/handler/PostMessageSentHandler.ts @@ -6,12 +6,13 @@ import { AppSetting } from '../config/Settings'; import { DialogflowRequestType, IDialogflowMessage, IDialogflowQuickReplies, LanguageCode, Message } from '../enum/Dialogflow'; import { Logs } from '../enum/Logs'; +import { Global } from '../Global'; import { botTypingListener, removeBotTypingListener } from '../lib//BotTyping'; import { Dialogflow } from '../lib/Dialogflow'; -import { getErrorMessage } from '../lib/Helper'; +import { getError, getErrorMessage } from '../lib/Helper'; import { createDialogflowMessage, createMessage, removeQuotedMessage } from '../lib/Message'; import { handleResponse } from '../lib/payloadAction'; -import { getIsProcessingMessage, getIsQueueWindowActive, getRoomAssoc, retrieveDataByAssociation, setIsProcessingMessage, setQueuedMessage } from '../lib/Persistence'; +import { getIsProcessingMessage, getIsQueueWindowActive, getRoomAssoc, retrieveDataByAssociation, setIsProcessingMessage, setIsQueueWindowActive, setQueuedMessage } from '../lib/Persistence'; import { handleParameters } from '../lib/responseParameters'; import { closeChat, performHandover, updateRoomCustomFields } from '../lib/Room'; import { cancelAllEventSchedulerJobForSession, cancelAllSessionMaintenanceJobForSession } from '../lib/Scheduler'; @@ -126,7 +127,7 @@ export class PostMessageSentHandler { await setIsProcessingMessage(this.read, this.persistence, rid, true); await cancelAllEventSchedulerJobForSession(this.modify, rid); await botTypingListener(this.modify, rid, servedBy.username); - response = (await Dialogflow.sendRequest(this.http, this.read, this.modify, rid, messageText, DialogflowRequestType.MESSAGE)); + response = (await Dialogflow.sendRequest(this.http, this.read, this.modify, this.persistence, rid, messageText, DialogflowRequestType.MESSAGE)); await setIsProcessingMessage(this.read, this.persistence, rid, false); } catch (error) { await setIsProcessingMessage(this.read, this.persistence, rid, false); @@ -198,10 +199,16 @@ export class PostMessageSentHandler { const defaultLanguageCode = await getLivechatAgentConfig(read, rid, AppSetting.DialogflowAgentDefaultLanguage); let res: IDialogflowMessage; - res = (await Dialogflow.sendRequest(this.http, this.read, this.modify, rid, { - name: DialogflowChatClosedByVisitorEventName, - languageCode: data.custom_languageCode || defaultLanguageCode || LanguageCode.EN, - }, DialogflowRequestType.EVENT)); + res = (await Dialogflow.sendRequest(this.http, + this.read, + this.modify, + this.persistence, + rid, + { + name: DialogflowChatClosedByVisitorEventName, + languageCode: data.custom_languageCode || defaultLanguageCode || LanguageCode.EN, + }, + DialogflowRequestType.EVENT)); } catch (error) { const errorContent = `${Logs.DIALOGFLOW_REST_API_ERROR}: { roomID: ${rid} } ${getErrorMessage(error)}`; this.app.getLogger().error(errorContent); diff --git a/lib/Dialogflow.ts b/lib/Dialogflow.ts index c86bfb8..2dd91d2 100644 --- a/lib/Dialogflow.ts +++ b/lib/Dialogflow.ts @@ -12,7 +12,7 @@ import { removeBotTypingListener } from './BotTyping'; import { handlePostPartialResponse } from './handleStreams'; import { base64urlEncode, getError } from './Helper'; import { createHttpRequest } from './Http'; -import { getRoomAssoc, retrieveDataByAssociation, setIsProcessingMessage, updatePersistentData } from './Persistence'; +import { getRoomAssoc, retrieveDataByAssociation, setIsProcessingMessage, setIsQueueWindowActive, updatePersistentData } from './Persistence'; import { updateRoomCustomFields } from './Room'; import { getLivechatAgentConfig } from './Settings'; @@ -22,12 +22,6 @@ const DialogFlowApi = require('@rocket.chat/apps-engine/lib/dialogflow-cx'); class DialogflowClass { private jwtExpiration: Date; - private JSON_SIMPLE_VALUE_KINDS = new Set([ - 'numberValue', - 'stringValue', - 'boolValue', - ]); - public async sendRequest(http: IHttp, read: IRead, modify: IModify, @@ -531,6 +525,7 @@ class DialogflowClass { const response = streamData.detectIntentResponse; if (response) { if (response.responseType === 'PARTIAL') { + await setIsQueueWindowActive(read, persistence, sessionId, true); const parsedResponse = await this.parseCXRequest(read, response, sessionId); await handlePostPartialResponse(Global.app, read, modify, http, persistence, rid, visitorToken, parsedResponse); } else { @@ -545,6 +540,7 @@ class DialogflowClass { } }) .on('end', async () => { + await setIsQueueWindowActive(read, persistence, sessionId, false); if (room.servedBy) { await removeBotTypingListener(modify, rid, room.servedBy.username); } @@ -562,32 +558,42 @@ class DialogflowClass { }); } - private structProtoToJson(proto) { + private structProtoToJson(proto: any) { if (!proto || !proto.fields) { return {}; } const json = {}; for (const k in proto.fields) { if (proto.fields[k]) { + console.log('a'); + console.log(proto.fields[k]); json[k] = this.valueProtoToJson(proto.fields[k]); } } return json; } - private valueProtoToJson(proto) { + private valueProtoToJson(proto: any) { if (!proto || !proto.kind) { return null; } - if (this.JSON_SIMPLE_VALUE_KINDS.has(proto.kind)) { - return proto[proto.kind]; + + const jsonValueKinds = new Set([ + 'numberValue', + 'stringValue', + 'boolValue', + ]); + + if (jsonValueKinds.has(proto.kind)) { + return proto[proto.kind]; } else if (proto.kind === 'nullValue') { - return null; + return null; } else if (proto.kind === 'listValue') { if (!proto.listValue || !proto.listValue.values) { console.warn('Invalid JSON list value proto: ', JSON.stringify(proto)); } - return proto.listValue.values.map(this.valueProtoToJson); + console.log(proto); + return proto.listValue.values.map((value) => (this.structProtoToJson(value))); } else if (proto.kind === 'structValue') { return this.structProtoToJson(proto.structValue); } else { diff --git a/lib/EventTimeoutProcessor.ts b/lib/EventTimeoutProcessor.ts index 5db7531..44c43e5 100644 --- a/lib/EventTimeoutProcessor.ts +++ b/lib/EventTimeoutProcessor.ts @@ -30,7 +30,7 @@ export class EventScheduler implements IProcessor { await setIsQueueWindowActive(read, persistence, sessionId, true); console.debug({rid: sessionId}, `Queue Window started`); - const response = await Dialogflow.sendRequest(http, read, modify, sessionId, event, DialogflowRequestType.EVENT); + const response = await Dialogflow.sendRequest(http, read, modify, persistence, sessionId, event, DialogflowRequestType.EVENT); const livechatRoom = await read.getRoomReader().getById(sessionId) as ILivechatRoom; if (!livechatRoom) { throw new Error(Logs.INVALID_ROOM_ID); } @@ -52,7 +52,13 @@ export class EventScheduler implements IProcessor { // Send Queued Message if (queuedMessage) { try { - const messageResponse = await Dialogflow.sendRequest(http, read, modify, sessionId, queuedMessage, DialogflowRequestType.MESSAGE); + const messageResponse = await Dialogflow.sendRequest(http, + read, + modify, + persistence, + sessionId, + queuedMessage, + DialogflowRequestType.MESSAGE); await handleResponse(Global.app, read, modify, http, persistence, sessionId, visitorToken, messageResponse); } catch (error) { console.error(`${Logs.DIALOGFLOW_REST_API_ERROR}: { roomID: ${sessionId} } ${getError(error)}`); diff --git a/lib/handleStreams.ts b/lib/handleStreams.ts index 50356aa..56be3ed 100644 --- a/lib/handleStreams.ts +++ b/lib/handleStreams.ts @@ -20,7 +20,7 @@ export const handlePostPartialResponse = async (app: IApp, read: IRead, modify return; } - await setIsProcessingMessage(persistence, rid, false); + await setIsProcessingMessage(read, persistence, rid, false); const createResponseMessage = async () => await createDialogflowMessage(rid, read, modify, response, Global.app); // synchronous handover check @@ -55,7 +55,6 @@ export const handlePartialPayloadActions = async (app: IApp, read: IRead, modi const { messages = [] } = dialogflowMessage; for (const message of messages) { const { action = null } = message as IDialogflowPayload; - console.log(action); if (action) { const { name: actionName, params } = action as IDialogflowAction; if (actionName) { @@ -77,7 +76,7 @@ export const handlePartialPayloadActions = async (app: IApp, read: IRead, modi // Start blackout window if (params.continue_blackout) { - await setIsProcessingMessage(persistence, rid, true); + await setIsProcessingMessage(read, persistence, rid, true); } } catch (error) { console.error(error); diff --git a/lib/payloadAction.ts b/lib/payloadAction.ts index f3e2f8d..968b803 100644 --- a/lib/payloadAction.ts +++ b/lib/payloadAction.ts @@ -106,7 +106,7 @@ const sendChangeLanguageEvent = async (app: IApp, read: IRead, modify: IModify, try { const event = { name: 'ChangeLanguage', languageCode, parameters: {} }; - const response: IDialogflowMessage = await Dialogflow.sendRequest(http, read, modify, rid, event, DialogflowRequestType.EVENT); + const response: IDialogflowMessage = await Dialogflow.sendRequest(http, read, modify, persis, rid, event, DialogflowRequestType.EVENT); await createDialogflowMessage(rid, read, modify, response, app); } catch (error) { @@ -158,7 +158,7 @@ export const sendWelcomeEventToDialogFlow = async (app: IApp, read: IRead, modif }; await createMessage(rid, read, modify, { customFields: disableInput }, app); - const response: IDialogflowMessage = await Dialogflow.sendRequest(http, read, modify, rid, event, DialogflowRequestType.EVENT); + const response: IDialogflowMessage = await Dialogflow.sendRequest(http, read, modify, persistence, rid, event, DialogflowRequestType.EVENT); await handleResponse(app, read, modify, http, persistence, rid, visitorToken, response); } catch (error) { console.error(`${Logs.DIALOGFLOW_REST_API_ERROR}: { roomID: ${rid} } ${getError(error)}`); From a9b091cf21bc88e7b4b8d01535c974e36a779986 Mon Sep 17 00:00:00 2001 From: AlexanderKanakis Date: Thu, 24 Mar 2022 17:46:26 +0200 Subject: [PATCH 9/9] added new custom payload for partials --- lib/handleStreams.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/handleStreams.ts b/lib/handleStreams.ts index 56be3ed..e9b4e4d 100644 --- a/lib/handleStreams.ts +++ b/lib/handleStreams.ts @@ -8,7 +8,7 @@ import { JobName } from '../enum/Scheduler'; import { Global } from '../Global'; import { removeBotTypingListener } from './BotTyping'; import { createDialogflowMessage, createMessage } from './Message'; -import { getRoomAssoc, retrieveDataByAssociation, setIsProcessingMessage, updatePersistentData } from './Persistence'; +import { getRoomAssoc, retrieveDataByAssociation, setIsProcessingMessage, setIsQueueWindowActive, setQueuedMessage, updatePersistentData } from './Persistence'; import { getLivechatAgentConfig } from './Settings'; import { incFallbackIntentAndSendResponse } from './SynchronousHandover'; @@ -97,6 +97,11 @@ export const handlePartialPayloadActions = async (app: IApp, read: IRead, modi await persistence.createWithAssociation({custom_languageCode: params.newLanguageCode}, assoc); } } + if (actionName === ActionIds.DROP_QUEUE) { + await setIsQueueWindowActive(read, persistence, rid, false); + await setQueuedMessage(read, persistence, rid, ''); + console.debug({rid}, 'Queue Window dropped'); + } } } }