How to Publish a pub/sub message from a Cloud Function triggered by Cloud Pub/Sub - google-cloud-functions

hope you're all safe! I need some help with my cloud function triggered by Cloud Pub/Sub...
I have a cloud function that is triggered by one of my topics. From this message I want to send a new message to another topic, but I'm getting some errors.
My Function:
const { PubSub } = require("#google-cloud/pubsub");
exports.helloPubSub = async (message, context) => {
try {
let messageData = Buffer.from(message.data, "base64").toString();
let messageObjectData = JSON.parse(messageData);
//send pub/sub message to another topic
sendPubSubMessage(messageObjectData);
} catch (error) {
console.log(error.message);
}
};
function sendPubSubMessage(
messageObjectData,
topicName = "projects/myproject/topics/mytopic",
data = JSON.stringify(messageObjectData)
) {
const pubSubClient = new PubSub();
async function publishMessage() {
const dataBuffer = Buffer.from(data);
try {
const messageId = await pubSubClient.topic(topicName).publish(dataBuffer);
console.log(`Message ${messageId} published.`);
} catch (error) {
console.error(`Received error while publishing: ${error.message}`);
process.exitCode = 1;
}
}
publishMessage();
}
Package.json:
{
"name": "sample-pubsub",
"version": "0.0.1",
"dependencies": {
"#google-cloud/pubsub": "^0.18.0"
}
}
When this function runs I get this error:
ERROR: PubSub is not a constructor
I tried to change the dependency version to the last 2.18.3, but then it does not deploy...
I also tried to load both versions:
{
"name": "sample-pubsub",
"version": "0.0.1",
"dependencies": {
"#google-cloud/pubsub": "^0.18.0",
"lastPubSub" : "npm:google-cloud/pubsub#^2.18.3",
}
}
and got the same error...
Does anyone have any clue on how to fix it?
Thanks in advance!

Thank you all for the comments, finally I got it...
Solution:
package.json:
{
"name": "sample-pubsub",
"version": "0.0.1",
"dependencies": {
"#google-cloud/pubsub": "^0.18.0",
"googlePub": "npm:#google-cloud/pubsub#^2.18.3"
}
}

Related

Error when deploying DialogFlow CX webhook on Google Cloud Functions: "Error: listen EADDRINUSE: address already in use :::8080"

I desperately try to implement a simple Webhook for my DialogFlow CX agent. Never done this before so I just copy paste the index.js and package.json code I found on the following page to my Google Cloud Function: DialogFlow CX calculate values
But it seems this is not working. When trying to deploy the Cloud Function I get the error "Error: listen EADDRINUSE: address already in use :::8080".
Same happens if I take this sample code: Dialogflow CX webhook for fulfilment to reply user using nodejs
What am I doing wrong? I am editing the code and trying to deploy it directly in the Google Cloude web console and not via a command prompt tool.
HERE SOME MORE DETAILS:
Setup of Google Cloud Function: I set up a new Google Cloud Function via Google Cloud Console by clicking Create Function. I set Region to us-east1, Trigger type to HTTP and Allow unauthenticated invocations. Then I save, update the index.js and package.json as described below and click Deploy. The result is that deployment could not be done because of Error: listen EADDRINUSE: address already in use :::8080.
Here the code I put into to index.js:
'use strict';
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
var port = process.env.PORT || 8080;
app.use(
bodyParser.urlencoded({
extended: true
})
);
app.use(bodyParser.json());
app.post('/BMI', (req, res) => processWebhook4(req, res));
var processWebhook4 = function(request, response ){
const params = request.body.sessionInfo.parameters;
var heightnumber = params["height.number"];
var weightnumber = params["weight.number"];
var heightunit = params["height.unit-height"]
var weightunit = params["weight.unit-weight"]
var computedBMI;
if (heightunit == "cm" && weightunit == "kg") { //using metric units
computedBMI = ((weightnumber/heightnumber/heightnumber )) * 10000;
} else if (heightunit == "in" && weightunit == "lb") { //using standard metrics
computedBMI = ((weightnumber/heightnumber/heightnumber )) * 703;
}
const replyBMI = {
'fulfillmentResponse': {
'messages': [
{
'text': {
'text': [
'This is a response from webhook! BMI is ' + computedBMI
]
}
}
]
}
}
response.send(replyBMI);
}
app.listen(port, function() {
console.log('Our app is running on http://localhost:' + port);
});
And here the code I put into package.json:
{
"name": "cx-test-functions",
"version": "0.0.1",
"author": "Google Inc.",
"main": "index.js",
"engines": {
"node": "8.9.4"
},
"scripts": {
"start": "node index.js"
},
"dependencies": {
"body-parser": "^1.18.2",
"express": "^4.16.2"
}
}
The code in the StackOverflow posts you’ve shared is working on other platforms such as Heroku.
The error you encountered “Error: listen EADDRINUSE: address already in use :::8080” is because of the code function listening to port 8080. Note that you will need to check and edit the sample code you’ve provided, and see if the libraries used are supported (for example: express js) in Google Cloud Functions and if the libraries are compatible in order to use it in Google Cloud Functions.
Here’s a working code for Cloud Functions from this StackOverflow Post:
exports.helloWorld = (req, res) => {
const params = req.body.sessionInfo.parameters;
var heightnumber = params.height.number;
var weightnumber = params.weight.number;
var heightunit = params.height.heightUnit;
var weightunit = params.weight.weightUnit;
var computedBMI;
if (heightunit == "cm" && weightunit == "kg") { //using metric units
computedBMI = ((weightnumber/heightnumber/heightnumber )) * 10000;
} else if (heightunit == "in" && weightunit == "lb") { //using standard metrics
computedBMI = ((weightnumber/heightnumber/heightnumber )) * 703;
}
const replyBMI = {
'fulfillmentResponse': {
'messages': [
{
'text': {
'text': [
'This is a response from webhook! BMI is ' + computedBMI
]
}
}
]
}
}
res.status(200).send(replyBMI);
};
Here’s the result:
Moreover, here’s a sample code you can also use for deploying in Cloud Function:
index.js
exports.helloWorld = (req, res) => {
let fulfillmentResponse = {
"fulfillmentResponse": {
"messages": [{
"text": {
"text": [
"This is a sample response"
]
}
}]
}
};
res.status(200).send(fulfillmentResponse);
};
package.json
{
"name": "sample-http",
"version": "0.0.1"
}
Once you deployed the sample code, you can do the following to be able to use the webhook:
Go to Manage > Webhooks > Create
Add Display Name and Webhook URL(Trigger URL in Cloud Functions)
Click Save
Go to Build > Flow > Start Page
Select any Route and add the webhook
Test in Simulator
Result should be like this:

How to get current event's conference data after use schedule conference in Google Calendar

Background: Google Calendar > click New button > enter New Event Page > Add Conference
Question: When user click Add Conference to schedule a conference(3rd party service, not Hangouts), how could I get the current event's conference data? I tried to use Calendar.Events.get API but it returned 404.
my appscripts setting is here:
when user schedule a conference, it will trigger onCalendarEventUpdate function
{
"timeZone": "America/Los_Angeles",
"addOns": {
"calendar": {
"eventUpdateTrigger": {
"runFunction": "onCalendarEventUpdate"
},
}
}
}
my onCalendarEventUpdate:
function onCalendarEventUpdate(context: any) {
// I can get calendarId, evnetId
const {
calendar: { calendarId, id: evnetId }
} = context;
// when I try to get event conferenceData, it returns 404
let event;
try {
event = Calendar.Events && Calendar.Events.get(calendarId, evnetId);
if (!event) {
Logger.log(`[getEventsCollectionByCalendarId]event not found`);
}
} catch (e) {
Logger.log(`[getEventsCollectionByCalendarId]error: ${JSON.stringify(e)}`);
}
}
now the error message is:
{
"message":"API call to calendar.events.get failed with error: Not Found",
"name":"GoogleJsonResponseException",
"lineNumber":64,
"details":{
"message":"Not Found",
"code":404,
"errors":[{
"domain":"global",
"reason":"notFound",
"message":"Not Found"
}]
}
}
I found the solution now, hope it is useful for others.
First update manifest file:
{
"timeZone": "America/Los_Angeles",
"oauthScopes": [
"https://www.googleapis.com/auth/calendar.addons.current.event.read",
"https://www.googleapis.com/auth/calendar.addons.current.event.write"
],
"addOns": {
"calendar": {
"currentEventAccess": "READ_WRITE",
"eventUpdateTrigger": {
"runFunction": "onCalendarEventUpdate"
},
}
}
}
Then in onCalendarEventUpdate function
function onCalendarEventUpdate(context) {
const { conferenceData } = context;
console.log('[onCalendarEventUpdate]conferenceData:', conferenceData);
}
You can get conference data here successfully
Reference Doc:
https://developers.google.com/apps-script/manifest/calendar-addons
Based on the error message, I would guess your calendarId and eventId are invalid. The event updated event does not give you the event id sadly. Because of this, you need to perform an incremental sync to get the updated event data, which means you need to do an initial sync first as described in the docs (link below).
First, run this code to perform the initial sync and get the nextSyncTokens for each calendar. You only need to run this once.
function initialSyncToSetNextSyncTokens() {
const calendarIds = Calendar.CalendarList.list()["items"].map((item) => {
return item["id"]
});
for (let calendarId of calendarIds) {
let options = {maxResults: 2500, nextPageToken: undefined}
let response = {}
do {
response = Calendar.Events.list(calendarId, options)
options["nextPageToken"] = response["nextPageToken"]
} while (options["nextPageToken"])
PropertiesService.getScriptProperties().setProperty(calendarId, response["nextSyncToken"])
}
}
Then, set your trigger to run this function and log the conference data. Notice we also update the nextSyncToken so the next execution will work correctly.
function onEventUpdated(context) {
const calendarId = context["calendarId"]
const nextSyncToken = PropertiesService.getScriptProperties().getProperty(calendarId)
const response = Calendar.Events.list(calendarId, {syncToken: nextSyncToken})
PropertiesService.getScriptProperties().setProperty(calendarId, response["nextSyncToken"])
const event = response["items"][0] // assumes this code will run before another event is created
const conferenceData = event["conferenceData"]
console.log(conferenceData)
}
Link to relevant docs:
https://developers.google.com/apps-script/guides/triggers/events#google_calendar_events

Error: No handler for requested intent at WebhookClient.handleRequest

Default intent calling a cloud function gives error
Error: No handler for requested intent
at WebhookClient.handleRequest (/user_code/node_modules/dialogflow-fulfillment/src/dialogflow-fulfillment.js:287:29)
at exports.dialogflowFirebaseFulfillment.functions.https.onRequest (/user_code/index.js:73:11)
at cloudFunction (/user_code/node_modules/firebase-functions/lib/providers/https.js:57:9)
at /var/tmp/worker/worker.js:783:7
at /var/tmp/worker/worker.js:766:11
at _combinedTickCallback (internal/process/next_tick.js:73:7)
at process._tickDomainCallback (internal/process/next_tick.js:128:9)
as my webresponse in diagnostic info log shows this.
{
"responseId": "86043a10-8bc2-4ee7-8e8b-1e997289ad7c",
"queryResult": {
"queryText": "hi",
"action": "input.welcome",
"parameters": {},
"allRequiredParamsPresent": true,
"fulfillmentText": "Hi. Am Uma. Kindly let me know your experience facing an issue.",
"fulfillmentMessages": [
{
"text": {
"text": [
"Hi. Am Uma and welcome to support. Kindly let me know your experience facing an issue."
]
}
}
],
"outputContexts": [
{
"name": "projects/handymanticketagent/agent/sessions/e416a522-da87-ebd1-348e-9fdea1efbf65/contexts/defaultwelcomeintent-followup",
"lifespanCount": 2
}
],
"intent": {
"name": "projects/handymanticketagent/agent/intents/c58f706f-6cb6-499d-9ce2-459e8054ddc1",
"displayName": "Default Welcome Intent"
},
"intentDetectionConfidence": 1,
"diagnosticInfo": {
"webhook_latency_ms": 10001
},
"languageCode": "en"
},
"webhookStatus": {
"code": 4,
"message": "Webhook call failed. Error: Request timeout."
}
}
Based on the stack overflow answers here, Have added an intent mapped to function but am still getting error and could progress further. Where and how the cloud function console says am missing a handler for my request?
Update : As #prisoner said, including my cloud function code.
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const { WebhookClient } = require('dialogflow-fulfillment');
process.env.DEBUG = 'dialogflow:*'; // enables lib debugging statements
admin.initializeApp(functions.config().firebase);
const db = admin.firestore();
exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
console.log(request.body.queryResult.fulfillmentText);
console.log(request);
console.log(response);
const agent = new WebhookClient({ request, response });
console.log(agent);
function writeToDb(agent) {
// Get parameter from Dialogflow with the string to add to the database
const databaseEntry = agent.parameters.databaseEntry;
console.log(databaseEntry);
// Get the database collection 'dialogflow' and document 'agent' and store
// the document {entry: "<value of database entry>"} in the 'agent' document
const dialogflowAgentRef = db.collection('dialogflow').doc('agent');
console.log(dialogflowAgentRef);
return db.runTransaction(t => {
t.set(dialogflowAgentRef, { entry: databaseEntry });
console.log(Promise.resolve('Write complete'));
return Promise.resolve('Write complete');
}).then(doc => {
agent.add('Wrote "${databaseEntry}" to the Firestore database.');
return null;
}).catch(err => {
if (err) {
console.log(err.stack);
}
console.log('Error writing to Firestore: ${err}');
agent.add('Failed to write "${databaseEntry}" to the Firestore database.');
});
}
function readFromDb(agent) {
console.log(agent);
// Get the database collection 'dialogflow' and document 'agent'
const dialogflowAgentDoc = db.collection('dialogflow').doc('agent');
console.log(dialogflowAgentDoc);
// Get the value of 'entry' in the document and send it to the user
return dialogflowAgentDoc.get()
.then(doc => {
if (!doc.exists) {
agent.add('No data found in the database!');
} else {
agent.add(doc.data().entry);
}
return Promise.resolve('Read complete');
}).catch(() => {
agent.add('Error reading entry from the Firestore database.');
agent.add('Please add a entry to the database first by saying, "Write <your phrase> to the database"');
});
}
function defaultwelcomeintent_function(agent) {
console.log(agent);
}
// Map from Dialogflow intent names to functions to be run when the intent is matched
let intentMap = new Map();
intentMap.set('defaultwelcomeintent-followup', defaultwelcomeintent_function);
intentMap.set('ReadFromFirestore', readFromDb);
intentMap.set('WriteToFirestore', writeToDb);
console.log(intentMap);
agent.handleRequest(intentMap);
});
The diagnostic info says that the intent's display name for that fulfillment is "Default Welcome Intent":
"intent": {
"name": "projects/handymanticketagent/agent/intents/c58f706f-6cb6-499d-9ce2-459e8054ddc1",
"displayName": "Default Welcome Intent"
},
So you'd need to create a mapping for it like this:
intentMap.set('Default Welcome Intent', defaultwelcomeintent_function);
Where defaultwelcomeintent_function is the handler you have defined within your cloud function.
I had the same issue with the exact error from Dialogflow : Error: No handler for requested intent, in my case I'm using async/await in order to make synchronous calls through a cloud function in dialogflow fulfillement.
I noticed that in one of my main function mapped to an agent, I wasn't returning anything. Since the function was using async I added a return statment with the promise that I was waiting for at the beggining.
async function getInfo(agent) {
var hh = await getUserInfos(request.body.originalDetectIntentRequest.payload.uuid);
// Do what you want here
var yy = hh.aa[0].zz.yy;
agent.setFollowupEvent({ "name": "xxx", "parameters": { "xxx": yy } });
// Return your promise
return hh;
}

Solidity v^0.5.0 compiler error [invalid callback specified]

I'm trying to compile my contract but get this error:
AssertionError [ERR_ASSERTION]: Invalid callback specified.
One answer was to change the version of the compiler but my version is up to date (0.5.0).
I'm actually trying to take an old code (0.4.17) and upgrade it. Tried for 2 days and just kept failing.
Here is my contract:
pragma solidity ^0.5.0;
contract Lottery{
address public manager;
address payable [] public players;
modifier restricted {
require(msg.sender == manager);
_;
}
constructor() public {
manager = msg.sender;
}
function participate() public payable {
require(msg.value > .01 ether);
players.push(msg.sender);
}
function pseudoRandom() private view returns(uint){
return uint(keccak256(abi.encodePacked(block.difficulty, now, players)));
}
function pickWinner() public restricted {
require(msg.sender == manager);
uint index = pseudoRandom() % players.length;
address(players[index]).transfer(address(this).balance);
(players) = new address payable[](0);
}
function getPlayers() public view returns(address payable[] memory){
return players;
}
}
here is my package.json:
{
"name": "lottery",
"version": "1.0.0",
"description": "lottery contract with Solidity",
"main": "compile.js",
"directories": {
"test": "test"
},
"scripts": {
"test": "mocha"
},
"author": "Torof",
"license": "ISC",
"dependencies": {
"ganache-cli": "^6.2.1",
"mocha": "^5.2.0",
"save": "^2.3.2",
"solc": "^0.5.0",
"tar": "^4.4.8",
"truffle": "^4.1.14",
"truffle-hdwallet-provider": "0.0.6",
"web3": "^1.0.0-beta.36"
}
}
and here is the compiler:
const path = require('path');
const fs = require('fs');
const solc = require('solc'); //Could the error be here ?
const lotteryPath = path.resolve(__dirname, 'contracts', 'Lottery.sol');
const source = fs.readFileSync( lotteryPath, 'utf8');
module.exports = solc.compile(source, 1).contracts[':Lottery'];
console.log(solc.compile(source, 1));
And lastly I found this err message but don't get it:
[ts]
Could not find a declaration file for module 'solc'.
'/home/torof/desk/coding/Udemy/ETH-stephenGrider/lottery/node_modules/solc/index.js'
implicitly has an 'any' type.
Try `npm install #types/solc` if it exists or add a new declaration (.d.ts) file containing `declare module 'solc';`
Previous versions of solc supported the style of compilation you're using, but it looks like the new versions only support standard JSON in and out. You probably want something like this:
console.log(JSON.parse(solc.compile(JSON.stringify({
language: 'Solidity',
sources: {
'lottery.sol': {
content: source,
},
},
settings: {
outputSelection: {
'*': {
'*': ['evm', 'bytecode'],
},
},
},
}))).contracts['lottery.sol'].Lottery);
Here is one way implementation for 0.5.X, together with the deploy code:
This is how I compile, there is deep nesting destruction to fetch the bytecode.
const path = require('path');
const fs = require('fs');
const solc = require('solc');
const templatePath = path.resolve(__dirname, 'contracts', 'templatename.sol');
const source = fs.readFileSync(templatePath, 'utf8');
const input = {
language: 'Solidity',
sources: {
'yourtemplate.sol': {
content: source
}
},
settings: {
outputSelection: {
'*': {
'*': ['*']
}
}
}
}
const { abi: interface, evm: { bytecode: { object } } } = JSON.parse(solc.compile(JSON.stringify(input))).contracts['yourtemplate.sol'].Templatename; //
module.exports = { interface, object }; // object is the actual name of the bytecode
And the code for deploy:
const ganache = require('ganache-cli');
const Web3 = require('web3');
const web3 = new Web3(ganache.provider());
const { interface, object: bytecode } = require('../compile');
// i've renamed object with bytecode
const accounts = await web3.eth.getAccounts();
templatename = await new web3.eth.Contract(interface)
.deploy({ data: bytecode, arguments: [INPUT_PARAMETER_GOES_HERE] })
.send({ from: accounts[0], gas: '1000000' });
Did you try installing this package:
npm install #types/solc

Chrome.storage.sync.get() seems to be duplicating keys?

I am saving some settings using the following sequence
var getSettings = async function() {
var settings;
try {
settings = await authenticatedGET(server_url + SETTINGS_ENDPOINT);
return settings;
} catch (error) {
console.log("Settings Fetch Failed: " + error);
throw new Error(error);
}
}
const setLocalSettings = function(settings) {
chrome.storage.sync.set({ 'LML_Settings': JSON.parse(settings) }, function() {
console.log("Settings saved locally");
});
}
At the line right after the setLocalSettings function definition, the 'settings' object logs out as
{"email_format":"individual","close_tab":true,"frequency":"DAILY"} (correctly as intended). When I go to fetch the settings using this sequence:
chrome.storage.sync.get('LML_Settings', function(LMLSettingsContainer) {
console.log(LMLSettingsContainer);
if (LMLSettingsContainer.LML_settings.close_tab == "true") {
closeCurrentTab();
}
})
LMLSettingsContainer logs out as
{
"LML_Settings": {
"close_tab": true,
"email_format": "individual",
"frequency": "DAILY"
}
}
accessing my settings with LMLSettingsContainer.LML_Settings["<setting>"] is a bit annoying (and its the whole reason I named the top variable LMLSettingsContainer).
Does anyone know if there's a way to have chrome save/get these values unwrapped?
chrome.storage.sync.get('LML_Settings', ({LML_settings}) => { ... }) works, per #wOxxOm