I was wondering if it was possible to use FCM in chrome extension Manifest version 3? And by calling the getToken function in the background.js script.
I tried to use it but I keep getting the following error:
FirebaseError: Messaging: We are unable to register the default service worker.
Even though the firebase-messaging-sw.js is in the extension root. And I see on the Chrome Extension API it has a gcm entry but not a fcm entry.
I did find a github repo where fcm worked with the V3 but in that repo they opened a new window and used the service worker registration function in there and the getToken function. That won't work for my case because I need the getToken function to be called in the background.js without user interaction.
Any suggestions?
Attached the modified background.js code:
import { initializeApp } from "./firebase-app.js";
import { getMessaging, onBackgroundMessage } from './firebase-messaging-sw.js';
import { getToken, onMessage } from "./firebase-messaging.js";
import { firebaseConfig, vapidKey } from './firebaseConfig.js';
// Initialize Firebase
const firebaseApp = initializeApp(firebaseConfig);
const messaging = getMessaging(firebaseApp);
onBackgroundMessage(messaging, (payload) => {
console.log('[background.js] Received background message ', payload);
self.registration.showNotification(payload.notification.title, {
body: payload.notification.body,
});
});
chrome.runtime.onInstalled.addListener(() => {
console.log('installed');
});
getToken(messaging, {
vapidKey,
}).then((currentToken) => {
if (currentToken) {
// Send the token to your server to send push notifications.
registrationTokenElement.textContent = currentToken;
console.log(currentToken);
} else {
// Show permission request UI
console.log('No registration token available. Request permission to generate one.');
// ...
}
}).catch((err) => {
console.log('An error occurred while retrieving token. ', err);
// ...
});
async function openPopupWindow() {
console.log('open popup');
const { popupWindowId } = await chrome.storage.local.get('popupWindowId');
if (popupWindowId) {
try {
await chrome.windows.update(popupWindowId, { focused: true });
return;
} catch (e) {
// ignore
}
}
const popup = await chrome.windows.create({
url: 'popup.html',
type: 'popup',
width: 300,
height: 500,
});
await chrome.storage.local.set({
popupWindowId: popup.id,
});
}
chrome.action.onClicked.addListener(() => {
console.log('click icon');
openPopupWindow();
});
self.onnotificationclick = function(event) {
console.log('On notification click: ', event.notification.tag);
event.notification.close();
event.waitUntil(openPopupWindow());
};
chrome.windows.onRemoved.addListener(async (windowId) => {
const { popupWindowId } = await chrome.storage.local.get('popupWindowId');
if (popupWindowId === windowId) {
console.log('close popup');
await chrome.storage.local.remove('popupWindowId');
}
});
Related
I need your help to mock a twilio service which sends a message, using jest to mock the service
I have the next code:
import { SQSEvent } from "aws-lambda";
import { GetSecretValueResponse } from "aws-sdk/clients/secretsmanager";
export async function sendSms(event: SQSEvent, data: GetSecretValueResponse) {
const secrets = JSON.parse(data.SecretString);
const accountSid = secrets.TWILIO_ACCOUNT_SID;
const authToken = secrets.TWILIO_AUTH_TOKEN;
const twilioNumber = secrets.TWILIO_PHONE_NUMBER;
if (accountSid && authToken && twilioNumber) {
//Create a Twilio Client
const client = new Twilio(accountSid, authToken);
//Loop into al records of the event, every record is every message sent from Sqs
for (const record of event.Records) {
const body = JSON.parse(record.body);
const userNumber = "+" + body.number;
//SendMessage function
try {
const message = client.messages.create({
from: twilioNumber,
to: userNumber,
body: body.message,
});
return message;
} catch (error) {
return `Failed to send sms message. Error Code: ${error.errorCode} / Error Message: ${error.errorMessage}`;
}
}
} else {
return "You are missing one of the variables you need to send a message";
}
}
The I call this function from my index:
import { SQSEvent } from "aws-lambda";
import { sendSms } from "./services/sendSms/sendSms";
import { getSecret } from "./services/obtainSecrets/getSecret";
import { SecretsManager } from "aws-sdk";
export const lambdaHandler = async (event: SQSEvent) => {
try {
const obtainedSecret = await getSecret()
.then((credentials: SecretsManager.GetSecretValueResponse) => {
return credentials;
})
.catch(error => {
return error;
});
const response = sendSms(event, obtainedSecret)
.then(response => {
return response;
})
.catch(error => {
return error;
});
return {
message: "OK " + obtainedSecret + response,
code: 200,
};
} catch (error) {
throw new Error(error);
}
};
I have already make some tests, but them always makes a connection with Twilio api(requiring the real token, sid,etc), and I need to mock the Twilio service, so the function I call in my test.ts doesn't connects to internet.
import { Twilio } from "twilio";
import { MessageInstance } from "twilio/lib/rest/api/v2010/account/message";
import { sendSms } from "../../services/sendSms/sendSms";
//mock Twilio library and sendSms service
jest.mock("twilio");
jest.mock("../../services/sendSms/sendSms");
const smsMessageResultMock: Partial<MessageInstance> = {
status: "sent",
sid: "AC-lorem-ipsum",
errorCode: undefined,
errorMessage: undefined,
};
describe("SMS Service", () => {
describe("Send Message", () => {
it("Should fail", async () => {
// update smsMessageResultMock to simulate a faled response
const smsMessageMock = {
...smsMessageResultMock,
status: "failed",
errorCode: 123,
errorMessage: "lorem-ipsum",
};
// simulated response of secret management
let data = {
ARN: "arn:aws:secretsmanager:us-west-2:123456789012:secret:MyTestDatabaseSecret-a1b2c3",
Name: "MyTestDatabaseSecret",
SecretString:
'{"TWILIO_ACCOUNT_SID": "ACTWILIO_ACCOUNT_SID","TWILIO_AUTH_TOKEN": "TWILIO_AUTH_TOKEN","TWILIO_PHONE_NUMBER": "TWILIO_PHONE_NUMBER"}',
VersionId: "EXAMPLE1-90ab-cdef-fedc-ba987SECRET1",
VersionStages: ["AWSPREVIOUS"],
};
// simulated response of SqsEvent
let event = {
Records: [
{
messageId: "19dd0b57-b21e-4ac1-bd88-01bbb068cb78",
receiptHandle: "MessageReceiptHandle",
body: '{"message": "Hello world","number": "(506)88888888"}',
attributes: {
ApproximateReceiveCount: "1",
SentTimestamp: "1523232000000",
SenderId: "123456789012",
ApproximateFirstReceiveTimestamp: "1523232000001",
},
messageAttributes: {},
md5OfBody: "{{{md5_of_body}}}",
eventSource: "aws:sqs",
eventSourceARN: "arn:aws:sqs:us-east-1:123456789012:MyQueue",
awsRegion: "us-east-1",
},
],
};
// simulate tokens for Twilio
const accountSid = "ACfjhdskjfhdsiuy876hfijhfiudsh";
const authToken = "fjfuewfiuewfbodfiudfgifasdsad";
//create client with mocked Twilio
const client = new Twilio(accountSid, authToken);
//call messages.create of Twilio client, and give it the expected result created
client.messages.create = jest
.fn()
.mockResolvedValue({ ...smsMessageMock });
console.log(await sendSms(event, data));
//expectes the function sendSms(event, data) to throw an error
await expect(sendSms(event, data)).rejects.toThrowError(
`Failed to send sms message. Error Code: ${smsMessageMock.errorCode} / Error Message: ${smsMessageMock.errorMessage}`
);
});
});
});
(event and data are simulated responses of SqsEvent and GetSecretValueResponse)
The problem is that when I run the npm test it throws me an error of Twilio's authentication, an it is because I'm passing self created tokens.
Expected substring: "Failed to send sms message. Error Code: 123 / Error Message: lorem-ipsum"
Received message: "Authentication Error - invalid username"
at success (node_modules/twilio/lib/base/Version.js:135:15)
at Promise_then_fulfilled (node_modules/q/q.js:766:44)
at Promise_done_fulfilled (node_modules/q/q.js:835:31)
at Fulfilled_dispatch [as dispatch] (node_modules/q/q.js:1229:9)
at Pending_become_eachMessage_task (node_modules/q/q.js:1369:30)
at RawTask.Object.<anonymous>.RawTask.call (node_modules/asap/asap.js:40:19)
at flush (node_modules/asap/raw.js:50:29)
So what I suppose is that the test is connecting to internet and calling Twilio's api.
I appreciate if you could help me.
I think what you want to do is mock the class returned by the module, using jest.mock('twilio', mockImplementation) and in mockImplementation return a function to act as a constructor that will take your account SID and auth token arguments and then return a mockClient implementation, which in this case needs to return an object which has a messages property, which in turn is an object with a create property that is a mock function.
It's probably easier to just show the code.
const mockClient = {
messages: {
create: jest.fn().mockResolvedValue({ ...smsMessageMock });
}
};
jest.mock("twilio", () => {
return function(accountSid, authToken) {
return mockClient;
}
});
I am working on a Next.js project and I included the twilio chat. The next step I want to do is to set up the push notifications. I tried to follow this guide and it seems all to be fine. I actually get the FCM token and pass it to the twilio chat SDK. I expect that when I write a message something happens even errors, but I see nothing.
I have the following code:
messaging_get_token.js
import { initializeApp } from 'firebase/app';
import { getAnalytics } from 'firebase/analytics';
import { getMessaging, getToken, onMessage } from 'firebase/messaging';
export const firebaseConfiguration = () => {
const firebaseConfig = {
apiKey: 'my api key',
authDomain: 'my auth domani',
projectId: 'my project id',
storageBucket: 'my storage bucket',
messagingSenderId: 'my messaging sender id',
appId: 'my app id',
measurementId: 'measurement id',
};
// Initialize Firebase
const app = initializeApp(firebaseConfig);
const analytics = getAnalytics(app);
};
export const handleNotifications = async (chatClientInstance) => {
firebaseConfiguration();
const messaging = getMessaging();
getToken(messaging, {
vapidKey: 'my vapid key'
})
.then((currentToken) => {
if (currentToken) {
// the flow arrives here and I get no errors
chatClientInstance?.client.setPushRegistrationId('fcm', currentToken);
onMessage((payload) => {
chatClientInstance?.client.handlePushNotification(payload);
});
} else {
console.log(
'No registration token available. Request permission to generate one.'
);
}
})
.catch((err) => {
console.log('An error occurred while retrieving token. ', err);
});
};
After this I call the function handleNotifications(client) in a useEffect when I render the chat component and pass to it the twilio Client.
In order to get the twilio client and channel I did this:
import toast from 'react-hot-toast';
const Chat = require('twilio-chat');
export const getTwilioClient = async (token: string, room?: string) => {
const client = await Chat.Client.create(token || 'no_token');
let channel;
const joinChannel = async (channel) => {
if (channel.channelState.status !== 'joined') {
await channel.join();
}
};
if (room) {
try {
channel = await client.getChannelByUniqueName(room);
await joinChannel(channel);
} catch {
try {
channel = await client.createChannel({
uniqueName: room,
friendlyName: room,
});
await joinChannel(channel);
} catch {
toast.error('Unable to create channel, please reload this page');
throw new Error('unable to create channel, please reload this page');
}
}
}
return { client, channel };
};
I followed all the steps, but I can't figure out why I don't see anything. Is there something I missed? Unfortunately I don't find any sample about doing this..
p.s.
I use twilio 3.71.3, twilio-chat 6.0.0 and firebase 9.6.8
I'm sending push messages using FCM through Firebase Functions. The messages are being sent properly, but I'm getting the 408 time-out error after the message is sent. I'm suspecting it might have to do with the unregistered tokens not being cleaned up because:
if I were to send another message to the same device, the same timeout occurs and
the only error message I get from the Firebase log is Function execution took 60002 ms, finished with status: 'timeout'.
exports.sendMessage = functions.https.onRequest(async (request, response) => {
const {
sender,
recipient,
content,
docID
} = request.body
functions.logger.log(
"docID:",
docID,
);
// Get the list of device notification tokens.
let deviceTokens; let ref;
try {
ref = admin.firestore().collection("deviceToken").doc(recipient);
const doc = await ref.get();
if (!doc.exists) {
console.log("No such document!");
response.status(500).send(e)
} else {
console.log("doc.data():", doc.data());
deviceTokens = doc.data().token;
}
} catch (e) {
response.status(500).send(e)
}
let senderProfile;
try {
senderProfile = await admin.auth().getUser(sender);
console.log("senderProfile", senderProfile);
} catch (e) {
console.log(e);
response.status(500).send(e)
}
// Notification details.
let payload = {
notification: {
title: senderProfile.displayName,
body: content,
sound: "default",
},
data: {
uid: senderProfile.uid,
displayName: senderProfile.displayName,
docID,
messageType: "status"
}
};
functions.logger.log(
"deviceTokens", deviceTokens,
"payload", payload,
);
// Send notifications to all tokens.
const messageResponse = await admin.messaging().sendToDevice(deviceTokens, payload);
// For each message check if there was an error.
messageResponse.results.forEach((result, index) => {
const error = result.error;
if (error) {
functions.logger.error(
"Failure sending notification to",
deviceTokens[index],
error,
);
// Cleanup the tokens who are not registered anymore.
if (error.code === "messaging/invalid-registration-token" ||
error.code === "messaging/registration-token-not-registered") {
const updatedTokens = deviceTokens.filter((token) => token !== deviceTokens[index]);
console.log("updatedTokens", updatedTokens);
ref.update({
token: updatedTokens,
})
.catch(function(e) {
console.error("Error removing tokens", e);
response.status(500).send(e)
});
}
}
});
response.status(200)
});
I'm unsure why the following isn't cleaning up the unregistered tokens:
const updatedTokens = deviceTokens.filter((token) => token !== deviceTokens[index]);
ref.update({
token: updatedTokens,
})
You always have to end HTTP functions with response.status(200).send() or response.status(200).end(). In the above function, you have response.status(200) so you have to end it either with response.status(200).send() or response.status(200).end(). Please check the documentation if it helps.
I successfully registered firebase service worker with
let messaging = null;
export const registerFirebaseServiceWorker = () => {
if ("serviceWorker" in navigator) {
console.log("serviceWorker in navigator");
navigator.serviceWorker.register(("./firebase-messaging-sw.js"))
.then((registration) => {
console.log("Registration successful, scope is: ", registration.scope);
})
}
}
and try to receive the message with messaging.onMessage callback
if (supportsWebPush) {
messaging.onMessage(payload => {
console.log("There's a message!", payload);
const notificationTitle = "notificationTitle"
const notificationOptions = {
body: payload
};
//self.registration.showNotification(notificationTitle, notificationOptions);
navigator.serviceWorker.getRegistration("./firebase-messaging-sw.js").then(registration => {
console.log("got the registration");
registration.showNotification('Hello world!');
}).catch(error => {
console.log("No service worker registered", error);
});
});
Even though console.log was working and registration.showNotification was executed, there were no push notification on my browser ? Why is this? I've been trying multiple different ways and search for many suspected issues but can't found a proper way to display default web push pop up
I am setting up FCM for my React web push notification and it is doing everything properly, except I don't know how to refresh token when it's expired
I have an FCMListener function which its code is as follow
navigator.serviceWorker
.register("/static-files/firebase-messaging-sw.js")
.then((registration) => {
firebase.initializeApp(settings.getConfig().FIREBASE_CONFIG);
const messaging = firebase.messaging();
messaging.useServiceWorker(registration);
try {
messaging
.requestPermission()
.then(() => {
return messaging.getToken();
})
.then((token) => {
let topic = `${userInfo.is_host ? "host" : "guest"}`;
if (token) {
this.subscribeToTopic(topic, token);
this.sendTokenToServer({
os: "web",
push_token: token,
});
} else {
messaging.onTokenRefresh(() => {
messaging
.getToken()
.then((refreshedToken) => {
this.subscribeToTopic(topic, token);
this.sendTokenToServer({
os: "web",
push_token: refreshedToken,
});
})
.catch((err) => {
console.log("Unable to retrieve refreshed token ", err);
});
});
}
});
} catch (error) {
if (error.code === "messaging/permission-blocked") {
console.log("Please Unblock Notification Request Manually");
} else {
console.log("Error Occurred", error);
}
}
messaging.onMessage((payload) => {
console.log("Notification Received", payload);
alert(payload.notification.body);
});
});
};
}
as I don't know how to expire a firebase token and I can't test what happens when the token is expired I don't know if the part where I am trying to get refreshedToken is right or not or even if this is the proper way to get refreshed token or not. I would really appreciate any hint and advise
For testing purposes you can delete the FCM token through the API by calling the deleteToken API. After doing that, reload the page, and your onTokenRefresh should fire.