How to send notifications following recent Firestore updates using Firebase FCM and Ionic Native - ionic-framework

I am trying to set up Cloud Messaging Functions using https://www.youtube.com/watch?v=SOOjamH1bAA&t=317s
The code for my question is at 7:35 of this video. The full doc is in the link accessible from the video.
I am struggling with the recent updates to Firestore (see https://firebase.google.com/docs/functions/beta-v1-diff#cloud-firestore ), especially for time 7:35 of the youtube video link above. following the updates I changed the .onCreate to:
exports.newSubscriberNotification = functions.firestore
.document('subscribers/{subscriberId}')
.onCreate((snap,context) => { //I notice no async event...
const data = snap.data();
The problem is: 'await' expression is only allowed within an async function. This makes sense as there is no longer an 'async event' due to the update.
const devices = await devicesRef.get(); //see time 8:25 of video above.
If I add an async even above, the problem becomes: Property 'error' does not exist on type '“” | Promise' on the line:
devices.forEach(result => {...}) //see time 8:35 of video above.
I have tried multiple solutions found online but I'm digging myself in a hole. I think the problem is I am not triggering the index.ts function file (according to console.log() ).
Please bear with me - I am relatively new to app development.
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
import { Subject } from 'rxjs/Subject';
import { tap } from 'rxjs/operators';
admin.initializeApp();
console.log('before initializeApp');
exports.newSubscriberNotification = functions.firestore
.document('subscribers/{subscriberId}') //odd
.onCreate((snap,context) => {
const data = snap.data();
console.log('const data fired');
const userId = data.userId
const subscriber = data.subscriberId
console.log('gathered data')
// Notification content
const payload = {
notification: {
title: 'New Subscriber',
body: `${subscriber} is following your content!`,
}
}
// ref to the device collection for the user
const db = admin.firestore();
const devicesRef = db.collection('devices').where('userId', '==', userId);
// get the user's tokens and send notifications
console.log('before await');
const devices = await devicesRef.get();
console.log('async function');
console.log('before devices');
//devices();
console.log('after devices');
const tokens = [];
// send a notification to each device token
devices.forEach(result => {
const token = result.data().token;
tokens.push( token )
})
return admin.messaging().sendToDevice(tokens, payload)
});

Related

How can I get the chat room online members without reload the page in converse js

I am using converse.js and I am trying to get the users who joined the chat room, I am able to get the users but when a new user joins I can not get the new user on my console log until I reload the page, Below I have created a plugin for getting the users.
export const moderationActions = () => {
window.converse.plugins.add('moderation-actions', {
dependencies: [],
initialize: function () {
const _converse = this._converse;
_converse.api.listen.on(
'getToolbarButtons',
async (toolbar_el: any, buttons: any) => {
toolbar_el.model.occupants.models.map((occupant: any) => {
console.log(occupant.get('nick'), occupant.get('show')),
console.log(occupant);
});
},
);
},
});
};
There are a few events related to users like membersFetched but don't know how can I get the users without reloading the page
Whenever someone joins or leaves a room, a new occupant model is added or removed from the occupants collection on the room model (available via the .occupants attribute).
Whenever a model is added or removed from a collection, an add or remove event is triggered which you can listen to.
So you can try something like this:
window.converse.plugins.add('num-occupants', {
initialize: function () {
const _converse = this._converse;
const room = await api.rooms.get('jid');
let num_occupants = room.occupants.length;
room.occupants.on('add', () => (num_occupants = room.occupants.length));
room.occupants.on('remove', () => (num_occupants = room.occupants.length));
}
});

Google charged me $5000 in a month for using Google Place API

My project is under development, Vuejs and Flutter in the frontend and Laravel is in the backend.
I am using Google place autocomplete API in this project, I made a big mistake due to not having enough knowledge of using google apiz.
After trial finished google charged $5000.
My mistakes were 2 things;
Requesting unnecessary data and not providing sessionToken
I am posting my edited codes here if anyone can check it please, I'm afraid to make mistake again.
vuejs code
mounted() {
this.$refs.focusable.focus();
let $vm = this;
var autocomplete = new google.maps.places.Autocomplete(
document.getElementById("autocomplete"),
{
componentRestrictions: { country: "ca" },
fields: ["address_components", "formatted_address", "geometry"],
types: ["address"],
bounds: new google.maps.LatLngBounds(
new google.maps.LatLng(49.246292, -123.116226)
),
//strictbounds: true,
}
);
google.maps.event.addListener(autocomplete, "place_changed", function () {
var data = autocomplete.getPlace();
let latlng = {
lat: data.geometry.location.lat(),
lng: data.geometry.location.lng(),
};
data.address_components.forEach((component) => {
if (component.types.indexOf("administrative_area_level_1") > -1) {
if ($vm.checkState(component.short_name)) {
$vm.validAddress(data, latlng);
} else {
$vm.invalidAddress(data);
}
}
});
});
},
According to google docs link I don't need to provide sessionToken for Autocomplete widget. and I think my code is correct here.
and this is my Flutter code
TextFormField(
autofocus: true,
controller: _addressController,
decoration: InputDecoration(hintText: 'Postal code'),
onChanged: (val) {
final sessionToken = Uuid.v4();
findPlace(val, sessionToken);
},
),
//get a place
void findPlace(String placeName, String sessionToken) async {
if (placeName.length > 1) {
String autoComplete =
"$googleApi?input=$placeName&types=address&components=country:ca&key=$mapKey&sessiontoken=$sessionToken";
var res = await http.get(Uri.parse(autoComplete));
Map data = jsonDecode(res.body);
var predictions = data['predictions'];
var placesList = (predictions as List)
.map((e) => PlacePredictions.fromJson(e))
.toList();
setState(() {
sToken = sessionToken;
placePredictionList = placesList;
});
}
}
// get place details using place_id
addressDetails(placeId, sessionToken) async {
String fields = 'address_component,formatted_address,geometry';
String placeDetailsUrl =
"$googleApiDetails?place_id=$placeId&fields=$fields&key=$mapKey&sessiontoken=$sessionToken";
var res = await http.get(Uri.parse(placeDetailsUrl));
Map data = jsonDecode(res.body);
}
In the above Flutter code there is a confusion for me is this sessiontoken or sessionToken I don't know how to check it and there is no any information in the Google docs.
Help really appreciated.
Can't post as a comment as I don't have the required rep.
But you can cap your API usage so things like these won't happen.
Read more here: https://cloud.google.com/apis/docs/capping-api-usage
In the Google Cloud console, go to the APIs & Services Dashboard page.
From the projects list, select a project or create a new one.
Click the name of the API you're interested in.
Click Quotas. If the Quotas tab is not present in the tab nav, it means the API you've selected doesn't have quotas defined.
To find the quota you want to cap, enter the appropriate properties and values in the filter_list Filter field. For example, to find the Subnetworks quota, enter Quota:Subnetworks.
Click the checkbox next to the quota you want to cap, and then click create EDIT QUOTAS.
Complete the quota change form, including the new limit that you want to set.
Click SUBMIT REQUEST.

Using audio files as no input prompts in Google Actions

I am trying to set up re-prompts in my Google Action, and I require them to be audio files.
This is my implementation:
'use strict';
const { dialogflow } = require("actions-on-google");
const functions = require("firebase-functions");
const app = dialogflow();
app.intent('Default Welcome Intent', (conv) => {
conv.noInputs = [`<speak> <audio src = "https://myurl.com/audio/myfile.mp3">My audio file</audio></speak>`];
console.log("Logging the conversation object... ");
console.log(JSON.stringify(conv));
conv.ask("Hello! ");
});
exports.yourAction = functions.https.onRequest(app);
However, at the moment it will just read the raw string of this noInputs array when I do a "no input" in the dev console!
Using this kind of static no-input handling is not suggested.
Better is to create an Intent that handles the actions_intent_NO_INPUT Event. You can then use the response section (or a response from your Fulfillment) to include SSML with the audio tag.

notification(daily update) is not working on google home mini device

i created an app on dialogflow and deployed on google assistant,
it is working fine on both mobile assistant and google home, but the notification(daily update) is not working on google home mini device
well daily update working really fine in mobile device, and i didnt use any rich response like card and other thing which mini device doesnt support, then what is the possible reason?
Currently it is deployed home-notification-6b314 same google app is used for deployment of firebase function(as webhook), dialogflow app and and action on google
what should i do?
here is some code which i write on my webhook for reference:
// process.env.DEBUG = 'actions-on-google:*';
import * as functions from 'firebase-functions';
const { DialogflowApp } = require('actions-on-google');
const Actions = {
UNRECOGNIZED_DEEP_LINK: 'deeplink.unknown',
FINISH_UPDATE_SETUP: 'finish.update.setup',
};
const Parameters = {
CATEGORY: 'category',
UPDATE_INTENT: 'UPDATE_INTENT'
};
const DAILY_NOTIFICATION_ASKED = 'daily_notification_asked';
const PUSH_NOTIFICATION_ASKED = 'push_notification_asked';
export const webhook = functions.https.onRequest((request, response) => {
try {
const app = new DialogflowApp({ request, response });
console.log('Request headers: ' + JSON.stringify(request.headers));
console.log('Request body: ' + JSON.stringify(request.body));
// Map of action from Dialogflow to handling function
const actionMap = new Map();
actionMap.set(app.StandardIntents.CONFIGURE_UPDATES, configureUpdates);
actionMap.set(Actions.FINISH_UPDATE_SETUP, finishUpdateSetup);
actionMap.set("welcome", welcome);
actionMap.set("whatMissed", whatMissed);
actionMap.set("what_did_i_missed.what_did_i_missed-yes", whatMissed_yes);
app.handleRequest(actionMap);
} catch (e) {
console.log("catch error: ", e)
}
});
function welcome(app) {
app.ask(app.buildRichResponse()
.addSimpleResponse({
speech:
`<speak>
<s> Hi, I'm you medication assistant </s>
</speak>`
})
)
}
// Start opt-in flow for daily updates
function configureUpdates(app) {
console.log("====>> configure triggered1")
const intent = app.getArgument('UPDATE_INTENT');
console.log("##### INTENT: ", intent);
const category = app.getArgument('notification-category');
console.log("##### category: ", category);
app.askToRegisterDailyUpdate(
'what_did_i_missed',
[{ name: "some name", textValue: "some text" }]
);
}
// Confirm outcome of opt-in for daily updates.
function finishUpdateSetup(app) {
console.log("====>> finish triggered")
if (app.isUpdateRegistered()) {
app.tell("Ok, I'll start giving you notification that time.");
} else {
app.tell("something went wrong when i was scheduling up notification");
}
}
// # NOTE
// must have to enable notification 2 places,
// - first in google action dashboard(overview>Action discovery and updates>{intent-name}>Enable User updates and notifications>set title of notification)
// - second in google cloud console(Firebase Cloud Messaging API),
// otherwise i will just keep saying '{your app name} is not responding'
function whatMissed(app) {
const status = app.getArgument("boolean");
if (status === 'yes') {
app.tell("Ok, good job. keep it up!");
} else {
app.ask("would you like me to remind you again?");
}
}
function whatMissed_yes(app) {
app.askToRegisterDailyUpdate(
'what_did_i_missed',
[{ name: "some name", textValue: "some text" }]
);
}
"In this first iteration updates are system notifications on the user's Assistant-enabled phones, but we plan to expand to new surfaces."
https://developers.google.com/actions/assistant/updates/overview
Support person is also saying it is only supported in mobile device, and not in other surfaces like google home and mini.

Can I create follow-up actions on Actions on Google?

I know that I can deep link into my Google Home application by adding to my actions.json.
I also know that I can parse raw string values from the app.StandardIntents.TEXT intent that's provided by default, which I am currently doing like so:
if(app.getRawInput() === 'make payment') {
app.ask('Enter payment information: ');
}
else if(app.getRawInput() === 'quit') {
app.tell('Goodbye!');
}
But does Actions on Google provide direct support for creating follow-up intents, possibly after certain user voice inputs?
An example of a conversation flow is:
OK Google, talk to my app.
Welcome to my app, I can order your most recent purchase or your saved favorite. Which would you prefer?
Recent purchase.
Should I use your preferred address and method of payment?
Yes.
OK, I've placed your order.
My previous answer won't work after testing.
Here is a tested version.
exports.conversationComponent = functions.https.onRequest((req, res) => {
const app = new ApiAiApp({request: req, response: res});
console.log('Request headers: ' + JSON.stringify(req.headers));
console.log('Request body: ' + JSON.stringify(req.body));
const registerCallback = (app, funcName)=>{
if (!app.callbackMap.get(funcName)){
console.error(`Function ${funcName} required to be in app.callbackMap before calling registerCallback`);
return;
}
app.setContext("callback_followup", 1, {funcName});
}
const deRegisterCallback = (app)=>{
const context = app.getContext("callback_followup");
const funcName = app.getContextArgument("callback_followup", "funcName").value;
const func = app.callbackMap.get(funcName);
app.setContext("callback_followup", 0);
return func;
}
app.callbackMap = new Map();
app.callbackMap.set('endSurvey', (app)=>{
if (app.getUserConfirmation()) {
app.tell('Stopped, bye!');
}
else {
app.tell('Lets continue.');
}
});
app.callbackMap.set('confirmationStartSurvey', (app)=>{
const context = app.getContext("callback_follwup");
if (app.getUserConfirmation()) {
registerCallback(app, 'endSurvey');
app.askForConfirmation('Great! I\'m glad you want to do it!, do you want to stop?');
} else {
app.tell('That\'s okay. Let\'s not do it now.');
}
});
// Welcome
function welcome (app) {
registerCallback(app, 'confirmationStartSurvey');
const prompt = "You have one survey in your task list, do you want to proceed now?";
app.askForConfirmation(prompt);
}
function confirmationCalbackFollowup (app) {
const context = app.getContext("callback_followup");
if (! context){
console.error("ERROR: confirmationCallbackFollowup should always has context named callback_followup. ");
return;
}
const callback = deRegisterCallback(app);
return callback(app);
}
const actionMap = new Map();
actionMap.set(WELCOME, welcome);
actionMap.set('confirmation.callback.followup', confirmationCalbackFollowup );
app.handleRequest(actionMap);
});
The previous solution won't work because app is generated everytime the action function is called. I tried to save a callback function into app.data but it won't be existing next intent coming. So I changed another way. Register the callback function to app.callbackMap inside the function. so it will be there anyway.
To make it work, one important thing is Api.Ai need to have context defined in the intent. See the Api.Ai Intent here.
Make sure you have event, context, and action of course. otherwise, this intent won't be triggered.
Please let me know if you can use this solution. sorry for my previous wrong solution.
thanks
Can you give an example of a conversation flow that has what you are trying to do?
If you can use API.AI, they have Follow Up intents in the docs.
I do not think your code
if(app.getRawInput() === 'make payment') {
app.ask('Enter payment information: ');
}
else if(app.getRawInput() === 'quit') {
app.tell('Goodbye!');
}
is a good idea. I would suggest you have two different intent to handle "Payment information" and "Quit".