I am using the following stack with versions
Laravel (9.11) vue.js (2.x) php (8.1.0) twilio/voice-sdk
(2.1.1) twilio/sdk (6.37)
Workflow of my application:
I am making an inbound contact center for voice calls by using a task router, where a customer initiates the call from his/her phone to our contact center base number(+1 873 --- 0331)
Step #1
when the user call on this number(+1 873 --- 0331) voice webhook is called with the following code for IVR
public function webhookForContactCenterBaseNumber(Request $request)
{
$response = new VoiceResponse();
$params = array();
$params['action'] = secure_url('/api/webhook-for-contact-center-ivr');
$params['numDigits'] = 1;
$params['timeout'] = 10;
$params['method'] = "POST";
$gather = $response->gather($params);
$gather->say('For Spanish, please press one.', ['language' => 'es']);
$gather->say('For Enghlish,please press two.', ['language' => 'en']);
return $response;
}
Step #2
When the user presses A digit(1/2) I create a task with workflow via the task router
public function webhookForContactCenterIvr(Request $request)
{
$response = new VoiceResponse();
$digits = $request['Digits'];
$language = $digits == 1 ? 'es' : 'en';
switch ($digits) {
case 1 || 2:
$response->enqueue(null, [
'waitUrl' => 'http://twimlets.com/holdmusic?Bucket=com.twilio.music.classical',
'workflowSid' => 'WW456fb07f4fdc4f55779dcb6bd90f9273'
])
->task(json_encode([
'selected_language' => $language,
]));
break;
default:
$response->say("Sorry, Caller. You can only press 1 for spanish, or 2 for english.");
break;
}
return $response;
}
step #3
After task creation, I make the targeted agent available manually from the console with the label ‘idle’, then following webhook called.
According to documentation bridge call was created between caller and agent Twilio phone number via Twilio caller id
public function assigment(Request $request)
{
$assignment_instruction = [
'instruction' => 'dequeue',
'post_work_activity_sid' => 'WA92871fe67075e6556c02e92de6---924',
'from' => '+1647---4676' // a verified phone number from your twilio account
];
return $this->respond($assignment_instruction, ['Content-Type', 'application/json']);
}
Call logs:
step #4
namespace App\Http\Controllers\Api;
use Twilio\Jwt\AccessToken;
use Twilio\Jwt\Grants\VoiceGrant;
use Illuminate\Http\Request;
use Twilio\Rest\Client;
class TwilioController extends ApiController
{
// Required for all Twilio access tokens
private $twilioAccountSid;
private $twilioAccountAuthToken;
private $twilioApiKey;
private $twilioApiSecret;
private $identity;
public function __construct()
{
$this->twilioAccountSid = config('general.twilio_account_sid');
$this->twilioAccountAuthToken = config('general.twilio_auth_token');
$this->twilioApiKey = 'SK45e57c57f923e5c3c0903f48b70ba9de';
$this->twilioApiSecret = 'uqDNnlnDZbWZCKBwlmMdlMIIonhh3X3K';
// choose a random username for the connecting user
$this->identity = 'daffdfwerweds';
}
public function getCallAccessToken()
{
$token = new AccessToken(
$this->twilioAccountSid,
$this->twilioApiKey,
$this->twilioApiSecret,
3600,
$this->identity
);
// Create Voice grant
$voiceGrant = new VoiceGrant();
// Optional: add to allow incoming calls
$voiceGrant->setIncomingAllow(true);
// Add grant to token
$token->addGrant($voiceGrant);
return $this->respond([
'status' => true,
'message' => '',
'data' => [
'accessToken' => $token->toJWT()
]
]);
}
public function getTwilioKey($frindlyName)
{
$twilio = new Client($this->twilioAccountSid, $this->twilioAccountAuthToken);
return $twilio->newKeys->create(["friendlyName" => $frindlyName]);
}
public function getKeys()
{
$twilio = new Client($this->twilioAccountSid, $this->twilioAccountAuthToken);
$keys = $twilio->keys
->read(20);
foreach ($keys as $record) {
$twilio->keys($record->sid)
->delete();
}
}
public function getAllCalls(Request $request)
{
$twilio = new Client($this->twilioAccountSid, $this->twilioAccountAuthToken);
$calls = $twilio->calls
->read([], 20);
foreach ($calls as $record) {
// print($record->sid);
$twilio->calls($record->sid)
->delete();
}
}
}
Step #5
I have installed twilio/voice-sdk in vue and register my device with following code
const accessToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImN0eSI6InR3aWxpby1mcGE7dj0xIn0.eyJqdGkiOiJTSzQ1ZTU3YzU3ZjkyM2U1YzNjMDkwM2Y0OGI3MGJhOWRlLTE2NTU3MzgxNjMiLCJpc3MiOiJTSzQ1ZTU3YzU3ZjkyM2U1YzNjMDkwM2Y0OGI3MGJhOWRlIiwic3ViIjoiQUMwMWExYTRmMDdjMGMwMDlhMmIyZTEyYmJkZWVhYjQ2NSIsImV4cCI6MTY1NTc0MTc2MywiZ3JhbnRzIjp7ImlkZW50aXR5IjoiZGFmZmRmd2Vyd2VkcyIsInZvaWNlIjp7ImluY29taW5nIjp7ImFsbG93Ijp0cnVlfX19fQ.4COIn-EQMQnD6alKUSOZPGIWG3jB5k17K418xCsSiZs"
const device = new Device(accessToken, {
logLevel: 1,
// Set Opus as our preferred codec. Opus generally performs better, requiring less bandwidth and
// providing better audio quality in restrained network conditions.
codecPreferences: ["opus", "pcmu"]
});
const handleSuccessfulRegistration = () => {
console.log('The device is ready to receive incoming calls.')
}
device.register();
device.on('registered', handleSuccessfulRegistration);
device.on('error', (twilioError, call) => {
console.log('An error has occurred: ', twilioError);
});
device.on('incoming', call => {
console.log('call received-----------------')
});
Verify token on jwt.io
Test Device Registration in console:
I was facing the same issue, thanks for detailed information, I go through the whole detail, and here is the answer after that issue will be fixed,
in step #4 you are creating call access token, and you are adding worker/agent identity you need to add some identity against the worker inside the Twilio console, in your case, it should be like that,
in code
$this->identity = 'daffdfwerweds';
in Twilio console under task router/workspace/workers/open target work
most important part
{contact_uri":"client:daffdfwerweds"}
Your browser will listen the incoming call via SDK if call router toward you this worker.
that's all.
Twilio function Problem: Tags are not working as expected.
I'm trying to create unique SMS subscription lists for users who SMS 'keyword1','keyword2','keyword3',etc to my number.
I'm doing this by intercepting the 'incoming message' events via a classic function.
I want to poll the keywords then assign tags to the users as I subscribe them to a list.
Then when I broadcast SMS I want to be able to send to only those users who are tagged with my keyword.
For example to capture the keyword 'test' and assign the tag 'test' to a user, I'm using the code below.
And then to send messages to all users with the tag 'test' I'm using a "sendtest" command.
I can broadcast to 'all' tags and it will work fine, but if I want to only send to users with tags ['test'], then there are no errors reported and the system tells me it was successful, but no subscribers will receive any messages.
I'm wondering if I have some problem in the way I am trying to define the tags? It looks like the data format is supposed to be a STRING[] array of some kind, I'm guessing ['test','two','three']. (If I can confirm this is right). But I notice as per the working examples provided by twilio if I set the notification arguments to a string IE: tags: 'all', then this syntax works to broadcast to all tags. Anything else though, will not work at all.
is there some trick to getting tags to work, or do they not work at all when trying to filter notifications via the classic function interface?
class TestCommand extends Command {
run(callback) {
// Create a new SMS Notify binding for this user's phone number
//and try to tag the user with keyword 'test'
notify.bindings.create({
identity: this.fromNumber,
bindingType: 'sms',
address: this.fromNumber,
tags: ['test']
}).then((response) => {
callback(null, 'test Message success')
}).catch(err => {
callback(err, 'test message fail')
})
}
}
class BroadcastTestCommand extends Command {
run(callback) {
// Check if sender is in list of admins, stored in the system environment
// as a comma-separated string
if (adminNumbers.indexOf(this.fromNumber) < 0) {
return callback(null, 'broadcast Not Authorized')
}
// Create a new SMS Notify binding for this user's phone number
//only notify users who are tagged with 'test'
notify.notifications.create({
tag: ['test'],
body: this.commandText
}).then((response) => {
callback(null, 'broadcast test Success')
}).catch(err => {
console.log(err)
callback(err, 'broadcast test Fail')
})
}
}
// Handle incoming SMS commands ####################
exports.handler = (context, event, callback) => {
// Get command text from incoming SMS body
let cmd = event.Body || ''
cmd = cmd.trim().split(' ')[0].toLowerCase()
// Default to help command
let cmdInstance = new HelpCommand(event, context)
// Choose other commands as appropriate
switch(cmd) {
case 'test': cmdInstance = new TestCommand(event, context); break;
case 'sendtest': cmdInstance = new BroadcastTestCommand(event, context); break;
}
// Execute command
cmdInstance.run((err, message) => {
let twiml = new twilio.twiml.MessagingResponse()
if (err) {
console.log(err)
message = 'There was a problem with your request. Try again!'
}
twiml.message(message)
callback(null, twiml)
})
}
Okay for anyone else pulling their hair out on this one..
The Twilio docs here on bindings will throw you off
https://www.twilio.com/docs/notify/api/binding-resource
Because the argument is NOT 'tags' it's 'tag'
This works ...
class TestCommand extends Command {
run(callback) {
// Create a new SMS Notify binding for this user's phone number
//and try to tag the user with keyword 'test'
notify.bindings.create({
identity: this.fromNumber,
bindingType: 'sms',
address: this.fromNumber,
tag: ['test']
}).then((response) => {
callback(null, 'test Message success')
}).catch(err => {
callback(err, 'test message fail')
})
}
}
After searching the docs I could not find any info on how to send device to device messages using FCM without the use of an external server.
For example, if I was creating a chat application I would need to send push notifications to users about unread messages since they won't be online all the time and I can't have a persistent service in the background that would always be connected to the real time database because that would be too resource heavy.
So how would I send a push notification to a user "A" when a certain user "B" sends him/her a chat message? Do I need an external server for this or can it be done with just Firebase servers?
UPDATE: It is now possible to use firebase cloud functions as the server for handling push notifications. Check out their documentation here
============
According to the docs you must implement a server for handling push notifications in device to device communication.
Before you can write client apps that use Firebase Cloud Messaging, you must have an app server that meets the following criteria:
...
You'll need to decide which FCM connection server protocol(s) you want to use to enable your app server to interact with FCM connection servers. Note that if you want to use upstream messaging from your client applications, you must use XMPP. For a more detailed discussion of this, see Choosing an FCM Connection Server Protocol.
If you only need to send basic notifications to your users from the server. You can use their serverless solution, Firebase Notifications.
See a comparison here between FCM and Firebase Notifications:
https://firebase.google.com/support/faq/#messaging-difference
Making a HTTP POST request with the link https://fcm.googleapis.com/fcm/send with required header and data helped me. In the below code snippet
Constants.LEGACY_SERVER_KEY is a local class variable, you can find this at your Firebase Project Settings->Cloud Messaging->Legacy Server key. You need to pass device registration token i.e. regToken in below code snippet referenced HERE.
At last you need okhttp library dependency in order to get this snippet work.
public static final MediaType JSON
= MediaType.parse("application/json; charset=utf-8");
private void sendNotification(final String regToken) {
new AsyncTask<Void,Void,Void>(){
#Override
protected Void doInBackground(Void... params) {
try {
OkHttpClient client = new OkHttpClient();
JSONObject json=new JSONObject();
JSONObject dataJson=new JSONObject();
dataJson.put("body","Hi this is sent from device to device");
dataJson.put("title","dummy title");
json.put("notification",dataJson);
json.put("to",regToken);
RequestBody body = RequestBody.create(JSON, json.toString());
Request request = new Request.Builder()
.header("Authorization","key="+Constants.LEGACY_SERVER_KEY)
.url("https://fcm.googleapis.com/fcm/send")
.post(body)
.build();
Response response = client.newCall(request).execute();
String finalResponse = response.body().string();
}catch (Exception e){
//Log.d(TAG,e+"");
}
return null;
}
}.execute();
}
further if you want to send message to a particular topic, replace regToken in json like this
json.put("to","/topics/foo-bar")
and don't forget to add INTERNET permission in your AndroidManifest.xml.
IMPORTANT : - Using above code means your server key resides in the client application. That is dangerous as someone can dig into your application and get the server key to send malicious notifications to your users.
You can do it using Volly Jsonobject request....
follow this Steps first:
1 copy legacy server key and store it as Legacy_SERVER_KEY
Legacy Server key
you can see in picture how to get
2 You need Volley dependency
compile 'com.mcxiaoke.volley:library:1.0.19'
Code for send Push:-
private void sendFCMPush() {
String Legacy_SERVER_KEY = YOUR_Legacy_SERVER_KEY;
String msg = "this is test message,.,,.,.";
String title = "my title";
String token = FCM_RECEIVER_TOKEN;
JSONObject obj = null;
JSONObject objData = null;
JSONObject dataobjData = null;
try {
obj = new JSONObject();
objData = new JSONObject();
objData.put("body", msg);
objData.put("title", title);
objData.put("sound", "default");
objData.put("icon", "icon_name"); // icon_name image must be there in drawable
objData.put("tag", token);
objData.put("priority", "high");
dataobjData = new JSONObject();
dataobjData.put("text", msg);
dataobjData.put("title", title);
obj.put("to", token);
//obj.put("priority", "high");
obj.put("notification", objData);
obj.put("data", dataobjData);
Log.e("!_#rj#_##_PASS:>", obj.toString());
} catch (JSONException e) {
e.printStackTrace();
}
JsonObjectRequest jsObjRequest = new JsonObjectRequest(Request.Method.POST, Constants.FCM_PUSH_URL, obj,
new Response.Listener<JSONObject>() {
#Override
public void onResponse(JSONObject response) {
Log.e("!_##_SUCESS", response + "");
}
},
new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
Log.e("!_##_Errors--", error + "");
}
}) {
#Override
public Map<String, String> getHeaders() throws AuthFailureError {
Map<String, String> params = new HashMap<String, String>();
params.put("Authorization", "key=" + Legacy_SERVER_KEY);
params.put("Content-Type", "application/json");
return params;
}
};
RequestQueue requestQueue = Volley.newRequestQueue(this);
int socketTimeout = 1000 * 60;// 60 seconds
RetryPolicy policy = new DefaultRetryPolicy(socketTimeout, DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT);
jsObjRequest.setRetryPolicy(policy);
requestQueue.add(jsObjRequest);
}
Just Call sendFCMPush();
1) subscribe an identical topic name, for example:
ClientA.subcribe("to/topic_users_channel")
ClientB.subcribe("to/topic_users_channel")
2) send messages inside the application
GoogleFirebase : How-to send topic messages
Yes, it's possible to do it without any server. You can create a device group client side and then you exchange messages in the group. However there are limitations:
You have to use the same Google account on the devices
You can't send high priority messages
Reference: Firebase doc See the section "Managing device groups on Android client apps"
Google Cloud Functions make it now possible send push notifications from device-to-device without an app server.
I have made cloud function which is trigger when new message is added in database
It is node.js code
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin'); admin.initializeApp();
exports.sendNotification = functions.database.ref('/conversations/{chatLocation}/{messageLocation}')
.onCreate((snapshot, context) => {
// Grab the current value of what was written to the Realtime Database.
const original = snapshot.val();
const toIDUser = original.toID;
const isGroupChat = original.isGroupChat;
if (isGroupChat) {
const tokenss = admin.database().ref(`/users/${toIDUser}/tokens`).once('value').then(function(snapshot) {
// Handle Promise
const tokenOfGroup = snapshot.val()
// get tokens from the database at particular location get values
const valuess = Object.keys(tokenOfGroup).map(k => tokenOfGroup[k]);
//console.log(' ____________ddd((999999ddd_________________ ' + valuess );
const payload = {
notification: {
title: original.senderName + " :- ",
body: original.content
}
};
return admin.messaging().sendToDevice(valuess, payload);
}, function(error) {
console.error(error);
});
return ;
} else {
// get token from the database at particular location
const tokenss = admin.database().ref(`/users/${toIDUser}/credentials`).once('value').then(function(snapshot) {
// Handle Promise
// The Promise was "fulfilled" (it succeeded).
const credentials = snapshot.val()
// console.log('snapshot ......snapshot.val().name****^^^^^^^^^^^^kensPromise****** :- ', credentials.name);
//console.log('snapshot.....****snapshot.val().token****^^^^^^^^^^^^kensPromise****** :- ', credentials.token);
const deviceToken = credentials.token;
const payload = {
notification: {
title: original.senderName + " :- ",
body: original.content
}
};
return admin.messaging().sendToDevice(deviceToken, payload);
}, function(error) {
console.error(error);
});
}
return ;
});
Google Cloud Functions make it now possible send push notifications from device-to-device without an app server.
From the relevant page on Google Cloud Functions:
Developers can use Cloud Functions to keep users engaged and up to
date with relevant information about an app. Consider, for example, an
app that allows users to follow one another's activities in the app.
In such an app, a function triggered by Realtime Database writes to
store new followers could create Firebase Cloud Messaging (FCM)
notifications to let the appropriate users know that they have gained
new followers.
Example:
The function triggers on writes to the Realtime Database path where followers are stored.
The function composes a message to send via FCM.
FCM sends the notification message to the user's device.
Here is a demo project for sending device-to-device push notifications with Firebase and Google Cloud Functions.
You can use firebase realtime database to do so. You can create data structure for storing chats and add observers for the conversation threads for both users. It still does device - server - device architecture, but in this case there is no additional server on the developers' part. This uses the firebase servers. You can check out a tutorial here (ignore the UI part, although, that is also a good starting point for chat UI frameworks).
Firebase Realtime Chat
If you have fcm(gcm) token of the device to whom you want to send notification. It's just a post request to send the notification.
https://github.com/prashanthd/google-services/blob/master/android/gcm/gcmsender/src/main/java/gcm/play/android/samples/com/gcmsender/GcmSender.java
In my case I use retrofit with this class Message:
public class Message {
private String to;
private String collapseKey;
private Notification notification;
private Data data;
public Message(String to, String collapseKey, Notification notification, Data data) {
this.to = to;
this.collapseKey = collapseKey;
this.notification = notification;
this.data = data;
}
}
Data
public class Data {
private String body;
private String title;
private String key1;
private String key2;
public Data(String body, String title, String key1, String key2) {
this.body = body;
this.title = title;
this.key1 = key1;
this.key2 = key2;
}
}
Notification
public class Notification {
private String body;
private String title;
public Notification(String body, String title) {
this.body = body;
this.title = title;
}
}
this the call
private void sentToNotification() {
String to = "YOUR_TOKEN";
String collapseKey = "";
Notification notification = new Notification("Hello bro", "title23");
Data data = new Data("Hello2", "title2", "key1", "key2");
Message notificationTask = new Message(to, collapseKey, notification, data);
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://fcm.googleapis.com/")//url of FCM message server
.addConverterFactory(GsonConverterFactory.create())//use for convert JSON file into object
.build();
ServiceAPI api = new retrofit.create(ServiceAPI.class);
Call<Message> call = api .sendMessage("key=YOUR_KEY", notificationTask);
call.enqueue(new Callback<Message>() {
#Override
public void onResponse(Call<Message> call, retrofit2.Response<Message> response) {
Log.d("TAG", response.body().toString());
}
#Override
public void onFailure(Call<Message> call, Throwable t) {
Log.e("TAG", t.getMessage());
}
});
}
our ServiceAPi
public interface ServiceAPI {
#POST("/fcm/send")
Call<Message> sendMessage(#Header("Authorization") String token, #Body Message message);
}
You can use Retrofit. Subscribe devices to topic news. Send notification from one device to other.
public void onClick(View view) {
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
httpClient.addInterceptor(new Interceptor() {
#Override
public okhttp3.Response intercept(Chain chain) throws IOException {
Request original = chain.request();
// Request customization: add request headers
Request.Builder requestBuilder = original.newBuilder()
.header("Authorization", "key=legacy server key from FB console"); // <-- this is the important line
Request request = requestBuilder.build();
return chain.proceed(request);
}
});
httpClient.addInterceptor(logging);
OkHttpClient client = httpClient.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://fcm.googleapis.com")//url of FCM message server
.client(client)
.addConverterFactory(GsonConverterFactory.create())//use for convert JSON file into object
.build();
// prepare call in Retrofit 2.0
FirebaseAPI firebaseAPI = retrofit.create(FirebaseAPI.class);
//for messaging server
NotifyData notifydata = new NotifyData("Notification title","Notification body");
Call<Message> call2 = firebaseAPI.sendMessage(new Message("topic or deviceID", notifydata));
call2.enqueue(new Callback<Message>() {
#Override
public void onResponse(Call<Message> call, Response<Message> response) {
Log.d("Response ", "onResponse");
t1.setText("Notification sent");
}
#Override
public void onFailure(Call<Message> call, Throwable t) {
Log.d("Response ", "onFailure");
t1.setText("Notification failure");
}
});
}
POJOs
public class Message {
String to;
NotifyData notification;
public Message(String to, NotifyData notification) {
this.to = to;
this.notification = notification;
}
}
and
public class NotifyData {
String title;
String body;
public NotifyData(String title, String body ) {
this.title = title;
this.body = body;
}
}
and FirebaseAPI
public interface FirebaseAPI {
#POST("/fcm/send")
Call<Message> sendMessage(#Body Message message);
}
Here is walk around how to get notifications without second server apart from the Firebase one. So we use Firebase only, without additional server.
At the mobile app code, we create its own notifications function by Android libraries like here, not using Firebase libraries like here, without Firebase Cloud messaging.
Here is an example with Kotlin:
private fun notification() {
createNotificationChannel()
val intent = Intent(this, LoginActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
val pendingIntent: PendingIntent = PendingIntent.getActivity(this, 0, intent, 0)
val notificationBuilder = NotificationCompat.Builder(this, "yuh_channel_id")
.setSmallIcon(R.drawable.ic_send)
.setContentText("yuh")
.setContentText("yuh")
.setAutoCancel(true)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setContentIntent(pendingIntent)
val notificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.notify(0, notificationBuilder.build())
with(NotificationManagerCompat.from(this)) {
// notificationId is a unique int for each notification that you must define
notify(0, notificationBuilder.build())
}
}
private fun createNotificationChannel() {
// Create the NotificationChannel, but only on API 26+ because
// the NotificationChannel class is new and not in the support library
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val name = "yuh_channel"
val descriptionText = "yuh_description"
val importance = NotificationManager.IMPORTANCE_DEFAULT
val CHANNEL_ID = "yuh_channel_id"
val channel = NotificationChannel(CHANNEL_ID, name, importance).apply {
description = descriptionText
}
// Register the channel with the system
val notificationManager: NotificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}
In the Firebase database, create collection "pending notifications". Documents should contain user name (to send notification to) and source name (where should user go upon tapping the notification).
In the app code, implement option for adding new records to the Pending Notifications collection. E. g. if user A sends message to user B, then the document with the id of user B (who will be notified) is created in the collection.
In the app code, set up background (when the app is not visible to the user) service. Like here. In the background service, set up a listener for changes in the "Notifications Pending" collection. When the new record with the user id comes to the collection, call the notification function created in the paragrath 1 supra and delete the consequent record from the collection.
So I had an idea here. See: If the FCM, as well as the GCM, has a endpoit to http request where we can send a post json with our message data, including the token (s) of devices that we want this message to be delivered.
So why not send a post to Firebase server with this notification to be delivered to user B? you understand ?
So, you send the message and chat with a call post to ensure delivery of the notification if the user is with your app in the background. I am also in need of it soon, I will test later. What do you say about?
Simplest way :
void sendFCMPush(String msg,String token) {
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
httpClient.addInterceptor(new Interceptor() {
#Override
public okhttp3.Response intercept(Chain chain) throws IOException {
Request original = chain.request();
// Request customization: add request headers
Request.Builder requestBuilder = original.newBuilder()
.header("Authorization", "key="+Const.FIREBASE_LEGACY_SERVER_KEY); // <-- this is the important line
Request request = requestBuilder.build();
return chain.proceed(request);
}
});
httpClient.addInterceptor(logging);
OkHttpClient client = httpClient.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://fcm.googleapis.com/")//url of FCM message server
.client(client)
.addConverterFactory(GsonConverterFactory.create())//use for convert JSON file into object
.build();
// prepare call in Retrofit 2.0
FirebaseAPI firebaseAPI = retrofit.create(FirebaseAPI.class);
//for messaging server
NotifyData notifydata = new NotifyData("Chatting", msg);
Call<Message> call2 = firebaseAPI.sendMessage(new Message(token, notifydata));
call2.enqueue(new Callback<Message>() {
#Override
public void onResponse(Call<Message> call, retrofit2.Response<Message> response) {
Log.e("## SUCCES #E$#", response.body().toString());
}
#Override
public void onFailure(Call<Message> call, Throwable t) {
Log.e("E$ FAILURE E$#", t.getMessage());
}
});
}
Create Class to make Object:
public class Message {
String to;
NotifyData data;
public Message(String to, NotifyData data) {
this.to = to;
this.data = data;
}
}
Create Class to make Object:
public class Notification {
String title;
String message;
enter code here`enter code here`
public Notification(String title, String message) {
this.title = title;
this.message = message;
}
}
// didn't add node definitions because this problem occurs on initial data fetch
// user type
const userType = new GraphQLObjectType({
name: 'User',
fields: () => ({
id: globalIdField('User'),
email: { type: GraphQLString },
posts: {
type: postConnection,
args: connectionArgs,
// getUserPosts() function is below
resolve: (user, args) => connectionFromArray(getUserPosts(user.id), args),
},
}),
interfaces: [nodeInterface],
})
// post type
const postType = new GraphQLObjectType({
name: 'Post',
fields: () => ({
id: globalIdField('Post'),
title: { type: GraphQLString },
content: { type: GraphQLString },
}),
interfaces: [nodeInterface],
})
// connection type
const {connectionType: postConnection} =
connectionDefinitions({name: 'Post', nodeType: postType})
// Mongoose query on other file
exports.getUserPosts = (userid) => {
return new Promise((resolve, reject) => {
Post.find({'author': userid}).exec((err, res) => {
err ? reject(err) : resolve(res)
})
})
}
I get the following warning in browser console:
Server request for query App failed for the following reasons:
Cannot read property 'length' of undefined
_posts40BFVD:posts(first:10) {
^^^
That's the only information I got, there's no more errors or references. What could be the reason?
This code is from relay-starter-kit, I only replaced all the Widget code with Post. Everything is almost the same as in starter, therefore I think the cause is somewhere around the database code.
But I can't see the problem because getUserPosts() returns same structure: array of objects..
What was the problem?
resolve: (user, args) => connectionFromArray(getUserPosts(user.id), args)
getUserPosts() returned a promise (blocking code would probably be a bad idea) but there was no callback. What happened in my opinion was that connectionFromArray() continued executing the code but it didn't have the data from getUserPosts() yet, which caused the whole system to fail.
One possible solution
I use Babel and JavaScript "future features" anyway, therefore I decided to use async and await. But there still was a problem and getUserPosts() returned an empty array.
Then I discovered that if another function is called with await in a async function, that another function has to be async as well, otherwise all await-s fail. Here's my final solution that works:
// async function that makes db query
exports.getUserPosts = async (userId) => {
try {
// db query
const posts = await Post.find({author: args}).exec()
return posts
} catch (err) {
return err
}
}
// and resolve method in Schema, also async
resolve: async (user, args) => {
const posts = await getUserPosts(user._id)
return connectionFromArray(posts, args)
}
Im not still sure though if it's the best way. My logic tells me that I should use async as much as possible in Node but Im far from Node expert. I'll update the question when I know more about it.
I would be glad to know if there's a better or even a recommended way to deal with this database query situation using Relay.
The problem is with the resolve function of posts field.
resolve: (user, args) => connectionFromArray(getUserPosts(user.id), args),
First, getUserPosts is an asynchronous function which returns a promise. You have to take that into account while writing resolve function. Second, user.id is a global ID field generated for you by graphql-relay module's globalIdField helper function. That ID should be converted to local ID.
With relay-starter-kit, you can use async and await. The code will look like:
resolve: async (user, args) => {
let userId = fromGlobalId(user.id).id;
// convert this userId string to Mongo ID if needed
// userId = mongoose.Types.ObjectId(userId).
const posts = await getUserPosts(userId);
return connectionFromArray(posts, args),
},
In your async version of getUserPosts, the err object is returned in case of error.
exports.getUserPosts = async (userId) => {
try {
// db query
const posts = await Post.find({author: args}).exec()
return posts
} catch (err) {
return err
}
}
A good practice is to either re-throw the error or return an empty array.
graphql-relay provides a connectionFromPromisedArray function which waits until the promise resolves. resolve: (user, args) => connectionFromPromisedArray(getUserPosts(user.id), args), which probably is the most recommended/easiest way when dealing with promised connections.
If your user.id is a global ID field then you have to extract the real DB ID from the base64 string const { type, id } = fromGlobalId(user.id);.
or
resolve: (user, args) => connectionFromPromisedArray(getUserPosts(fromGlobalId(user.id).id), args),
In a app I work on which is using Relay and Mongoose I found it better to write my own connection implementation to handle pagination and filtering on the DB level rather than on the app level. I took a lot of inspiration from graffiti-mongoose when I wrote it.