Add sound to push notifications - ios

I'm using django-push-notifications to send push to our ios users (using APNS).
from push_notifications.models import APNSDevice, APNSDeviceQuerySet
from apps.notifications.db.models import Notification
from apps.users.db.models import User
class APNSService:
def __init__(self, user: User, title: str, body: str, data: dict):
self.user = user
self.title = title
self.body = body
self.data = data
def get_devices(self) -> APNSDeviceQuerySet:
return APNSDevice.objects.filter(user=self.user)
def send_message(self):
return self.get_devices().send_message(
message=dict(
title=self.title,
body=self.body
),
badge=Notification.objects.filter(recipient=self.user, unread=True).count(),
extra=self.data
)
The problem is, that notifications comes without a sound. According to the docs, there should be extra field to execute the sound when notification is received.
How to do this?

There is param sound, example
def send_message(self):
return self.get_devices().send_message(
message=dict(
title=self.title,
body=self.body
),
extra=self.data,
sound="default",
)

Related

Messages in Django Channels sent outside consumer not being received

I'm trying to send messages to specific websocket instances, but neither channel_layer.send, nor using channel_layer.group_send with unique groups for each instance seems to be working, no errors are being raised, they just aren't received by the instances.
The function that sends the message is:
def listRequest(auth_user, city):
request_country = city["country_name"]
request_city = city["city"]
request_location = request_city +", "+request_country
concatenate_service_email = auth_user.service + "-" + auth_user.email
this_request = LoginRequest(service_and_email=concatenate_service_email, location=request_location)
this_request.generate_challenge()
this_request.set_expiry(timezone.now() + timezone.timedelta(minutes=5))
this_request.save()
channel_layer = get_channel_layer()
print(auth_user.current_socket)
async_to_sync(channel_layer.group_send)(
auth_user.current_socket,{
"type": "new.request",
"service_and_email" : concatenate_service_email
},
)
My current working consumers.py (receive and scanrequest don't have anything that's likely to be relevant to the issue):
from asgiref.sync import async_to_sync
from channels.generic.websocket import WebsocketConsumer
from .helpers import verify_identity, unique_group
from django.utils import timezone
from .models import Authentication, LoginRequest
import json
import time
class AuthConsumer(WebsocketConsumer):
account_list =[]
def connect(self):
print("Connect attempted")
print(self.channel_name)
print(unique_group(self.channel_name))
async_to_sync(self.channel_layer.group_add)(unique_group(self.channel_name), self.channel_name)
self.accept()
def disconnect(self, close_code):
print("Disconnect attempted")
async_to_sync(self.channel_layer.group_discard)(unique_group(self.channel_name), self.channel_name)
for i in self.account_list:
serviceEmailSplit = i.split("-")
try:
auth_user = Authentication.objects.get(service=serviceEmailSplit[0],email=serviceEmailSplit[1])
auth_user.set_socket("NONE")
auth_user.save()
except:
print("Error user %s does not exist" %i)
pass
def receive(self, text_data):
print("Receiving data")
if text_data[0:7] == "APPROVE":
data_as_list = text_data.split(",")
serviceEmailSplit = data_as_list[1].split("-")
auth_user = Authentication.objects.get(service=serviceEmailSplit[0],email=serviceEmailSplit[1])
this_request = LoginRequest.objects.get(service_and_email=data_as_list[1],approved=False, expiry__gt=timezone.now())
if verify_identity(auth_user.public_key, data_as_list[2], this_request.challenge):
this_request.set_approved()
self.send("Request Approved!")
else:
self.send("ERROR: User verification failed")
else:
self.account_list = text_data.split(",")
self.account_list.pop(-1)
print(self.account_list)
for i in self.account_list:
serviceEmailSplit = i.split("-")
try:
auth_user = Authentication.objects.get(service=serviceEmailSplit[0],email=serviceEmailSplit[1])
auth_user.set_socket(unique_group(self.channel_name))
auth_user.save()
except:
self.send("Error user %s does not exist" %i)
self.scanRequest()
def scanRequest(self):
requestSet = LoginRequest.objects.filter(service_and_email__in = self.account_list, approved = False, request_expiry__gt = timezone.now())
if requestSet.count() > 0:
for request in requestSet:
self.send(request.service_and_email+","+request.location+","+str(request.challenge))
else:
self.send("NOREQUESTS")
def new_request(self,event):
print("NEW REQUEST!")
this_request = LoginRequest.objects.filter(service_and_email = event["service_and_email"]).latest('request_expiry')
self.send(this_request.service_and_email+","+this_request.location+","+str(this_request.challenge))
And my routing.py:
from django.urls import re_path
from . import consumers
from django.conf.urls import url
websocket_urlpatterns = [
url(r"^ws/$", consumers.AuthConsumer.as_asgi()),
]
"NEW REQUEST!" is never printed, having tried to call it both by sending a message directly, and neither does using groups like I have written above.
My redis server appears to be working from testing like the documentation for the channels tutorial suggests:
https://channels.readthedocs.io/en/stable/tutorial/part_2.html
I'm pretty stumped after attempts to fix it, and I've looked at the other posts on stackoverflow with the same/similar issues and I'm already following whatever solutions they have in my code.

how to trigger push notifications of firebase from an ios device using swift3

I am new iOS developer.
I am using firebase and I am trying to make a chat application in which I need to notify user when he/she receives any message.
For this purpose I tried firebase push notifications, but couldn't be able to trigger them when other user send the message.
The only way I found is using firebase console to send push notification, but it doesn't fulfill my requirements.
I've just configured local notifications.
Please guide me how can we trigger push notifications without using the firebase console.
Finally found the solution after struggling for almost 1 month.
These are the basic steps
Firs off all you need to make sure that you have an active apple developers account
just enable firebase push notifications
here ie the link of youtube video for this step
Now your app is set up for firebase remote notifications but we can only trigger them from the firebase console so here is the tricky part. here is the link of video to enable firebase console on your mac
for the first time it will be good to see this video too because in this video they'll learn to write code in node.js and deploy it to the firebase.
Now if anyone wants to make an API instead of making a trigger then here is a code of API which sends the notification to other user by getting his FCM token from the firebase...
You can modify it according to your need
i am finding it difficult to post code in its exact form but the code starts from here
const functions = require('firebase-functions');
const admin =require('firebase-admin');
admin.initializeApp(functions.config().firebase);
const ref=admin.database().ref();
exports.sendNotification=functions.https.onRequest((req,res) =>{
const id = req.query.id
const title = req.query.title
const message = req.query.message
const key = req.query.key
// admin.database.ref()
const payload ={
notification: {
title: title,
body: message,
//badge: '1',
sound: 'default',
}
};
console.log('Param [key] is '+key);
const keys = [] ;
ref.child('keys').once('value')
.then(snap => {
snap.forEach(childSnap => {
const loopKey=childSnap.val().key;
keys.push(loopKey);
})
return keys;
})
.then(keys=>{
//console.log('Keys in database : '+keys.join());
if(keys.indexOf(key)>-1)
{
if(!key || !id || !message)
{
res.status(400).send('Bad request');
}
else{
ref.child('users').child(id).child('fcmToken').once('value')
.then(snapshot => {
if (snapshot.val()){
const token = snapshot.val()
console.log(token);
return admin.messaging().sendToDevice(token,payload).then(response =>{
res.status(200).send('Notification sent')
});
}
});
}
}
else
{
console.log("In-valid key : "+key);
res.status(400).send('Bad request');
}
})
.catch(error => {
res.send(error)
});
});
ends at this point
this is the function to store your fcm to database
func postToken(token: String, id: String){
print("FCM Token \(token)")
let ref = Database.database().reference().child("users").child(id)
ref.child("fcmToken").setValue(token)
}
here is the function which i used to trigger this API
func sendNotification(id: String, title: String, message: String){
var url = "your URL"
var urlComponents = NSURLComponents(string: url)
urlComponents?.queryItems = [
URLQueryItem(name: "key", value: self.apiKey),
URLQueryItem(name: "id", value: id),
URLQueryItem(name: "title", value: title),
URLQueryItem(name: "message", value: message)
]
Alamofire.request((urlComponents?.url)!).responseJSON { (response) in
print(response.response)
print(response.response)
print(response.result)
}
}
the above API was written according to my database structure. you can change it easily for your own structure.
after doing this all you'll be able to send your notifications after hitting the URL
Hope it will give a nice idea to you people to work with your own notifications according to your need.
I was able to send Push Notifications from my code using PushNotificationSender class from this article -
https://www.iosapptemplates.com/blog/ios-development/push-notifications-firebase-swift-5

Can I send a silent push notification from a Firebase cloud function?

Is it possible to send a silent APNs (iOS) remote notification from a Firebase Cloud Function? If so, how can this be done? I want to send data to iOS app instances when the app is not in the foreground, without the user seeing a notification.
I currently send a notification that can be seen by users:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.sendNotifications = functions.database.ref('/events/{pushId}').onWrite(event => {
const id = event.params.pushId
const payload = {
notification: {
title: 'An event has occurred!',
body: 'Please respond to this event.',
event_id: id
}
};
return admin.messaging().sendToTopic("events", payload);
});
I would like to be able to send that id to the app without a visual notification.
I figured out how to modify my code to successfully send a silent notification. My problem was that I kept trying to put content_available in the payload, when it really should be in options. This is my new code:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.sendNotifications = functions.database.ref('/events/{pushId}').onWrite(event => {
const id = event.params.pushId
const payload = {
data: {
title: 'An event has occurred!',
body: 'Please respond to this event.',
event_id: id
}
};
const options = {
content_available: true
}
return admin.messaging().sendToTopic("events", payload, options);
});
I successfully received the silent notification on my iOS device after implementing application:didReceiveRemoteNotification:fetchCompletionHandler and userNotificationCenter:willPresent:withCompletionHandler.
If you're talking about APNs notifications, the answer is: No, you can't send a notification without visualisation. You can only disable a sound. But, you can passed an FCM Data message without visualisation. You can read about it here: https://firebase.google.com/docs/cloud-messaging/concept-options
{
"to" : "bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1...",
"data" : {
"Nick" : "Mario",
"body" : "great match!",
"Room" : "PortugalVSDenmark"
}
}

Action Cable issue: messages occasionally not delivering or occasionally sent to the wrong channel

I'm using Action Cable for a chat application with many different channels. Everything is set up with Redis and Postgresql (to save messages) on Heroku and 95% of the time works great in development and production.
Occasionally though, messages sent do not show up. The message is sent, it is saved in the database, but then it never shows up on the front end unless I refresh. Or, the message shows up in another channel for which it is not directed. Again, everything is properly saved in the Postgres database. It just gets wonky on the front end. Somewhere the ActionCable seems to get confused.
This issue happens so rarely for me, that it is very difficult to replicate to debug properly. But I have a bunch of users that are regularly reporting the issue and I'm struggling to figure out how to track it down.
Here is some of my code:
javascripts/channels/channels.js
class PodsChannel < ApplicationCable::Channel
def subscribed
stream_from "pods_channel_#{params['pod_slug']}"
end
def unsubscribed
# Any cleanup needed when channel is unsubscribed
end
def speak(data)
#after_create_commit callback fires to create a job to broadcast message
pod_message = PodMessage.create! pod_slug: data['pod_slug'], message_body: data['message'], user_id: data['user_id'], message_type: data['msg_type']
end
end
channels/pods_channel.rb
$(document).on("ready",function(){
var pod_slug = $("#pod_slug_value").val();
// hack to prevent double posting of messages
if (!App.pods || JSON.parse(App.pods.identifier).pod_slug != pod_slug){
App.pods = App.cable.subscriptions.create(
{ channel: 'PodsChannel', pod_slug: pod_slug },
{
received: function(data) {
if ( ($(".chat-stream").length) && data.pod_slug == pod_slug ){ // hack to prevent msgs going accross pods
//#CLEAN: this is a super hackish way of preventing double messages
if(!$("#msg_" + data.msg_id).length){
$(data.message).appendTo($(".chat-stream")).find('.msg-text a').attr('target', '_blank');
$("#msg_" + data.msg_id + " .msg-text").html(Autolinker.link($("#msg_" + data.msg_id + " .msg-text").html(), { mention: "sutra" }));
$(".chat-stream").scrollTop($(".chat-stream")[0].scrollHeight);
}
}
},
speak: function(message, pod_slug, user_id, msg_type) {
return this.perform('speak',{
message: message,
pod_slug: pod_slug,
user_id: user_id,
msg_type: msg_type,
});
}
});
};
if ( $(".chat-stream").length ) {
$(".chat-stream").scrollTop($(".chat-stream")[0].scrollHeight);
};
captureMessage();
});
function captureMessage(){
$(document).on('click', "#btn-submit-msg", {}, function(){
var raw_text = $("#msg-input-text").val();
if (raw_text != ""){
submitMessage(raw_text, "pod_message");
}
});
$(document).on('keydown', '#msg-input-text', {}, function(event) {
if (event.which == 13 && !event.shiftKey && !event.ctrlKey && !event.metaKey) {
event.preventDefault();
event.stopPropagation();
if (event.target.value != "") {
submitMessage(event.target.value, "pod_message")
}
}
});
}
function submitMessage(raw_text, msg_type){
var message = raw_text;
var pod_slug = $("#pod_slug_value").val();
var user_id = $("#current_user_id_value").val();
var msg_type = msg_type;
if (App.pods.consumer.connection.disconnected == false) {
App.pods.speak(message, pod_slug, user_id, msg_type);
if (msg_type != "attachment") {
$("#msg-input-text").val("");
}
}
}
models/pod_message.rb
class PodMessage < ApplicationRecord
after_create_commit { MessageBroadcastJob.perform_now self }
end
jobs/message_broadcast_job.rb
class MessageBroadcastJob < ApplicationJob
queue_as :default
def perform(pod_message)
stream_id = "pods_channel_#{pod_message.pod_slug}"
ActionCable.server.broadcast stream_id, message: render_message(pod_message), msg_id: pod_message.id, pod_slug: pod_message.pod_slug
end
private
def render_message(pod_message)
renderer = ApplicationController.renderer.new
renderer.render(partial: 'pod_messages/pod_message', locals: { pod_message: pod_message })
end
end

Atmosphere Grails single user broadcast

I'm creating a Grails application which makes use of the Atmosphere plugin to push data to the browser. However I'm having trouble in creating a broadcast channel to a single user (the session's user). My code is has follows:
Service:
static atmosphere = [mapping: '/atmosphere/recommend']
def onRequest = { event ->
def request = event.request
def response = event.response
event.suspend()
def broadcaster = event.broadcaster
request.session.broadcaster = broadcaster
broadcaster.broadcasterConfig.addFilter(new XSSHtmlFilter())
}
def onStateChange = { event ->
if (!event.message) return
event.resource.response.writer.with {
write "<script>parent.callback('${event.message}');</script>"
flush()
}
}
Controller:
def builder = new JSONBuilder()
def jsonResult = builder.build{
artist = artistInstance
location = {
lat = eventInstance.location.lat
lng = eventInstance.location.lng
}
}
session.broadcaster.broadcast(jsonResult)
This solution broadcasts the jsonResult to every user. What I want to achieve is to broadcast only for the current user.
Any ideas?
If you need more details just let me know.
Thanks
I thinks you can use session to share the onRequest's event.
request.session[userId]=event
then in controller :
broadcast(jsonResult,session[userId])
When you define your events in grails, you can filter out cients by using a browserFilter closure.
Hey you can make use of the uuid which is assigned to each Atmosphere Resource.
To retrieve the suspended uuid based on an Atmosphere Request you can do:
String suspendedUUID = (String)req.getAttribute(ApplicationConfig.SUSPENDED_ATMOSPHERE_RESOURCE_UUID);
You can pass the session
session.broadcaster.broadcast(jsonResult, AtmosphereResource)
See this API.
(I am Atmosphere's creator).

Resources