Firebase Cloud Messaging

Hello,
Firebase API Cloud Messaging (V1) will no longer be available on 20/06/2024.
I am using this code to send notifications in my app :

	local url = "https://fcm.googleapis.com/fcm/send"
	local parameters =
	{
		headers =
		{
			["Authorization"] = "Key=xxx",
			["Content-Type"] = "application/json",
		},
		body = msgTblJson,
	}

	network.request (url, "POST", onSendNotification, parameters)					

Anyone knows what should be modified to update to the new version of FCM?

1 Like

Anyone managed to get the Firebase Cloud Messaging API (V1) working?

Using this url , “https://fcm.googleapis.com/v1/projects/{project_id}/messages:send”
But no success.

Anyone managed to use the new FCM API V1 ?

Hi LourencoA,

Were you ever able to get this resolved?

I’m not sure if the OP ever got this going, so this is for future readers. It is quite lengthy, but I was able to get this working utilizing a webserver and Node js. A little background, I mainly need notifications for player-to-player turn-based notifications, so while this approach works for that and should still work for sending mass notifications, there are certainly better ways to accomplish mass notifications than this one. I am also not a Node js person, so I am sure there is a better way to do this as well! Most of it came from online sources.

Main steps:

  1. Create a js script that runs every 45 minutes on the webserver that trades your google credentials for an oauth2 authorization token that is then stored in a file on the webserver.
  2. Setup a listening Node js service that will authenticate client requests via (API_KEY/API_SECRET) and allow the download of the file from above.
  3. App retrieves this file every time it needs to send a push notification.

I store the entire project and Node js itself in a folder called /srv/googleAuth on the webserver. (I am using Debian 12).

First, create a “.env” file in the project folder that contains nothing but your made-up api key and secret. No spaces or anything else. Much documentation on the web concerning “.env” files.

API_KEY=YOUR_API_KEY
API_SECRET=YOUR_API_SECRET

Next, create a Node js script to make the request to Google for the authorization token. I call this script “reqAuth.js”. The oauth2 token expires in about an hour, so run this script every 45 minutes or so (cron) to keep it viable.

// this script will get an oauth2 token from Google and store this token in a file on the webserver. 
const { GoogleAuth } = require('google-auth-library');
const fs = require('fs');
const filePath = '/srv/googleAuth/secure/authToken.txt';   // path to where the google token will be stored

async function getAccessToken() {
     const scopes = ['https://www.googleapis.com/auth/firebase.messaging']; 
     const keyFilePath = '/srv/googleAuth/serviceAccountKeys.json';     // location of your stored google credentials for firebase.
  
     const auth = new GoogleAuth({
          keyFile: keyFilePath,
          scopes: scopes
});

try {
    const client = await auth.getClient();
    const accessToken = await client.getAccessToken();
    // console.log('Access Token:', accessToken.token);
    
    fs.writeFile(filePath, accessToken.token, (err) => {
        if (err) {
             console.error('Error writing file:', err);
        } else {
         //    console.log('Variable successfully stored in file and overwritten.');
        }
   });
return accessToken.token;
} catch (error) {
    console.error('Authentication error:', error);
   }
}

getAccessToken();

Now that we have the oauth2 token refreshing every 45 minutes, let’s setup a Node js service that listens for incoming requests from clients who are seeking the oauth2 token. I call this script “authServer.js” and it is loaded on bootup.

require('/srv/googleAuth/node_modules/dotenv').config({ path: '/srv/googleAuth/.env' });
const https = require('https');

// Node.js file system module
const fs = require('fs'); 
const path = require('path');
const sslOptions = {
  key: fs.readFileSync('/etc/letsencrypt/live/<MY_WEBSITE>/privkey.pem'),
  // Important, use your full chain next and not your intermediate.
  cert: fs.readFileSync('/etc/letsencrypt/live/<MY_WEBSITE>/fullchain.pem') 
};

const API_KEY = process.env.API_KEY;
const API_SECRET = process.env.API_SECRET;
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;

// middleware to validate api key
function authenticateApi(req, res, next) {
  const apiKey = req.headers['x-api-key'];
  const apiSecret = req.headers['x-api-secret'];

  // Check if both key and secret are provided and match
  if (apiKey && apiSecret && apiKey === API_KEY && apiSecret === API_SECRET) {
    next(); // Credentials are valid, proceed to the next handler
  } else {
    // Authentication failed
    res.status(401).send('Unauthorized: Invalid API credentials');
  }
}

// Apply the authentication middleware to a specific route or globally
app.use(authenticateApi);

// I use "/srv/googleAuth/secure" to store the authenticated google token, so it needs to be secured.
app.get('/secure', (req, res) => {
    const secretInfo = `The secret is: ${process.env.API_SECRET}`; 
    res.json({ 
        message: 'This is secure data, only accessible with a valid API key.',
        info: secretInfo
    });
});

// Now secure endpoint to request a specific file. This is the file that contains the token.
app.get('/secure/:filename', (req, res) => {
    const filename = req.params.filename;
    const filePath = path.join(__dirname, 'secure', filename);

    // Security checks:
    // 1. Check if the file exists
    if (!fs.existsSync(filePath)) {
        return res.status(404).send('File not found.');
    }
    
    // 2. Ensure the file requested is within the designated secure directory
    // This prevents "path traversal" attacks
    if (!filePath.startsWith(path.join(__dirname, 'secure'))) {
      return res.status(403).send('Forbidden: Access denied to this file path.');
    }

    // Use express's built-in download method to send the file securely
    res.download(filePath, (err) => {
        if (err) {
            console.error('File download error:', err);
            res.status(500).send('Could not download the file.');
        }
    });
});

// Create the HTTPS server
const server = https.createServer(sslOptions, app);

server.listen(PORT, () => {
    console.log(`HTTPS Server running on https://localhost:${PORT}`);
});

Finally, in my solar2d app I download the token from my webserver first, then pair that token with my push request.

local json = require("json")
local network = require("network")
local ProjId = "<your_google_project_id>"
local OAUTH_API_KEY = "<YOUR_API_KEY>"
local OAUTH_API_SECRET = "<YOUR_API_SECRET>"
local SERVER_URL = "https://<your web address>:3000/secure/authToken.txt"
local deviceToken="rtgQX7qRTjyorQa19PsFPR:APA91bFdOWhNQbPbJyLNCbIdv1OFMra15hPky0mQ53yYbXMdQQjyOhx45BJYHEuwykaxt2dZaRtkvheAHco6RUJ_Ht-oRoZggInAfb8UoUxiMUPmYn4mQgk" 

-- function to listen for response and send the push if successful 
local function networkListener(event)
	if event.isError then
		print("Network error: " .. event.status)
	else
		-- oauth token retrieval was successful so send push notification
		if event.status == 200 then
			oauthToken=event.response
			local title = "Your Notification Title âšľ"
			local alert = "Your notification message!"			 
			local sound = "notification.mp3"
			local pushUrl = "https://fcm.googleapis.com/v1/projects/"..ProjId.."/messages:send"
			local headers = {}
			local body = {}
			local FCMkey = "Bearer "..oauthToken
			headers = 
			{
				["Content-Type"] = "application/json",
				["Authorization"] = FCMkey
			}
			body = 
			{
				["message"] =
				{
					["token"] = deviceToken,
					["notification"] = 
					{
						["title"] = title,
						["body"] = alert,
					},
					["android"] = 
					{
						["notification"] = 
						{
							["sound"] = sound,
						}
					},
					["apns"] =
					{
						["payload"] = 
						{
							["aps"] =
							{
								["sound"] = sound,
							}
						}
					}
				}	
			}

			local function pushResponse(event)
				-- do stuff with responses
			end

			local params = {}
			params.headers = headers
			params.body = json.prettify(body)
			network.request( pushUrl, "POST", pushResponse, params )		
		else
			print("Server error: " .. event.status)
		end
	end
end

-- get oauth2 token from web server first
local params = {
	headers = 
	{
		["Content-Type"] = "application/json",
		["x-api-key"] = OAUTH_API_KEY,
		["x-api-secret"] = OAUTH_API_SECRET
	}
}

network.request(
	SERVER_URL,
	"GET",
	networkListener,
	params
)
1 Like