Actions on Google: How To Implement Database & User Entities (Session) using NodeJS - actions-on-google

I'm looking to have a database which contain the devices data that each user has. When the user connects to the Node server, it will somehow retrieve all the devices names and return it to AoG for the NLB engine recognise those names.
How would I go about implementing this?
My current code is attached below (built from sample code from Google):
'use strict';
const util = require('util');
const functions = require('firebase-functions');
const {
dialogflow,
Suggestions,
BasicCard,
Button,
SimpleResponse,
} = require('actions-on-google');
const {values, concat, random, randomPop} = require('./util');
const responses = require('./responses');
/** Dialogflow Contexts {#link https://dialogflow.com/docs/contexts} */
const AppContexts = {
FACT: 'choose_fact-followup',
CATS: 'choose_cats-followup',
XXX: 'choose_xxx-followup',
};
/** Dialogflow Context Lifespans {#link https://dialogflow.com/docs/contexts#lifespan} */
const Lifespans = {
DEFAULT: 5,
};
const app = dialogflow({
debug: true,
init: () => ({
data: {
// Convert array of facts to map
facts: responses.categories.reduce((o, c) => {
o[c.category] = c.facts.slice();
return o;
}, {}),
cats: responses.cats.facts.slice(), // copy cat facts
},
}),
});
/**
* Greet the user and direct them to next turn
* #param {DialogflowConversation} conv DialogflowConversation instance
* #return {void}
*/
app.intent('Unrecognized Deep Link Fallback', (conv) => {
const response = util.format(responses.general.unhandled, conv.query);
const suggestions = responses.categories.map((c) => c.suggestion);
conv.ask(response, new Suggestions(suggestions));
});
// redirect to the intent handler for tell_fact
app.intent('choose_fact', 'tell_fact');
// Say a fact
app.intent('tell_fact', (conv, {category}) => {
const {facts, cats, xxx} = conv.data;
if (values(facts).every((c) => !c.length)) {
// If every fact category facts stored in conv.data is empty,
// close the conversation
return conv.close(responses.general.heardItAll);
}
const categoryResponse =
responses.categories.find((c) => c.category === category);
const fact = randomPop(facts[categoryResponse.category]);
if (!fact) {
const otherCategory =
responses.categories.find((other) => other !== categoryResponse);
const redirect = otherCategory.category;
const parameters = {
category: redirect,
};
// Add facts context to outgoing context list
conv.contexts.set(AppContexts.FACT, Lifespans.DEFAULT, parameters);
const response = [
util.format(responses.transitions.content.heardItAll, category, redirect),
];
// If cat facts not loaded or there still are cat facts left
if (cats.length) {
response.push(responses.transitions.content.alsoCats);
}
response.push(responses.general.wantWhat);
conv.ask(concat(...response));
conv.ask(new Suggestions(otherCategory.suggestion));
if (cats.length) {
conv.ask(new Suggestions(responses.cats.suggestion));
}
return;
}
const {factPrefix} = categoryResponse;
// conv.ask can be called multiple times to have the library construct
// a single response itself the response will get sent at the end of
// the function or if the function returns a promise, after the promise
// is resolved.
conv.ask(new SimpleResponse({
speech: concat(factPrefix, fact),
text: factPrefix,
}));
conv.ask(responses.general.nextFact);
conv.ask(new BasicCard({
title: fact,
image: random(responses.content.images),
buttons: new Button({
title: responses.general.linkOut,
url: responses.content.link,
}),
}));
console.log('hiwwxxxxxww thi is aaron');
conv.ask(responses.general.suggestions.confirmation);
});
// Redirect to the intent handler for tell_cat_fact
app.intent('choose_cats', 'tell_cat_fact');
// Say a cat fact
app.intent('tell_cat_fact', (conv) => {
const {cats} = conv.data;
console.log('this is cats data' + {cats});
const fact = randomPop(cats);
if (!fact) {
conv.contexts.delete(AppContexts.FACT);
conv.contexts.delete(AppContexts.CATS);
conv.ask(responses.transitions.cats.heardItAll);
return conv.ask(responses.general.suggestions.confirmation);
}
const {factPrefix, audio} = responses.cats;
// conv.ask can be called multiple times to have the library construct
// a single response itself. The response will get sent at the end of
// the function or if the function returns a promise, after the promise
// is resolved.
const sound = util.format(audio, random(responses.cats.sounds));
conv.ask(new SimpleResponse({
// <speak></speak> is needed here since factPrefix is a SSML string
// and contains audio.
speech: `<speak>${concat(factPrefix, sound, fact)}</speak>`,
text: factPrefix,
}));
conv.ask(responses.general.nextFact);
conv.ask(new BasicCard({
title: fact,
image: random(responses.cats.images),
buttons: new Button({
title: responses.general.linkOut,
url: responses.cats.link,
}),
}));
console.log('hiwwxxxxxww thi is aaron');
conv.ask(responses.general.suggestions.confirmation);
});
//say a tv channel
app.intent('volume', (conv, {device_name,device_action, value}) => {
var no_device_name = device_name;
var no_value = value;
var no_device_action = device_action;
var this_device_value = util.inspect(value, false, null);
var this_device_name = util.inspect(device_name, false, null);
var this_device_action = util.inspect(device_action, false, null);
console.log(this_device_action[0]);
if (no_device_name[0] == 'channel'){
console.log('inside tv but CHANNEL');
}
else{
console.log('VOLUME');
}
console.log('THIS IS VOL');
console.log(no_device_action[0]);
conv.ask(`Alright, ${device_name} VOLUM is now ${value}! `);
console.log('inside volume ' + value[0]);
console.log('inside volume' + device_name[0]);
console.log('inside volume' + device_action[0]);
console.log('hiwwxxxxxww thi is aaron');
});
//say a tv channel
app.intent('channel', (conv, {device_channel_name, device_channel_action, channel_value}) => {
console.log('THIS IS CHANNEL');
conv.ask(`Alright, ${device_channel_name} CHANNEL is now ${channel_value}! `);
console.log('inside CHANNEL ' + channel_value[0]);
console.log('inside CHANNEL' + device_channel_name[0]);
console.log('inside CHANNEL' + device_channel_action[0]);
console.log('hiwwxxxxxww thi is aaron');
});
app.intent('no_input', (conv) => {
const repromptCount = parseInt(conv.arguments.get('REPROMPT_COUNT'));
if (repromptCount === 0) {
conv.ask(`What was that?`);
} else if (repromptCount === 1) {
conv.ask(`Sorry I didn't catch that. Could you repeat yourself?`);
} else if (conv.arguments.get('IS_FINAL_REPROMPT')) {
conv.close(`Okay let's try this again later.`);
}
});
// The entry point to handle a http request
exports.factsAboutGoogle = functions.https.onRequest(app);

conv.user.storage is supposed to persist for up to 30 days of non-use, indefinitely for regular use, and pretty much linked to the relevant 'Google account', across devices. Note the user can also FORCIBLY disassociate themselves from the data manually (via the assistant app, if they dig into options to find your app).
All that as described here at the time of writing:
https://developers.google.com/actions/identity/user-info
conv.user.id seems to remain constant similarly - So could be used to 'key' into an external database if you want to do that.
But you might as well just use userStorage - It seems to have the same lifespan and reliability as the user.id key anyway, and saves you hassle/latency of hitting a DB.
As far as I can tell, if you want a more reliable mechanism than this, then Account Linking is the only other way to go. I will leave you to dig into that yourself...

Related

How do I make 2 (or more) calls with Adobe PDF Services and skip using the file system (in between?)

It's fairly simple to make one call to Adobe PDF Services, get the result, and save it, for example:
// more stuff above
exportPdfOperation.execute(executionContext)
.then(result => result.saveAsFile(output))
But if I want to do two, or more, operations, do I need to keep saving the result to the file system and re-providing it (is that even a word ;) to the API?
So this tripped me up as well. In most demos, you'll see:
result => result.saveAsFile()
towards the end. However, the object passes to the completed promise, result, is a FileRef object that can then be used as the input to another call.
Here's a sample that takes an input Word doc and calls the API method to create a PDF. It then takes that and runs OCR on it. Both methods that wrap the API calls return FileRefs, so at the end I saveAsFile on it. (Note, this demo is using v1 of the SDK, it would work the same w/ v2.)
const PDFToolsSdk = require('#adobe/documentservices-pdftools-node-sdk');
const fs = require('fs');
//clean up previous
(async ()=> {
// hamlet.docx was too big for conversion
const input = './hamlet2.docx';
const output = './multi.pdf';
const creds = './pdftools-api-credentials.json';
if(fs.existsSync(output)) fs.unlinkSync(output);
let result = await createPDF(input, creds);
console.log('got a result');
result = await ocrPDF(result, creds);
console.log('got second result');
await result.saveAsFile(output);
})();
async function createPDF(source, creds) {
return new Promise((resolve, reject) => {
const credentials = PDFToolsSdk.Credentials
.serviceAccountCredentialsBuilder()
.fromFile(creds)
.build();
const executionContext = PDFToolsSdk.ExecutionContext.create(credentials),
createPdfOperation = PDFToolsSdk.CreatePDF.Operation.createNew();
// Set operation input from a source file
const input = PDFToolsSdk.FileRef.createFromLocalFile(source);
createPdfOperation.setInput(input);
let stream = new Stream.Writable();
stream.write = function() {
}
stream.end = function() {
console.log('end called');
resolve(stream);
}
// Execute the operation and Save the result to the specified location.
createPdfOperation.execute(executionContext)
.then(result => resolve(result))
.catch(err => {
if(err instanceof PDFToolsSdk.Error.ServiceApiError
|| err instanceof PDFToolsSdk.Error.ServiceUsageError) {
reject(err);
} else {
reject(err);
}
});
});
}
async function ocrPDF(source, creds) {
return new Promise((resolve, reject) => {
const credentials = PDFToolsSdk.Credentials
.serviceAccountCredentialsBuilder()
.fromFile(creds)
.build();
const executionContext = PDFToolsSdk.ExecutionContext.create(credentials),
ocrOperation = PDFToolsSdk.OCR.Operation.createNew();
// Set operation input from a source file.
//const input = PDFToolsSdk.FileRef.createFromStream(source);
ocrOperation.setInput(source);
let stream = new Stream.Writable();
stream.end = function() {
console.log('end called');
resolve(stream);
}
// Execute the operation and Save the result to the specified location.
ocrOperation.execute(executionContext)
.then(result => resolve(result))
.catch(err => reject(err));
});
}

How to properly use jasmine-marbles to test multiple actions in ofType

I have an Effect that is called each time it recives an action of more than one "kind"
myEffect.effect.ts
someEffect$ = createEffect(() =>
this.actions$.pipe(
ofType(fromActions.actionOne, fromActions.actionTwo),
exhaustMap(() => {
return this.myService.getSomeDataViaHTTP().pipe(
map((data) =>
fromActions.successAction({ payload: data})
),
catchError((err) =>
ObservableOf(fromActions.failAction({ payload: err }))
)
);
})
)
);
in my test I tried to "simulate the two different actions but I always end up with an error, while if I try with one single action it works perfectly
The Before Each part
describe('MyEffect', () => {
let actions$: Observable<Action>;
let effects: MyEffect;
let userServiceSpy: jasmine.SpyObj<MyService>;
const data = {
// Some data structure
};
beforeEach(() => {
const spy = jasmine.createSpyObj('MyService', [
'getSomeDataViaHTTP',
]);
TestBed.configureTestingModule({
providers: [
MyEffect,
provideMockActions(() => actions$),
{
provide: MyService,
useValue: spy,
},
],
});
effects = TestBed.get(MyEffect);
userServiceSpy = TestBed.get(MyService);
});
This works perfectly
it('should return successActionsuccessAction', () => {
const action = actionOne();
const outcome = successAction({ payload: data });
actions$ = hot('-a', { a: action });
const response = cold('-a|', { a: data });
userServiceSpy.getSomeDataViaHTTP.and.returnValue(response);
const expected = cold('--b', { b: outcome });
expect(effects.someEffect$).toBeObservable(expected);
});
This doesn't work
it('should return successAction', () => {
const actions = [actionOne(), actionTwo()];
const outcome = successAction({ payload: data });
actions$ = hot('-a-b', { a: actions[0], b: actions[1] });
const response = cold('-a-a', { a: data });
userServiceSpy.getSomeDataViaHTTP.and.returnValue(response);
const expected = cold('--b--b', { b: outcome });
expect(effects.someEffect$).toBeObservable(expected);
});
There are two problems in this code.
It suggests that getSomeDataViaHTTP returns two values. This is wrong, the response is no different from your first example: '-a|'
It expects the second successAction to appear after 40 ms (--b--b, count the number of dashes). This is not correct, because actionTwo happens after 20 ms (-a-a) and response takes another 10 ms (-a). So the first successAction is after 20ms (10+10), the second is after 30ms (20+10). The marble is: '--b-b'.
Input actions : -a -a
1st http response : -a
2nd http response : -a
Output actions : --b -b
The working code:
it('should return successAction', () => {
const actions = [actionOne(), actionTwo()];
actions$ = hot('-a-b', { a: actions[0], b: actions[1] });
const response = cold('-a|', { a: data });
userServiceSpy.getSomeDataViaHTTP.and.returnValue(response);
const outcome = successAction({ payload: data });
const expected = cold('--b-b', { b: outcome });
expect(effects.someEffect$).toBeObservable(expected);
});
Marble testing is cool but it involves some black magic you should prepare for. I'd very much recommend you to carefully read this excellent article to have a deeper understanding of the subject.

Puppeteer and express can not load new data using REST API

I'm using puppeteer to scrape page that has contents that change periodically and use express to present data in rest api.
If I turn on headless chrome to see what is being shown in the browser, the new data is there, but the data is not showing up in get() and http://localhost:3005/api-weather. The normal browser only shows the original data.
const express = require('express');
const server = new express();
const cors = require('cors');
const morgan = require('morgan');
const puppeteer = require('puppeteer');
server.use(morgan('combined'));
server.use(
cors({
allowHeaders: ['sessionId', 'Content-Type'],
exposedHeaders: ['sessionId'],
origin: '*',
methods: 'GET, HEAD, PUT, PATCH, POST, DELETE',
preflightContinue: false
})
);
const WEATHER_URL = 'https://forecast.weather.gov/MapClick.php?lat=40.793588904953985&lon=-73.95738513173298';
const hazard_url2 = `file://C:/Users/xdevtran/Documents/vshome/wc_api/weather-forecast-nohazard.html`;
(async () => {
try {
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
await page.setRequestInterception(true);
page.on("request", request => {
console.log(request.url());
request.continue();
});
await page.goto(hazard_url2, { timeout: 0, waitUntil: 'networkidle0' });
hazard = {
"HazardTitle": "stub",
"Hazardhref": "stub"
}
let forecast = await page.evaluate(() => {
try {
let forecasts = document.querySelectorAll("#detailed-forecast-body.panel-body")[0].children;
let weather = [];
for (var i = 0, element; element = forecasts[i]; i++) {
period = element.querySelector("div.forecast-label").textContent;
forecast = element.querySelector("div.forecast-text").textContent;
weather.push(
{
period,
forecast
}
)
}
return weather;
} catch (err) {
console.log('error in evaluate: ', err);
res.end();
}
}).catch(err => {
console.log('err.message :', err.message);
});
weather = forecast;
server.get('/api-weather', (req, res) => {
try {
res.end(JSON.stringify(weather, null, ' '));
console.log(weather);
} catch (err) {
console.log('failure: ', err);
res.sendStatus(500);
res.end();
return;
}
});
} catch (err) {
console.log('caught error :', err);
}
browser.close();
})();
server.listen(3005, () => {
console.log('http://localhost:3005/api-weather');
});
I've tried several solutions WaitUntil, WaitFor, .then and sleep but nothing seems to work.
I wonder if it has something to do with express get()? I'm using res.end() instead of res.send() is because the json looks better when I use res.end(). I don't really know the distinction.
I'm also open to using this reload solution. But I received errors and didn't use it.
I also tried waitForNavigation(), but I don't know how it works, either.
Maybe I'm using the wrong search term to find the solution. Could anyone point me in the right direction? Thank you.

How can I send and receive a signal when a file loads in my GJS app?

I have an app that needs to open a file and update UI elements accordingly. I can select and open the file (and log the file contents), but I can't tell the UI elements to update.
I have tried read that I can create and add signals to just about any object, but I need to emit a signal from a function in an imported library.
I'm trying to do something like this:
(in my function that has read a file from disk)
try {
Signals.addSignalMethods(this);
this.emit('update_ui', true);
} catch(e) {
print(e);
}
(and in the main app class)
Signals.addSignalMethods(this);
this.connect('update_ui',() => {
try {
print('>>> updating UI');
this.ui.updateUI();
} catch (e) {
print(e);
}
});
I don't get any errors when I run the app, but the update function is never called.
How can I get the signal to go through?
Here's the code from the main.js file that should catch the signal :
#!/usr/bin/gjs
Gio = imports.gi.Gio;
GLib = imports.gi.GLib;
Gtk = imports.gi.Gtk;
Lang = imports.lang;
Webkit = imports.gi.WebKit2;
Signals = imports.signals;
GObject = imports.gi.GObject;
Pango = imports.gi.Pango;
//
// add app folder to path
//
function getAppFileInfo() {
let stack = (new Error()).stack,
stackLine = stack.split('\n')[1],
coincidence, path, file;
if (!stackLine) throw new Error('Could not find current file (1)');
coincidence = new RegExp('#(.+):\\d+').exec(stackLine);
if (!coincidence) throw new Error('Could not find current file (2)');
path = coincidence[1];
file = Gio.File.new_for_path(path);
return [file.get_path(), file.get_parent().get_path(), file.get_basename()];
}
const path = getAppFileInfo()[1];
imports.searchPath.push(path);
const myApp = new Lang.Class({
Name: 'My Application',
// Create the application itself
_init: function() {
this.application = new Gtk.Application();
// Connect 'activate' and 'startup' signals to the callback functions
this.application.connect('activate', Lang.bind(this, this._onActivate));
this.application.connect('startup', Lang.bind(this, this._onStartup));
},
// Callback function for 'activate' signal presents windows when active
_onActivate: function() {
this._window.present();
},
// Callback function for 'startup' signal builds the UI
_onStartup: function() {
this._buildUI();
},
// Build the application's UI
_buildUI: function() {
// Create the application window
this._window = new Gtk.ApplicationWindow({
application: this.application,
title: "My App",
default_height: 200,
default_width: 400,
window_position: Gtk.WindowPosition.CENTER
});
//
// menu bar
//
const Menubar = imports.lib.menubar;
this._window.set_titlebar(Menubar.getHeader());
Signals.addSignalMethods(this);
this.connect('update_ui', () => {
try {
print('>>> updating UI');
//this.ui.updateUI();
} catch (e) {
print(e);
}
});
// Vbox to hold the switcher and stack.
this._Vbox = new Gtk.VBox({
spacing: 6
});
this._Hbox = new Gtk.HBox({
spacing: 6,
homogeneous: true
});
// const UI = imports.UI.UI;
// this.ui = new UI.UIstack();
// this.ui._buildStack();
// this._Hbox.pack_start(this.ui._stack_switcher, true, true, 0);
this._Vbox.pack_start(this._Hbox, false, false, 0);
// this._Vbox.pack_start(this.ui._Stack, true, true, 0);
// Show the vbox widget
this._window.add(this._Vbox);
// Show the window and all child widgets
this._window.show_all();
},
});
// Run the application
const app = new myApp();
app.application.run(ARGV);
and here's the header bar file that emits the signal:
const GLib = imports.gi.GLib;
const Gtk = imports.gi.Gtk;
const File = imports.lib.file;
const PopWidget = function(properties) {
let label = new Gtk.Label({
label: properties.label
});
let image = new Gtk.Image({
icon_name: 'pan-down-symbolic',
icon_size: Gtk.IconSize.SMALL_TOOLBAR
});
let widget = new Gtk.Grid();
widget.attach(label, 0, 0, 1, 1);
widget.attach(image, 1, 0, 1, 1);
this.pop = new Gtk.Popover();
this.button = new Gtk.ToggleButton();
this.button.add(widget);
this.button.connect('clicked', () => {
if (this.button.get_active()) {
this.pop.show_all();
}
});
this.pop.connect('closed', () => {
if (this.button.get_active()) {
this.button.set_active(false);
}
});
this.pop.set_relative_to(this.button);
this.pop.set_size_request(-1, -1);
this.pop.set_border_width(8);
this.pop.add(properties.widget);
};
const getHeader = function() {
let headerBar, headerStart, imageNew, buttonNew, popMenu, imageMenu, buttonMenu;
headerBar = new Gtk.HeaderBar();
headerBar.set_title("My App");
headerBar.set_subtitle("Some subtitle text here");
headerBar.set_show_close_button(true);
headerStart = new Gtk.Grid({
column_spacing: headerBar.spacing
});
// this.widgetOpen = new PopWidget({ label: "Open", widget: this.getPopOpen() });
imageNew = new Gtk.Image({
icon_name: 'document-open-symbolic',
icon_size: Gtk.IconSize.SMALL_TOOLBAR
});
buttonNew = new Gtk.Button({
image: imageNew
});
buttonNew.connect('clicked', () => {
const opener = new Gtk.FileChooserDialog({
title: 'Select a file'
});
opener.set_action(Gtk.FileChooserAction.OPEN);
opener.add_button('open', Gtk.ResponseType.ACCEPT);
opener.add_button('cancel', Gtk.ResponseType.CANCEL);
const res = opener.run();
if (res == Gtk.ResponseType.ACCEPT) {
const filename = opener.get_filename();
print(filename);
const fileData = File.open(filename);
print(JSON.stringify(fileData, null, 2));
File.unRoll(fileData);
//
// SHOULD SEND A SIGNAL
//
try {
Signals.addSignalMethods(this);
this.emit('update_ui', true);
} catch (e) {
print(e);
}
// this._window.ui.updateUI();
}
opener.destroy();
});
// headerStart.attach(this.widgetOpen.button, 0, 0, 1, 1);
headerStart.attach(buttonNew, 1, 0, 1, 1);
headerBar.pack_start(headerStart);
popMenu = new Gtk.Popover();
imageMenu = new Gtk.Image({
icon_name: 'document-save-symbolic',
icon_size: Gtk.IconSize.SMALL_TOOLBAR
});
buttonMenu = new Gtk.MenuButton({
image: imageMenu
});
buttonMenu.set_popover(popMenu);
popMenu.set_size_request(-1, -1);
buttonMenu.set_menu_model(this.getMenu());
headerBar.pack_end(buttonMenu);
return headerBar;
};
const getPopOpen = function() {
/* Widget popover */
let widget = new Gtk.Grid(),
label = new Gtk.Label({
label: "Label 1"
}),
button = new Gtk.Button({
label: "Other Documents ..."
});
button.connect('clicked', () => {
this.widgetOpen.pop.hide();
this.printText('Open other documents');
});
button.set_size_request(200, -1);
widget.attach(label, 0, 0, 1, 1);
widget.attach(button, 0, 1, 1, 1);
widget.set_halign(Gtk.Align.CENTER);
return widget;
};
const getMenu = function() {
/* GMenu popover */
let menu, section, submenu;
menu = new Gio.Menu();
section = new Gio.Menu();
section.append("Save As...", 'app.saveAs');
section.append("Save All", 'app.saveAll');
menu.append_section(null, section);
section = new Gio.Menu();
submenu = new Gio.Menu();
section.append_submenu('View', submenu);
submenu.append("View something", 'app.toggle');
submenu = new Gio.Menu();
section.append_submenu('Select', submenu);
submenu.append("Selection 1", 'app.select::one');
submenu.append("Selection 2", 'app.select::two');
submenu.append("Selection 3", 'app.select::thr');
menu.append_section(null, section);
section = new Gio.Menu();
section.append("Close All", 'app.close1');
section.append("Close", 'app.close2');
menu.append_section(null, section);
// Set menu actions
let actionSaveAs = new Gio.SimpleAction({
name: 'saveAs'
});
actionSaveAs.connect('activate', () => {
const saver = new Gtk.FileChooserDialog({
title: 'Select a destination'
});
saver.set_action(Gtk.FileChooserAction.SAVE);
saver.add_button('save', Gtk.ResponseType.ACCEPT);
saver.add_button('cancel', Gtk.ResponseType.CANCEL);
const res = saver.run();
if (res == Gtk.ResponseType.ACCEPT) {
const filename = saver.get_filename();
print(filename);
const data = File.rollUp();
File.save(filename, data);
// let data = JSON.stringify(<FILE DATA>, null, '\t');
// GLib.file_set_contents(filename, data);
}
saver.destroy();
});
APP.add_action(actionSaveAs);
let actionSaveAll = new Gio.SimpleAction({
name: 'saveAll'
});
actionSaveAll.connect('activate', () => {
Gtk.FileChooserAction.OPEN
});
APP.add_action(actionSaveAll);
let actionClose1 = new Gio.SimpleAction({
name: 'close1'
});
actionClose1.connect('activate', () => {
this.printText('Action close all');
});
APP.add_action(actionClose1);
let actionClose2 = new Gio.SimpleAction({
name: 'close2'
});
actionClose2.connect('activate', () => {
this.printText('Action close');
});
APP.add_action(actionClose2);
let actionToggle = new Gio.SimpleAction({
name: 'toggle',
state: new GLib.Variant('b', true)
});
actionToggle.connect('activate', (action) => {
let state = action.get_state().get_boolean();
if (state) {
action.set_state(new GLib.Variant('b', false));
} else {
action.set_state(new GLib.Variant('b', true));
}
this.printText('View ' + state);
});
APP.add_action(actionToggle);
let variant = new GLib.Variant('s', 'one');
let actionSelect = new Gio.SimpleAction({
name: 'select',
state: variant,
parameter_type: variant.get_type()
});
actionSelect.connect('activate', (action, parameter) => {
let str = parameter.get_string()[0];
if (str === 'one') {
action.set_state(new GLib.Variant('s', 'one'));
}
if (str === 'two') {
action.set_state(new GLib.Variant('s', 'two'));
}
if (str === 'thr') {
action.set_state(new GLib.Variant('s', 'thr'));
}
this.printText('Selection ' + str);
});
APP.add_action(actionSelect);
return menu;
};
it's not a class, just a static library... it's imported by the main.js file, so I would think the scope would be the same, but maybe not...
I'm going to over-answer while I get to your question again, because I see some difficulties and some "best practices" you could be using. I think it's much preferrable to use a direct subclass of GtkApplication and use ES6 classes.
const Gio = imports.gi.Gio;
const GObject = imports.gi.GObject;
const Gtk = imports.gi.Gtk;
var MyApp = GObject.registerClass({
// This must be unique, although I don't believe you have to specify it
GTypeName: 'MyApp',
// GObject already have their own signal system, so invoking addSignalMethods()
// will override the existing methods and could cause you problems. Here's a
// simple example of how you'd add a GObject signal to a subclass; the resulting
// callback would look like:
//
// myapp.connect('update-ui', (application, bool) => { ... });
Signals: {
'update-ui': {
param_types: [GObject.TYPE_BOOLEAN]
},
}
}, class MyApp extends Gtk.Application {
_init(params) {
// You could pass the regular params straight through
//super._init(params);
// Or setup your class to be initted without having to pass any args in your
// constructor
super._init({
// This will also become your well-known name on DBus, with the matching
// object path of /org/github/username/MyApp.
//
// GApplication framework will also use these for other things like any
// GResource you bundle with your application.
application_id: 'com.github.username.MyApp',
// There are other flags you can set to allow your app to be a target for
// "Open with...", but that's not what you're asking about today :)
flags: Gio.ApplicationFlags.HANDLES_OPEN
});
// You can also do other setup , however it's important to note that
// GApplication's are generally "single-instance" processes, so you want most
// of that setup in ::startup which will only run if this is the primary
// instance.
//
// On the other hand if you pass HANDLES_OPEN, for example, that signal/vfunc
// will still be invoked.
GLib.set_prgname(this.application_id);
GLib.set_application_name('MyApp');
}
_buildUI() {
// ...
}
// defining a virtual function usually means overriding the default handler for a
// a signal. In this case, it's not much of an issue but for frequently emitted
// signals it avoids "marshalling" the C values into JS values and back again.
//
// In other words, defining this function means it will be called as if you
// connected to the ::activate signal.
vfunc_activate() {
this._window.present();
}
// ::activate is a special case, but for most vfunc's is necessary to "chain up",
// which really just means calling the super class's original function inside your
// override.
vfunc_startup() {
// In ::startup we need to chain up first since GApplication does important
// setup checks here
super.vfunc_startup();
// your stuff after
this._buildUI();
// In _buildUI() you correctly passed your GtkApplication as the application
// property, which will keep the application running so long as that window
// is open.
//
// If you wanted your application to stay open regardless, you call hold()
this.hold();
}
vfunc_shutdown() {
// ::shutdown on the other hand is the reverse; first we do our stuff, then
// chain up to super class. This is a good time to do any cleanup you need
// before the process exits.
this._destroyUI();
// chain up
super.vfunc_shutdown();
}
});
// For technical reasons, this is the proper way you should start your application
(new MyApp()).run([imports.system.programInvocationName].concat(ARGV));
The addSignalMethods() function only needs to be called once, but as mentioned it will override the existing signal methods and that could cause you problems if you call it on a GObject that already has signals defined. It basically does this:
addSignalMethods(obj) {
obj.emit = function() {
// custom signal code
}
...
It was really meant for pure JS classes that don't have a signal system already.
// SHOULD SEND A SIGNAL
//
try {
Signals.addSignalMethods(this);
this.emit('update_ui', true);
} catch (e) {
print(e);
}
The reason why this wasn't working for you is that getHeaderbar() is a top-level function in imports.lib.menubar so when you called addSignalMethods(this), this === imports.lib.menubar. Therefore to catch that signal you would had to call:
imports.lib.menubar.connect('update_ui', () => {});
When you called it in _buildUI():
Signals.addSignalMethods(this);
this.connect('update_ui', () => {
try {
print('>>> updating UI');
//this.ui.updateUI();
} catch (e) {
print(e);
}
});
this === MyApp, so you were first overriding MyApp's signal methods, then connecting to itself. The Signals import is fairly lax so it doesn't require a signal to be defined before use; you just connect to what you want and emit what you want. This is why you weren't getting any errors or warnings.
There are couple ways you can solve your problem:
// (1) Pick an object to emit and connect from and only add the signal methods once
Signals.addSignalMethods(headerBar);
// Since you're using an arrow function you can call this in the same place since it
// should still be in scope
headerBar.emit('update_ui', true);
// You can connect by grabbing the headerbar widget from your constructed window
this._window.get_titlebar().connect('update_ui', (headerBar, bool) => {});
// (2) Define a signal *on* your GtkApplication and use it as a relay
// Connect from a function in MyApp (so in `this.connect`, `this === MyApp`)
// To set `this` for the callback itself, use `Function.bind()`
this.connect('update-ui', this.ui.updateUI.bind(ui));
// You can always grab the primary instance of your GtkApplication (from anywhere)
// and emit the signal to invoke the MyApp.ui.updateUI() function
let myApp = Gio.Application.get_default();
myApp.emit('update-ui', true);
// (3) How I'd probably do it; just invoke the method directly
let myApp = Gio.Application.get_default();
myApp.ui.updateUI();

When I launch an event, this call to the intent but not show in the client app

I have this event:
Image of the intent
And I have this code in the Fullfillment:
function LanzarEvento(session) {
//var user = firebase.auth().currentUser;
const uuid = require('uuid');
const sessionId = uuid.v1();//user.uid;
// Imports the Dialogflow library
const dialogflow = require('dialogflow');
// Instantiates a sessison client
const sessionClient = new dialogflow.SessionsClient();
// The path to identify the agent that owns the created intent.
const sessionPath = session;//sessionClient.sessionPath(projectId, sessionId);
console.log('sessionPath -> ' + sessionPath);
let responseToUser = {
'fulfillmentText': 'Voy a buscar los mejores alojamientos, dame unos segundos.'
};
sendResponse(responseToUser);
// The text query request.
const request = {
session: sessionPath,
queryInput: {
event: {
name: 'PruebaEvento',
parameters: jsonToStructProto({user_name: 'Andres',name: 'Andres'}),
languageCode: languageCode,
},
},
};
sessionClient
.detectIntent(request)
.then(responses => {
console.log('Detected intent');
logQueryResult(sessionClient, responses[0].queryResult);
})
.catch(err => {
console.error('ERROR:', err);
});
}
And this is the conversation in the Training section of Dialogflow:
Trainning capture
How you can see, the event was returning "Hola Andres", but this never appears in the Facebook Messenger or in the Slack.
Please, I need your help. Can you help me?
Thanks in advance,
Andrés Gilabert