Save a user's pushSubscription info in Rails database? - ruby-on-rails

I have followed this tutorial: https://dzone.com/articles/how-to-add-real-web-push-notifications-to-your-web to enable push notifications on my rails app. I am using the webpush gem to send the notifications.
So far, all I have managed to do is get the browser to ask for permission to send notifications, and when I try to call the method send_web_push_notification (shown below) line 2 is throwing up an error.
I think it is because I am not saving the user's pushSubscription info to the database, but I don't know how to do this. In the tutorial, there is this line at the end: 'We use a database JSON field called web_push_subscription to save the pushSubscription info on our users.'
Would someone be able to show me how to do this?
Any help would be appreciated. Thanks.
send_web_push_notification method:
def send_web_push_notification(user_id)
subscription = User.find(user_id).web_push_subscription
message = {
title: "You have a message!",
body: "This is the message body",
tag: "new-message"
}
unless subscription.nil?
Webpush.payload_send(
message: JSON.generate(message),
endpoint: subscription["endpoint"],
p256dh: subscription["keys"]["p256dh"],
auth: subscription["keys"]["auth"],
ttl: 15,
vapid: {
subject: 'mailto:admin#example.com',
public_key: Rails.application.config.webpush_keys[:public_key],
private_key: Rails.application.config.webpush_keys[:private_key]
}
)
end
end
serviceworker.js.erb:
function showNotification(event) {
return new Promise(resolve => {
const { body, title, tag } = JSON.parse(event.data.text());
self.registration
.getNotifications({ tag })
.then(existingNotifications => { // close? ignore? })
.then(() => {
const icon = `/path/to/icon`;
return self.registration
.showNotification(title, { body, tag, icon })
})
.then(resolve)
})
}
self.addEventListener("push", event => {
event.waitUntil(
showNotification(event)
);
}
});
self.addEventListener("notificationclick", event => {
event.waitUntil(clients.openWindow("/"));
});
application.js:
const permission = Notification.requestPermission();
if (permission !== 'granted') {
// no notifications
}else{
// yay notifications
}
function subscribeToPushNotifications(registration) {
return registration.pushManager
.subscribe({
userVisibleOnly: true,
applicationServerKey: window.vapidPublicKey
})
.then(pushSubscription => {
console.log(
"Received PushSubscription:",
JSON.stringify(pushSubscription)
);
return pushSubscription;
});
}

If you look closely codes you will notice that you have to create a Json field in database to save subscription. If there is subscription available than push notification will be sent. Actually there many more scenarios it is not necessary the you want to save one browser for user notification, if you plan multiple browser than you have to create separate table, but if you want to add one browser for push notification, than you can add this information in user table too. Create new migration to update your user table and add following column
t.json "web_push_subscription"
Run migration, Now you have Json column if you notice code clearly following are information you require in your user database, you will save this information when user subscribe for push notification
user. web_push_subscription[:endpoint] = what_ever_value_received
user.web_push_subscription[:auth] = what_ever_value_received
Unfortunately it is just idea as I have not implement it, but I should check JSON.stringify(pushSubscription) object recived, and there are chances all data would be in this response which you received you may need to save it as it is to your subscription.
You also need to save permission, that user really allowed you to send notification, if yes than one field in user as boolean notification = true, so you can check if user allow you to send notification, than you can send, otherwise don't send. You should also have way to remove these keys for specific user when they unsubscribe notifications.

You basically need to update a model, which is backend, but you do not want the user to go through all that process. This is where ajax comes in handy. I am not very comfortable with ajax but it is one of the best things provided by JS.
With the code in ajax function, you will hit the controller update action with the changed attribute and the update will change the model as necessary and update it. then your html will change accordingly without page refresh.
TLDR: I think you are looking for this.

Related

SendInBlue trackEvent returns 204 but does not show event in the console

I am trying to send an event using the SendInBlue API here.
When I send the event, it returns a 204 correctly - but I am not getting any events here and I have created an automation flow which is triggered by the event, and it does not send.
const axios = require("axios");
const url = 'https://in-automate.sendinblue.com/api/v2/trackEvent';
(async() => {
try {
const event = await axios.post(
url,
JSON.stringify( {
email: 'myemail#emailprovider.co',
event: 'USER_SUBSCRIBED'
}),
{
Accept: 'application/json',
'Content-Type': 'application/json',
'ma-key': 'xkeysib-MY_v3_API_KEY'
},
);
console.log(event);
} catch (err) {
console.log(JSON.stringify(err))
}
})();
Is there a way I can see the events from this call coming in on the console?
The ma-key is not the same that API KEY. You should use the ma-key instead your current API for the automatization key.
After a couple of mails and a phone call, i figured out where is the ma-key:
You should login at send inblue. Click on Automatization (top menu). Click on Config (left tab bar). Click on something like 'see tracking code'. Then, you see a JS code. In this code, there is a key on it. This is your key.
My panel is in Spanish so maybe the words are not the same. Cheers.
As far as I know you can't really see the events in the console.
If you just want to make sure it's working you can
go to automation
start a workflow
Select a trigger: Website activities => An event happens
If you can select your event it means it worked.
Sendinblue is more a marketing automation tool and not an event analytics. So I'm not surprised you can't see the event in the GUI. If you want to see the events, try something like Mixpanel.
As #hector said pay attention to the API key. you're using the V3 campaigns (emails, contacts...) key. The tracking API is different.
Also, if you want to add event data, apparently you absolutely need to add a random unique ID. I struggled to find this as their docs are not super clear about it. So the body should look like something like this:
body: jsonEncode(<String, dynamic>{
'eventdata': {
id:"123456",
data: {
event_data1: value1,
event_data2: value2,
}
}
'email': example#mail.com,
'event': eventName
}),

Sending unique codes via SMS automation

I have a guest wifi that required the user to have a voucher code in order to access the wifi network.
What am trying to achieve is for the user to send a sms message to my twilio phone number.
I would for the user to be somehow validated. once validated a sms message would be sent to the user with a unique voucher code.
Each voucher code would be unique and i would need a way to upload them so that twilio can grab a new code each time and send to the user.
I would really appreciate if someone would be willing to help me on this.
I have already created a sms automation using twilio studio. It does everything that i want it to do except for the validation part and i am not sure how to send a unique code each time the flow is triggered.
Thanks!
Okay, you didn't mention the framework or language you are using to communicate with the Twilio API. but I will explain the principle.
first, I will recommend that you have a data table where you store the codes you send and the user who received it. ( if you are working with a database you can set the code field to unique, so you can be sure about the duplication )
then before going to send the code to Twilio API so it can forward it in an SMS to the user you should validate it by checking if the newly generated code is already used or not.
this is an example using the nodejs and the mongoose ORM ( Mongo DB database ):
const mongoose = require( "mongoose" );
const Schema = mongoose.Schema;
const uniqueValidator = require( "mongoose-unique-validator" );
const CodeSchema = new Schema( {
owner: { type: Schema.Types.ObjectId, ref: "User" },
code: { type: Number, index: true, unique: true }
} ,{ timestamps: true } );
CodeSchema.plugin( uniqueValidator, { message: "is already taken." } );
CodeSchema.pre( "validate", function( next ){
if( !this.code ) {
this.code = Math.floor( Math.random() * Math.pow( 36, 6 ) | 0 );
}
next();
} );
module.exports = mongoose.model( "Code", CodeSchema ) || mongoose.models.Code;
then when you create the schema this pre-validate function will execute and fill the code field with a unique code.

How to get registration id in push event in ServiceWorker?

I have the registration id when subscribing, but I can't figure out how to get it in the service worker file when I get the push notification?
client.js
pushManager.subscribe({ userVisibleOnly: true }).then(({ endpoint }) => {
const registrationId = endpoint.split('https://android.googleapis.com/gcm/send/')[1];
// How do I get this registrationId in service-worker.js below?
});
service-worker.js
self.addEventListener('push', event => {
// I'd like to make a request to server to get the notification data, but I need the registrationId for that. How do I get registration id here?
});
Inside the ServiceWorkerGlobalScope, you can get the effective ServiceWorkerRegistration, which in turns exposes the PushManager, which then gives you a getSubscription() method, which returns a Promise that resolves with the current subscription.
There are a lot of layers to follow, but the actual code is fairly straightforward:
self.registration.pushManager.getSubscription().then(subscription => {
// Do something with subscription.endpoint
});

Using a JSON user object for Authentication in AngularJS

So I've got an question about authentication and have been wondering how other people might handle this situation. I'm currently running an Angular app that is built on a Rails API.
So far for authentication I have a form that does a post to the Rails side which logs the user in and then sends them back to the Angular app on success. Once the cookie is set and the user is logged in, I'm able to access a user.json file which contains all the User information one might expect (Id, username, roles, rights, etc). Since verification all happens on Rails, if the user logs out then this information is removed. So the two states look like so...
Logged in
{
id: 99384,
name: "Username",
url: "//www.test.com/profiles/Username",
timezone: null,
rights: [ ],
roles: [
"admin"
],
}
Logged out
{
error: "You need to login or join before continuing."
}
So far I've seen all these millions of different ways to do auth for Angular, but it seems like nothing fits this type of method. So my question is, since the server is handling all of the verification, is there a way to just check if they user.json file is empty (displaying the error message) and if it is send the Angular app to the Rails login page? Is there really any point messing with Cookies, Tokens, etc when I can base it all on the JSON file?
You are already using cookies - the server is setting them. What you have done is a fairly standard way of doing things.
To check the json file, you can do something like this stub shows in your controller:
app.controller('AppControl', function($scope, $http, $location){
// Get the JSON file.
$http.get('/path/to/json/file')
.then(response){
if(response.data.error){
// redirect to login
$location.path('login');
}
else{
$scope.user = response.data;
// your app code here.
}
})
.catch(function (error){
// unable to reach the json file - handle this.
});
});
Of course, you should really move this out into a service so you can re-use it, and also cache the data, rather than getting the user every time you change route/page, but this gives you a vague idea.
EDIT Example factory:
.factory('User', function( $http ){
// Create a user object - this is ultimately what the factory will return.
// it's a singleton, so there will only ever by one instance of it.
var user = {};
// NOTE: I am assigning the "then" function of the login promise to
// "whenLoggedIn" - your controller code is then very easy to read.
user.whenLoggedIn = $http.get('user.json')
.then(function(response){
// Check to see if there is an error.
if (response.data.error !== undefined) {
// You could be more thorough with this check to determine the
// correct action (examine the error)
user.loggedIn = false;
}
else {
// the user is logged in
user.loggedIn = true;
user.details = response.data;
return user;
}
}).then; // <-- make sure you understand why that .then is there.
return user;
})
Usage in the controller
.controller('ExampleController', function($scope, User){
// It's handy to have the user on the scope - you can use it in your markup
// like I have with ng-show on index.html.
$scope.User = User;
// Do stuff only if the user is loggedin.
// See how neat this is because of the use of the .then function
User.whenLoggedIn( function (user){
console.log(user.details.name + " is logged in");
});
});
Because it's on the scope, we can do this in the html:
<body ng-controller="ExampleController">
<h1 ng-show="User.loggedIn == null">Logging in..</h1>
<h1 ng-show="User.loggedIn == true">Logged in as {{ User.details.name }}</h1>
<h1 ng-show="User.loggedIn == false">Not logged in</h1>
</body>
Here is an example on plunker where this is working.
Note the following:
If the user is/was already logged in, when you inject the service in the future, it won't check the file again. You could create other methods on the service that would re-check the file, and also log the user out, back in, etc. I will leave that up to you.
There are other ways to do this - this is just one possible option!
This might be obvious, but it's always worth saying. You need to primarily handle authentication and security on the server side. The client side is just user experience, and makes sure the user doesn't see confusing or conflicting screens.

How to access Twitter screenname, avatar when autopublish has been turned off

In Meteor, I believe ordinarily you can get the screenname of a twitter user after they've logged in with {{services.twitter.screenName}}.
However with autopublish turned off the only thing that seems to be available is {{currentUser.profile.name}} (which returns their 'full name' i.e. Kevin Rose, not krose).
How would I go about getting the screenname or avatar from a user that has logged in with Twitter, if autopublish has been removed?
You just need to set up a publish record on the server to determine what information you're going to send to the client, and then subscribe to it in a client-side startup function (or better still, iron-router).
Meteor.publish("userData", function () {
return Meteor.users.find({_id: this.userId},
{fields: {'services.twitter': 1}});
});
That will provide the services field for the logged in user in Meteor.user() once that client has subscribed to "userData" in addition to the fields that are automatically supplied.
You need to manually publish / subscribe your data. By default, only emails, username and profile fields are published for Meteor.users collection (see the docs). So you need to publish others:
Meteor.publish('userData', function() {
if(!this.userId) return null;
return Meteor.users.find(this.userId, {fields: {
services: 1,
...
}});
});
After that, subscribe to this channel on the client:
Deps.autorun(function() {
Meteor.subscribe('userData');
});

Resources