Skip to content
This repository was archived by the owner on Oct 23, 2023. It is now read-only.

Commit 1eda868

Browse files
author
Matt Carroll
committed
Update context API
Change-Id: I2ac7672fead481c6d97846fb5735a9672761b5cb
1 parent f838362 commit 1eda868

12 files changed

+646
-74
lines changed

.eslintrc.json

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
{
22
"extends": "google",
33
"parserOptions": {
4-
"ecmaVersion": 8
4+
"ecmaVersion": 8,
5+
"ecmaFeatures": {
6+
"experimentalObjectRestSpread": true
7+
}
58
},
69
"rules": {
710
"max-len": ["error", 120]
811
}
9-
}
12+
}

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ coverage
66
*.tgz
77
package-lock.json
88
webhook-client.md
9-
rich-responses.md
9+
rich-responses.md
10+
.nyc_output

README.md

-7
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
# Dialogflow Fulfillment Library
2-
32
The [Dialogflow Fulfillment Library](https://dialogflow.com/docs/fulfillment) allows you to connect natural language understanding and processing to your own systems, APIs, and databases. Using Fulfillment, you can surface commands and information from your services to your users through a natural conversational interface.
43

54
Dialogflow Fulfillment makes creating fulfillment for Dialogflow v1 and v2 agents for 8 chat and voice platforms on Node.js easy and simple.
65

7-
86
![fulfillment library works with 8 platforms](https://raw.githubusercontent.com/dialogflow/dialogflow-fulfillment-nodejs/master/dialogflow-fulfillment-graphic.png "Dialogflow's fulfillment library works with 8 platforms")
97

108
## Supported features
@@ -18,26 +16,21 @@ This library is intended to help build Node.js Dialogflow Fulfillment for multip
1816

1917
If only building Dialogflow Fulfillment for the [Google Assistant](https://dialogflow.com/docs/integrations/google-assistant) and no other integrations, use the Actions of Google NPM module ([actions-on-google](https://www.npmjs.com/package/actions-on-google)) which supports all Actions on Google features.
2018

21-
2219
## Quick Start
23-
2420
1. [Sign-up/Log-in to Dialogflow](https://console.dialogflow.com/api-client/#/login)
2521
2. Create a Dialogflow agent
2622
3. Go to **Fulfillment** > **Enable Dialogflow Inline Editor**<sup> A.</sup> > **package.json** tab to add `"dialogflow-fulfillment": "^0.5.0"` to the `dependencies` object.
2723
4. Select **Deploy**.
28-
2924
<sup>A.</sup> Powered by Cloud Functions for Firebase
3025

3126
## Setup Instructions
32-
3327
```javascript
3428
// Import the appropriate class
3529
const { WebhookClient } = require('dialogflow-fulfillment');
3630

3731
//Create an instance
3832
const agent = new WebhookClient({request: request, response: response});
3933
```
40-
4134
## Samples
4235
| Name | Language |
4336
| ------------------------------------ |:---------------------------------|

generateDocs.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const inputFiles = ['./src/*.js', './src/rich-responses/*.js'];
2424
const outputDir = './docs/';
2525

2626
const webhookFilename = 'webhook-client.md';
27-
const webhookClientClassNames = ['WebhookClient', 'V2Agent', 'V1Agent'];
27+
const webhookClientClassNames = ['WebhookClient', 'V2Agent', 'V1Agent', 'Context'];
2828
const richResponseFilename = 'rich-responses.md'
2929
const richRepsonseClassNames = [ 'RichResponse', 'Card', 'Suggestion', 'Image', 'Payload', 'Text'];
3030

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@
3737
"fulfillment"
3838
],
3939
"dependencies": {
40-
"debug": "^3.1.0"
40+
"debug": "^3.1.0",
41+
"lodash": "^4.17.11"
4142
},
4243
"peerDependencies": {
4344
"actions-on-google": "^2.1.3"

src/contexts.js

+268
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
/**
2+
* Copyright 2017 Google Inc. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
const debug = require('debug')('dialogflow:debug');
18+
const _ = require('lodash');
19+
20+
// Configure logging for hosting platforms that only support console.log and console.error
21+
debug.log = console.log.bind(console);
22+
23+
const DELETED_LIFESPAN_COUNT = 0; // Lifespan of a deleted context
24+
25+
26+
/**
27+
* This is the class that handles Dialogflow's contexts for the WebhookClient class
28+
*/
29+
class Context {
30+
/**
31+
* Constructor for Context object
32+
* To be used in by Dialogflow's webhook client class
33+
* context objects take are formatted as follows:
34+
* { "context-name": {
35+
* "lifespan": 5,
36+
* "parameters": {
37+
* "param": "value"
38+
* }
39+
* }
40+
* }
41+
*
42+
* @example
43+
* const context = new Context(inputContexts);
44+
* context.get('name of context')
45+
* context.set('another context name', 5, {param: 'value'})
46+
* context.delete('name of context') // set context lifespan to 0
47+
*
48+
* @param {Object} inputContexts input contexts of a v1 or v2 webhook request
49+
* @param {string} session for a v2 webhook request & response
50+
*/
51+
constructor(inputContexts, session) {
52+
/**
53+
* Dialogflow contexts included in the request or empty object if no value
54+
* https://dialogflow.com/docs/contexts
55+
* @type {object}
56+
*/
57+
this.contexts = {};
58+
this.session = session;
59+
if (inputContexts && session) {
60+
this.inputContexts = this._processV2InputContexts(inputContexts);
61+
this.contexts = this._processV2InputContexts(inputContexts);
62+
} else if (inputContexts) {
63+
this.contexts = this._processV1InputContexts(inputContexts);
64+
this.inputContexts = this._processV1InputContexts(inputContexts);
65+
}
66+
}
67+
// ---------------------------------------------------------------------------
68+
// Public CRUD methods
69+
// ---------------------------------------------------------------------------
70+
/**
71+
* Set a new Dialogflow outgoing context: https://dialogflow.com/docs/contexts
72+
*
73+
* @example
74+
* const { WebhookClient } = require('dialogflow-webhook');
75+
* const agent = new WebhookClient({request: request, response: response});
76+
* agent.context.set('sample context name');
77+
* const context = {'name': 'weather', 'lifespan': 2, 'parameters': {'city': 'Rome'}};
78+
*
79+
* @param {string|Object} name of context or an object representing a context
80+
* @param {number} [lifespan=5] lifespan of context, number with a value of 0 or greater
81+
* @param {Object} [params] parameters of context (can be arbitrary key-value pairs)
82+
*/
83+
set(name, lifespan, params) {
84+
if (!name || (typeof name !== 'string' && typeof name['name'] !== 'string')) {
85+
throw new Error('Required "name" argument must be a string or an object with a string attribute "name"');
86+
}
87+
if (typeof name !== 'string') {
88+
params = name['parameters'];
89+
lifespan = name['lifespan'];
90+
name = name['name'];
91+
}
92+
if (!this.contexts[name]) {
93+
this.contexts[name] = {name: name};
94+
}
95+
if (lifespan !== undefined && lifespan !== null) {
96+
this.contexts[name].lifespan = lifespan;
97+
}
98+
if (params !== undefined) {
99+
this.contexts[name].parameters = params;
100+
}
101+
}
102+
103+
/**
104+
* Get an context from the Dialogflow webhook request: https://dialogflow.com/docs/contexts
105+
*
106+
* @example
107+
* const { WebhookClient } = require('dialogflow-webhook');
108+
* const agent = new WebhookClient({request: request, response: response});
109+
* let context = agent.context.get('sample context name');
110+
*
111+
* @param {string} name of an context present in the Dialogflow webhook request
112+
* @return {Object|null} context object with lifespan and parameters (if defined) or null
113+
*/
114+
get(name) {
115+
return this.contexts[name];
116+
}
117+
/**
118+
* Delete an context a Dialogflow session (set the lifespan to 0)
119+
*
120+
* @example
121+
* const { WebhookClient } = require('dialogflow-webhook');
122+
* const agent = new WebhookClient({request: request, response: response});
123+
* agent.context.delete('no-longer-relevant-context-name');
124+
*
125+
* @param {string} name of context to be deleted
126+
*
127+
* @public
128+
*/
129+
delete(name) {
130+
this.set(name, DELETED_LIFESPAN_COUNT);
131+
}
132+
/**
133+
* Returns contexts as an iterator.
134+
*
135+
* @example
136+
* const { WebhookClient } = require('dialogflow-webhook');
137+
* const agent = new WebhookClient({request: request, response: response});
138+
* for (const context of agent.context) {
139+
* // do something with the contexts
140+
* }
141+
*
142+
* @return {iterator} iterator of all context objects
143+
* @public
144+
*/
145+
[Symbol.iterator]() {
146+
let contextArray = [];
147+
for (const contextName of Object.keys(this.contexts)) {
148+
contextArray.push(this.contexts[contextName]);
149+
}
150+
return contextArray[Symbol.iterator]();
151+
// suppose to be Array.prototype.values(), but can't use because of bug:
152+
// https://bugs.chromium.org/p/chromium/issues/detail?id=615873
153+
}
154+
// ---------------------------------------------------------------------------
155+
// Private methods
156+
// ---------------------------------------------------------------------------
157+
/**
158+
* Remove an context from Dialogflow's outgoing context webhook response
159+
* used to maintain compatibility with legacy clearContext methods
160+
*
161+
* @example
162+
* const { WebhookClient } = require('dialogflow-webhook');
163+
* const agent = new WebhookClient({request: request, response: response});
164+
* agent.context._removeOutgoingContext('no-longer-sent-context-name');
165+
*
166+
* @param {string} name of context to be removed from outgoing contexts
167+
*
168+
* @private
169+
*/
170+
_removeOutgoingContext(name) {
171+
delete this.contexts[name];
172+
}
173+
// ---------------------------------------------------------------------------
174+
// Private v2 <--> v1 translation methods
175+
// ---------------------------------------------------------------------------
176+
/**
177+
* Translate context object from v1 webhook request format to class format
178+
*
179+
* @param {Array} v1InputContexts to be used by the Contexts class
180+
*
181+
* @return {Object} internal representation of contexts
182+
* @private
183+
*/
184+
_processV1InputContexts(v1InputContexts) {
185+
let contexts = {};
186+
for (let index = 0; index<v1InputContexts.length; index++) {
187+
const context = v1InputContexts[index];
188+
contexts[context['name']] = {
189+
name: context['name'],
190+
parameters: context['parameters'],
191+
lifespan: context['lifespan'],
192+
};
193+
}
194+
return contexts;
195+
}
196+
/**
197+
* Translate context object from v2 webhook request format to class format
198+
*
199+
* @param {Array} v2InputContexts to be used by the Contexts class
200+
*
201+
* @return {Object} internal representation of contexts
202+
* @private
203+
*/
204+
_processV2InputContexts(v2InputContexts) {
205+
let contexts = {};
206+
for (let index = 0; index<v2InputContexts.length; index++) {
207+
let context = v2InputContexts[index];
208+
const name = context['name'].split('/')[6];
209+
contexts[name] = {
210+
name: name,
211+
lifespan: context['lifespanCount'],
212+
parameters: context['parameters']};
213+
}
214+
return contexts;
215+
}
216+
/**
217+
* Get array of context objects formatted for v1 webhook response
218+
*
219+
* @return {Object[]} array of v1 context objects for webhook response
220+
*/
221+
getV1OutputContextsArray() {
222+
let v1OutputContexts = [];
223+
for (const ctx of this) {
224+
// Skip context if it is the same as the input context
225+
if (this.inputContexts &&
226+
this.inputContexts[ctx.name] &&
227+
_.isEqual(ctx, this.inputContexts[ctx.name])) {
228+
continue;
229+
}
230+
let v1Context = {name: ctx.name};
231+
if (ctx.lifespan !== undefined) {
232+
v1Context['lifespan'] = ctx.lifespan;
233+
}
234+
if (ctx.parameters) {
235+
v1Context['parameters'] = ctx.parameters;
236+
}
237+
v1OutputContexts.push(v1Context);
238+
}
239+
return v1OutputContexts;
240+
}
241+
/**
242+
* Get array of context objects formatted for v2 webhook response
243+
*
244+
* @return {Object[]} array of v2 context objects for webhook response
245+
*/
246+
getV2OutputContextsArray() {
247+
let v2OutputContexts = [];
248+
for (const ctx of this) {
249+
// Skip context if it is the same as the input context
250+
if (this.inputContexts &&
251+
this.inputContexts[ctx.name] &&
252+
_.isEqual(ctx, this.inputContexts[ctx.name])) {
253+
continue;
254+
}
255+
let v2Context = {name: `${this.session}/contexts/${ctx.name}`};
256+
if (ctx.lifespan !== undefined) {
257+
v2Context['lifespanCount'] = ctx.lifespan;
258+
}
259+
if (ctx.parameters) {
260+
v2Context['parameters'] = ctx.parameters;
261+
}
262+
v2OutputContexts.push(v2Context);
263+
}
264+
return v2OutputContexts;
265+
}
266+
}
267+
268+
module.exports = Context;

0 commit comments

Comments
 (0)