Google Calendar API watch channels not stopping - google-calendar-api

Stopping a watch channel is not working, though it's not responding with an error, even after allowing for propagation overnight.  I'm still receiving 5 notifications for one calendarlist change.  Sometimes 6.  Sometimes 3.  It's sporadic. We're also receiving a second round of notifications for the same action after 8 seconds.  Sometimes 6 seconds.  Sometimes a third set with a random count.  Also sporadic. Received a total of 10 unique messages for a single calendar created via web browser.

You can perform infinite amount of watch requests on specific calendar resource, Google will always return the same calendar resource Id for the same calendar, but the uuid you generate in the request will be different, and because of that, you will receive multiple notifications for each watch request that you've made. One way to stop all notifications from specific calendar resource, is to listen for notifications, pull out "x-goog-channel-id" and "x-goog-resource-id" from notification headers, and use them in Channels.stop request.
{
"id": string,
"resourceId": string
}
Every time you perform a watch request, you should persist the data from the response, and check if the uuid or resource id already exist, if yes don't perform watch request for that resource id again (if you don't want to receive multiple notifications).
e.g.
app.post("/calendar/listen", async function (req, res) {
var pushNotification = req.headers;
res.writeHead(200, {
'Content-Type': 'text/html'
});
res.end("Post recieved");
var userData = await dynamoDB.getSignInData(pushNotification["x-goog-channel-token"]).catch(function (err) {
console.log("Promise rejected: " + err);
});
if (!userData) {
console.log("User data not found in the database");
} else {
if (!userData.calendar) {
console.log("Calendar token not found in the user data object, can't perform Calendar API calls");
} else {
oauth2client.credentials = userData.calendar;
await calendarManager.stopWatching(oauth2client, pushNotification["x-goog-channel-id"], pushNotification["x-goog-resource-id"])
}
}
};
calendarManager.js
module.exports.stopWatching = function (oauth2client, channelId, resourceId) {
return new Promise(function (resolve, reject) {
calendar.channels.stop({
auth: oauth2client,
resource: {
id: channelId,
resourceId: resourceId
}
}, async function (err, response) {
if (err) {
console.log('The API returned an error: ' + err);
return reject(err);
} else {
console.log("Stopped watching channel " + channelId);
await dynamoDB.deleteWatchData(channelId)
resolve(response);
}
})
})
}

Not a google expert but I recently implement it in my application,
I am trying to answer some of your questions for future readers:
It's sporadic
Tha's because you have create more than 1 channels for watching events.
We're also receiving a second round of notifications for the same action after 8 seconds
Google doesn't say anything about the maximum delay for sending a push notification.
Suggestions:
CREATE:
When you create a new channel, always save the channel_id and channel_resource in your database.
DELETE:
When you want to delete a channel just use stop API endpoint with the channel data saved in your database
RENEW:
As you have noticed the channels do expire, so you need to update them once in a while. To do that create a crone in your server that is going to STOP all previous channels and it will create new one.
Comment: Whenever something is going wrong please read the error message sent from the Google API calendar. Most of the time, it tells you what is wrong.

Use Channels.stop which is mentioned in the docs. Supply the following data in your request body:
{
"id": string,
"resourceId": string
}
id is the channel ID when you created your watch request. Same goes with resource ID.
Read this SO thread and this github forum for additional reference.

Related

Flutter: How do I send a push notification from the device itself?

I'm new to programming and I'm developing an app in which the user is suppose to get a notification 30 minutes before an event that's scheduled on the app. The schedule is saved in the firebase database and the device checks every 30 minutes to see if it's time to send an alert. If that condition becomes true, I want the device to send the notification so that the user will be alerted about the event. Every tutorial I saw only showed how to send notification through firebase itself. None of them covered how you can send them from the device.
I came across this code:
final postUrl = Uri.parse('https://fcm.googleapis.com/fcm/send');
final data = {
"registration_ids": tokens, //list of tokens
"collapse_key": "type_a",
"notification": {
"title": 'title',
"body": 'body',
},
"data": {
"data1": 'data 1', //data passed
}
};
final Map<String, String> headers = {
'content-type': 'application/json',
'Authorization': serverKey, //..................FCM server key
};
final response = await http.post(postUrl,
body: json.encode(data),
encoding: Encoding.getByName('utf-8'),
headers: headers);
if (response.statusCode == 200) {
print('test ok push CFM');
return true;
} else {
print(' CFM error');
print(response.statusCode);
return false;
}
}
But isn't this bad practice since your server key is exposed? Are there any better and safe methods to do this using flutter??
I think checking the firebase database every 30 minutes from the device is not a good decision.
You can use/write a firebase cloud function. Using a firebase cloud function you can watch any document/field and try to write notification trigger logic like if a event is before 30 minutes then this cloud function will throw a notification via firebase messaging.
See https://firebase.flutter.dev/docs/functions/overview/ for more information.

Am I doing Firestore Transactions correct?

I've followed the Firestore documentation with relation to transactions, and I think I have it all sorted correctly, but in testing I am noticing issues with my documents not getting updated properly sometimes. It is possible that multiple versions of the document could be submitted to the function in a very short interval, but I am only interested in only ever keeping the most recent version.
My general logic is this:
New/Updated document is sent to cloud function
Check if document already exists in Firestore, and if not, add it.
If it does exist, check that it is "newer" than the instance in firestore, if it is, update it.
Otherwise, don't do anything.
Here is the code from my function that attempts to accomplish this...I would love some feedback if this is correct/best way to do this:
const ocsFlight = req.body;
const procFlight = processOcsFlightEvent(ocsFlight);
try {
const ocsFlightRef = db.collection(collection).doc(procFlight.fltId);
const originalFlight = await ocsFlightRef.get();
if (!originalFlight.exists) {
const response = await ocsFlightRef.set(procFlight);
console.log("Record Added: ", JSON.stringify(procFlight));
res.status(201).json(response); // 201 - Created
return;
}
await db.runTransaction(async (t) => {
const doc = await t.get(ocsFlightRef);
const flightDoc = doc.data();
if (flightDoc.recordModified <= procFlight.recordModified) {
t.update(ocsFlightRef, procFlight);
console.log("Record Updated: ", JSON.stringify(procFlight));
res.status(200).json("Record Updated");
return;
}
console.log("Record isn't newer, nothing changed.");
console.log("Record:", JSON.stringify("Same Flight:", JSON.stringify(procFlight)));
res.status(200).json("Record isn't newer, nothing done.");
return;
});
} catch (error) {
console.log("Error:", JSON.stringify(error));
res.status(500).json(error.message);
}
The Bugs
First, you are trusting the value of req.body to be of the correct shape. If you don't already have type assertions that mirror your security rules for /collection/someFlightId in processOcsFlightEvent, you should add them. This is important because any database operations from the Admin SDKs will bypass your security rules.
The next bug is sending a response to your function inside the transaction. Once you send a response back the client, your function is marked inactive - resources are severely throttled and any network requests may not complete or crash. As a transaction may be retried a handful of times if a database collision is detected, you should make sure to only respond to the client once the transaction has properly completed.
You use set to write the new flight to Firestore, this can lead to trouble when working with transactions as a set operation will cancel all pending transactions at that location. If two function instances are fighting over the same flight ID, this will lead to the problem where the wrong data can be written to the database.
In your current code, you return the result of the ocsFlightRef.set() operation to the client as the body of the HTTP 201 Created response. As the result of the DocumentReference#set() is a WriteResult object, you'll need to properly serialize it if you want to return it to the client and even then, I don't think it will be useful as you don't seem to use it for the other response types. Instead, a HTTP 201 Created response normally includes where the resource was written to as the Location header with no body, but here we'll pass the path in the body. If you start using multiple database instances, including the relevant database may also be useful.
Fixing
The correct way to achieve the desired result would be to do the entire read->check->write process inside of a transaction and only once the transaction has completed, then respond to the client.
So we can send the appropriate response to the client, we can use the return value of the transaction to pass data out of it. We'll pass the type of the change we made ("created" | "updated" | "aborted") and the recordModified value of what was stored in the database. We'll return these along with the resource's path and an appropriate message.
In the case of an error, we'll return a message to show the user as message and the error's Firebase error code (if available) or general message as the error property.
// if not using express to wrangle requests, assert the correct method
if (req.method !== "POST") {
console.log(`Denied ${req.method} request`);
res.status(405) // 405 - Method Not Allowed
.set("Allow", "POST")
.end();
return;
}
const ocsFlight = req.body;
try {
// process AND type check `ocsFlight`
const procFlight = processOcsFlightEvent(ocsFlight);
const ocsFlightRef = db.collection(collection).doc(procFlight.fltId);
const { changeType, recordModified } = await db.runTransaction(async (t) => {
const flightDoc = await t.get(ocsFlightRef);
if (!flightDoc.exists) {
t.set(ocsFlightRef, procFlight);
return {
changeType: "created",
recordModified: procFlight.recordModified
};
}
// only parse the field we need rather than everything
const storedRecordModified = flightDoc.get('recordModified');
if (storedRecordModified <= procFlight.recordModified) {
t.update(ocsFlightRef, procFlight);
return {
changeType: "updated",
recordModified: procFlight.recordModified
};
}
return {
changeType: "aborted",
recordModified: storedRecordModified
};
});
switch (changeType) {
case "updated":
console.log("Record updated: ", JSON.stringify(procFlight));
res.status(200).json({ // 200 - OK
path: ocsFlightRef.path,
message: "Updated",
recordModified,
changeType
});
return;
case "created":
console.log("Record added: ", JSON.stringify(procFlight));
res.status(201).json({ // 201 - Created
path: ocsFlightRef.path,
message: "Created",
recordModified,
changeType
});
return;
case "aborted":
console.log("Outdated record discarded: ", JSON.stringify(procFlight));
res.status(200).json({ // 200 - OK
path: ocsFlightRef.path,
message: "Record isn't newer, nothing done.",
recordModified,
changeType
});
return;
default:
throw new Error("Unexpected value for 'changeType': " + changeType);
}
} catch (error) {
console.log("Error:", JSON.stringify(error));
res.status(500) // 500 - Internal Server Error
.json({
message: "Something went wrong",
// if available, prefer a Firebase error code
error: error.code || error.message
});
}
References
Cloud Firestore Transactions
Cloud Firestore Node SDK Reference
HTTP Event Cloud Functions

can we unregister old service worker by its name and register new service worker

I am facing some problem related to service worker before some time i am using gcm and service worker file name was service-worker.js after releasing fcm i changed my code and now my service worker file name is firebase-messaging-sw.js but in some my client browser calling old service-worker.js file which is generating an error(service-worker.js not found 500). I already used following code before gettoken().
const messaging = firebase.messaging();
navigator.serviceWorker.register('/firebase-messaging-sw.js')
.then((registration) => {
messaging.useServiceWorker(registration);
// Request permission and get token.....
});
but its still showing this error.
In general, if you have multiple service workers registered with different scopes, and you want to get a list of them from a client page (and potentially unregister some of them, based on either matching scope or SW URL), you can do the following:
async unregisterSWs({matchingScope, matchingUrl}) {
const registrations = await navigator.serviceWorker.getRegistrations();
const matchingRegistrations = registrations.filter(registration => {
if (matchingScope) {
return registration.scope === matchingScope;
}
if (matchingUrl) {
return registration.active.scriptURL === matchingUrl;
}
});
for (const registration of matchingRegistrations) {
await registration.unregister();
console.log('Unregistered ', registration);
}
}
and then call it passing in either a scope or SW script URL that you want to use to unregister:
unregisterSWs({matchingScope: 'https://example.com/push'});
unregisterSWs({matchingUrl: 'https://example.com/my-push-sw.js'});

Parse Server Cloud code keeps sending push notifications

I've developed an app that sends push notifications using Parse Server Cloud code. These notifications are received correctly in the devices but hours later they are automatically sent from Parse Server again (and they are received again). This happens 3 or 4 times for each push notifications.
If push notifications are sent from Parse Dashboard they are only sent once, so it seems it's a problem of my cloud code.
This is my code:
Parse.Cloud.define("sendPushNotification", function(request, response) {
var userId = request.params.userId;
var message = request.params.message;
var queryUser = new Parse.Query(Parse.User);
queryUser.equalTo('objectId', userId);
var query = new Parse.Query(Parse.Installation);
query.matchesQuery('user', queryUser);
Parse.Push.send({
where: query,
data: {
alert: message,
badge: 0,
sound: 'default'
}
}, {
success: function() {
console.log('##### PUSH OK');
response.success();
},
error: function(error) {
console.log('##### PUSH ERROR');
response.error('ERROR');
},
useMasterKey: true
});
});
I had a similar issue sending emails from another cloud code function (not included in the question) and my problem was because I forgot to add response.success(); and response.error('ERROR'); methods.
So this time I was sure to include these 2 calls in the responses of "sendPushNotification" method.
After sending a push notification the logs show this:
2017-07-09T15:38:02.427Z - Ran cloud function sendPushNotification for user undefined with:
Input: {"message":"This is my message","userId":"myUserIdInParse"}
Result: undefined
I think that this "Result: undefined" could be related with the problem because success and error functions are not called.
What could be the problem with this code? Why the code doesn't receive a success() when the notifications are received correctly in the devices?

Meteor: Passing value to database after successful Paypal payment

I'd like to update the users database after a successful payment. Basically, converting $ to site credits. I've used https://github.com/tirtohadi/meteor-paypal-demo/, basically using his code implementing paypal to the web app. The only idea I have is when the site gets routed to the return page after payment. Code's here.
Router.map(function() {
this.route('/payment_return/:invoice_no/:amount/', {
where: 'server',
onBeforeAction: function() {
console.log("result");
result = paypal_return(this.params.invoice_no,this.params.amount,this.params.query.token,this.params.query.PayerID);
console.log(result);
if (result)
{
this.response.end("Payment captured successfully");
}
else
{
this.response.end("Error in processing payment");
}
}
});
});
I guess my question is, how do I securely update the db after a successful payment. Because I know client side update is dangerous (from what I've read anyway)

Resources