Expo notifications - no sound - javascript

I'm building a React Native/Expo app that uses Push Notifications. For them to work I'm using package expo-notifications as shown below:
First, I'm setting notification handler
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldPlaySound: true,
shouldSetBadge: false
})
})
Then I get push token:
const registerForPushNotificationsAsync = async () => {
let pushToken
if (Constants.isDevice) {
const { status: existingStatus } = await Notifications.getPermissionsAsync()
let finalStatus = existingStatus
if (existingStatus !== 'granted') {
const { status } = await Notifications.requestPermissionsAsync()
finalStatus = status
}
if (finalStatus !== 'granted') {
alert(I18n.t('errors.push.token'))
return
}
pushToken = (await Notifications.getExpoPushTokenAsync()).data
AsyncStorage.getItem('push_token').then((savedToken) => {
if (pushToken !== savedToken) {
AsyncStorage.setItem('push_token', pushToken)
Networking.registerDeviceForPushNotifications(pushToken, Platform.OS)
}
})
console.log(pushToken)
} else {
alert(I18n.t('errors.push.physical'))
}
if (Platform.OS === 'android') {
Notifications.setNotificationChannelAsync('default', {
name: 'default',
importance: Notifications.AndroidImportance.MAX,
vibrationPattern: [0, 250, 250, 250],
lightColor: Colors.primary,
sound: 'default'
})
}
return pushToken
}
At the end I'm starting listeners:
notificationListener.current = Notifications.addNotificationReceivedListener(notification => {
setNotification(notification)
})
responseListener.current = Notifications.addNotificationResponseReceivedListener(response => {
// navigation here
})
The problem:
The notifications send to my app go through normally, but whatever I do I cannot get sound to work. I want my push messages to play a default system sound when they arrive, but there's nothing and I don't know why.

Your code snipped that refers to Notifications.setNotificationHandler only fires when the app is foregrounded according to the docs here. You will have to send a test notification on a physical device with the app foregrounded to hear a sound.
It may also help to add a custom sound. This is handled in the app.json file and will require a rebuild but you can see and example and read more here

Related

Supabase realtime slow and skipping some broadcasts

I'm using supabase realtime channels to build a collaborative editor. I'm using slatejs and I'm broadcasting operations on the channel but the broadcasts appear really slow on other clients and also some of the broadcasts are lost.
This is the main code:
const blockUpdateChannel = supabaseClient.channel(
"block-updates" + DOCUMENT_ID
);
// Applying the received broadcasts to current editor
blockUpdateChannel
.on("broadcast", { event: "blockupdate" }, (event: any) => {
Editor.withoutNormalizing(editor as any, () => {
const operations = event.payload.ops;
operations.forEach((operation: any) => {
console.log(operation);
if (operation.source !== userId) {
editor?.apply(operation);
}
});
});
})
.subscribe();
// sending broadcasts
const ops: any = [];
editor?.operations.forEach((operation: any) => {
var shouldAdd = false;
if (!operation.source) {
if (operation.type !== "set_selection") {
shouldAdd = true;
if (operation.type === "set_node") {
if (operation.newProperties.modifiedByUserId !== undefined) {
shouldAdd = false;
}
}
}
}
if (shouldAdd) {
operation.source = userId;
ops.push(operation);
}
});
if (ops.length) {
console.log("Sending ops", ops);
blockUpdateChannel.send({
type: "broadcast",
event: "blockupdate",
payload: { ops },
});
}
You can debug Supabase Realtime by looking at the (new) Realtime logs at:
https://app.supabase.com/project/YOUR_PROJECT/database/realtime-logs
You can also inspect the websocket frames with Chrome Inspector. There may be some odd messages there. Or you might find that it's disconnecting / reconnecting frequently for some reason.

Why useFocusEffect doesn't notice when the screen is not on focus and comes back to focus

What I want is to notice when the user is on the app screen, or off the application(in the setting screen in this case)
The reason Im doing this is because I want to check the permissions of the user if it's "denied" or "granted".
and if its "denied" to not allow the user to navigate to other screen, and if its "granted" to allow the user to navigate to other screen.
const PermissionsIntro = ({ navigation}) => {
async function configurePushNotifications() {
const { status } = await Notifications.getPermissionsAsync();
let finalStatus = status;
console.log('status of notification', status)
if(status === 'granted'){
setNavigate(true)
}
else if (finalStatus !== 'granted') {
const { status } = await Notifications.requestPermissionsAsync();
finalStatus = status;
setNavigate(false)
}
else if (finalStatus !== 'granted') {
setNavigate(false)
Alert.alert('Permission required', 'Push notifications need the appropriate permissions.');
return;
}
const pushTokenData = await Notifications.getExpoPushTokenAsync();
setExpoPushToken(pushTokenData.data);
if (Platform.OS === 'android') {
Notifications.setNotificationChannelAsync('default', {
name: 'default',
importance: Notifications.AndroidImportance.DEFAULT,
});
}
}
useFocusEffect(
React.useCallback(() => {
configurePushNotifications();
alert('screen is on application')
console.log('tfffffff')
return () => {
openSettings()
console.log('screen is on settings')
// alert('screen is on settings')
};
}, [])
);
const openSettings = () => {
Linking.openSettings();
};
//onPress function
const confirm = () => {
console.log('navigate', navigate);
if (navigate == true) {
navigation.navigate('CreateProfile');
}
else {
console.log('turn the permissions to true!');
openSettings();
}
};
}
When I navigate to this screen it showed me the alert alert(screen is on application),
but when I go to the settings and go back to the application the useFocusEffect is not called at all.
How can I fix this error?
useFocusEffect is not able to run when app is coming to foreground/background unfortunately. In this case you should use AppState instead. AppState can tell you if the app is in the foreground or background, and notify you when the state changes. More can be found in the documentation.

React native randomly crashes with error 'Attempted to remove more RCTKeyboardObserver listeners than added'

My react native project on iOS simulator crashes randomly with this error when I open and close the ChatRoom screen, I guess it's a problem with the react native gifted chat library I'm not sure, that's the only external library I use in that screen.
I have tried Keyboard remove listener solution Keyboard.addListener("keyboardWillShow").remove(); from https://github.com/rgommezz/react-native-offline/issues/55 in my App.js file but it don't work.
I have added the code of the screen where this crash randomly occurs. Project versions are "react": "17.0.2", "react-native": "0.66.4", "react-native-gifted-chat": "^0.16.3".
ChatRoom
const ChatRoom = ({ navigation, route }) => {
React.useLayoutEffect(() => {
navigation.setOptions({
title: name,
});
}, [navigation, name]);
React.useEffect(() => {
mounted.current = true;
async function fetch() {
const response = await getUserData(receiverId);
if (mounted.current) {
setChatImage(response.userimg);
setName(response.name);
}
const chat = await getChat(user.uid, userid);
if (chat) {
const unsubscribe = firestore()
.collection("Messages")
.where("chatid", "==", chat)
.onSnapshot((querySnapshot) => {
const messages = querySnapshot
.docChanges()
.filter(({ type }) => type === "added")
.map(({ doc }) => {
const message = doc.data();
return {
...message,
createdAt: message.createdAt.toDate(),
};
})
.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
appendMessages(messages);
});
return () => unsubscribe();
}
}
fetch();
return () => (mounted.current = false);
}, []);
React.useEffect(() => {
// set message state
if (receiverId) {
if (user.uid !== receiverId) {
async function fetch() {
const chat = await getChat(user.uid, userid);
await markMsgStatus(user.uid, chat);
}
fetch();
}
}
}, [receiverId]);
const appendMessages = React.useCallback(
(messages) => {
setMessages((previousMessages) =>
GiftedChat.append(previousMessages, messages)
);
},
[messages]
);
const handleSend = async (messages) => {
// if there are no chats, create one
const chat = await getChat(user.uid, userid);
let chatid;
if (!chat) {
chatid = await createChat(user.uid, userid);
}
// increment the no of messages in the chat by one
firestore()
.collection("Chats")
.doc(chat ? chat : chatid)
.set(
{
noofmsgs: firestore.FieldValue.increment(1),
},
{ merge: true }
)
.then(() => console.log("message count on chat increased by one"))
.catch((e) => console.log(e));
// add the messages
const writes = messages.map((m) =>
firestore()
.collection("Messages")
.add({
...m,
sent: true,
received: false,
senderid: user.uid,
receiverid: userid,
chatid: chat ? chat : chatid,
})
);
await Promise.all(writes);
// add the latest msg to chat
const writetochat = messages.find((m) =>
firestore()
.collection("Chats")
.doc(chat ? chat : chatid)
.set(
{
latestmsg: m,
latestmsgsender: user.uid,
latestmsgreceiver: userid,
},
{ merge: true }
)
);
await Promise.all(writetochat);
};
return (
<View>
<GiftedChat
messages={messages}
user={{
_id: user.uid,
}}
onSend={handleSend}
showAvatarForEveryMessage={true}
/>
</View>
);
};
export default ChatRoom;
Having the same issue and a kinda crash on ios when unmounting the component I was not able to make AkShil's solution work.
I found this tutorial which shows a similar solution.
For me it worked like this :
npm install patch-package -D
Change the root package.json script section and add
"postinstall": "patch-package"
Change the file node_modules/react-native-gifted-chat/lib/MessageContainer.js according to AkShil's solution
npx patch-package react-native-gifted-chat
This creates a folder /patches in the root directory and updates automatically the node_modules' packages when you npm install.
Works like charm.
PS: to ios simulator users just check and uncheck I/O -> Keyboard if it does not show anymore
This is the issue of the package itself. Below is how I fixed it.
Install package npm i patch-package
Create a Folder called patches on the root of the directory
Create a new file name patch-rn-gifted-chat within patch folder
add below code to it
diff --git a/node_modules/react-native-gifted-chat/lib/MessageContainer.js b/node_modules/react-native-gifted-chat/lib/MessageContainer.js
index 193772a..4e80378 100644
--- a/node_modules/react-native-gifted-chat/lib/MessageContainer.js
+++ b/node_modules/react-native-gifted-chat/lib/MessageContainer.js
## -55,18 +55,18 ## export default class MessageContainer extends React.PureComponent {
this.attachKeyboardListeners = () => {
const { invertibleScrollViewProps: invertibleProps } = this.props;
if (invertibleProps) {
- Keyboard.addListener('keyboardWillShow', invertibleProps.onKeyboardWillShow);
- Keyboard.addListener('keyboardDidShow', invertibleProps.onKeyboardDidShow);
- Keyboard.addListener('keyboardWillHide', invertibleProps.onKeyboardWillHide);
- Keyboard.addListener('keyboardDidHide', invertibleProps.onKeyboardDidHide);
+ this.willShowSub = Keyboard.addListener('keyboardWillShow', invertibleProps.onKeyboardWillShow);
+ this.didShowSub = Keyboard.addListener('keyboardDidShow', invertibleProps.onKeyboardDidShow);
+ this.willHideSub = Keyboard.addListener('keyboardWillHide', invertibleProps.onKeyboardWillHide);
+ this.didHideSub = Keyboard.addListener('keyboardDidHide', invertibleProps.onKeyboardDidHide);
}
};
this.detachKeyboardListeners = () => {
const { invertibleScrollViewProps: invertibleProps } = this.props;
- Keyboard.removeListener('keyboardWillShow', invertibleProps.onKeyboardWillShow);
- Keyboard.removeListener('keyboardDidShow', invertibleProps.onKeyboardDidShow);
- Keyboard.removeListener('keyboardWillHide', invertibleProps.onKeyboardWillHide);
- Keyboard.removeListener('keyboardDidHide', invertibleProps.onKeyboardDidHide);
+ this.willShowSub?.remove();
+ this.didShowSub?.remove();
+ this.willHideSub?.remove();
+ this.didHideSub?.remove();
};
this.renderTypingIndicator = () => {
if (Platform.OS === 'web') {
Run command yarn or npm i

Undefined is not an object (evaluating 'navigator.permissions.query')

I am getting this error when trying to access my website on an iPhone 7, with a white bank screen (the main screen loads fine, but then I get this at the net screen after I click something.
I assume this is what it's talking about:
useEffect(() => {
navigator.permissions
.query({ name: "microphone" })
.then((permissionStatus) => {
setMicrophonePermissionGranted(permissionStatus.state === "granted");
permissionStatus.onchange = function () {
setMicrophonePermissionGranted(this.state === "granted");
};
});
navigator.permissions.query({ name: "camera" }).then((permissionStatus) => {
setCameraPermissionGranted(permissionStatus.state === "granted");
permissionStatus.onchange = function () {
setCameraPermissionGranted(this.state === "granted");
};
});
}, []);
How do I fix this?
You need to check permission APIs availability and then if not available - query standard APIs.
Here is the location example:
Permissions API
Navigation API
if ( navigator.permissions && navigator.permissions.query) {
//try permissions APIs first
navigator.permissions.query({ name: 'geolocation' }).then(function(result) {
// Will return ['granted', 'prompt', 'denied']
const permission = result.state;
if ( permission === 'granted' || permission === 'prompt' ) {
_onGetCurrentLocation();
}
});
} else if (navigator.geolocation) {
//then Navigation APIs
_onGetCurrentLocation();
}
function _onGetCurrentLocation () {
navigator.geolocation.getCurrentPosition(function(position) {
//imitate map latlng construct
const marker = {
lat: position.coords.latitude,
lng: position.coords.longitude
};
})
}
Permissions.query() is marked as an experimental feature as of June 2021 https://developer.mozilla.org/en-US/docs/Web/API/Permissions/query.
As of today, that traduces into that you'll need to implement two UIs / flows; one capable of supporting fancy flows to tell the user how to proceed, and the other one more standard, using try / catch blocks. Something like:
useEffect(() => {
requestPermissions();
}, []);
const requestPermissions = async () => {
try {
handlePermissionsGranted();
const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true });
startRecording();
} catch {
...
}
};
const handlePermissionsGranted = async () => {
if (navigator.permissions && navigator.permissions.query) {
const permissions = await navigator.permissions.query({name: 'microphone'});
permissions.onchange = () => {
setMicrophonePermissionGranted(permissions === 'granted');
};
}
};
const startRecording = async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false });
const mediaRecorder = new MediaRecorder(stream, { mimeType: 'audio/webm' });
...
} catch {
... << if you reach this catch means that either the browser does not support webrtc or that the user didn't grant permissions
}
};
I was trying to check for the mic and camera permissions from iOs devices and through the Facebook browser, which I guess makes the whole thing fail, as these don't exist in those environments.
Once I've moved that query to the component that only loads if it is not a mobile device, my error fixed.

Bootstraping Strapi Role Permissions

I am a developer that is doing front end work, strapi and javascript for the first time. I hope someone could take pity on me and provide an example of how to set the Public role permissions via a bootstrap.js script.
node.js v10.16.0
Strapi v3.0.0-next.11
Graphql 14.3.1
MongoDB: 3.6
All on Windows 10
In the Strapi UI, it is the Roles and Permissions for the Public Role
(source: strapi.io)
I want to set these boxes to CHECKED
(source: strapi.io)
Another developer has used the bootstrap.js file to add items to the services we created (menu). I don't know how to return even the most basic information on the role permissions.
My function is called test() I searched for examples and the best I found was this on stackoverflow:
Strapi Plugin Route Default Permission :
strapi.plugins['users-permissions'].models.role.find
but I cannot figure out how to use it:
WORKING
function add_widgets_from_sheet(sheet_name, model_object){
console.log(`adding ${sheet_name}`)
let xlsxSheet = Sheets[sheet_name]
const widgets = XLSX.utils.sheet_to_json(xlsxSheet)
widgets.forEach(function (widget) {
//See if the object is already in the db before adding it
model_object.count(widget)
.then(result => {
if (result == 0) {
console.log('Adding '+sheet_name+': ' + JSON.stringify(widget))
return model_object.add(widget)
}
})
})
}
NOT WORKING
function test(){
console.log(`Testing ${strapi.plugins['users-permissions'].models.role.find}`)
}
module.exports = next => {
console.log('Starting Strapi bootstrap')
add_widgets_from_sheet('Menus', strapi.services.menu) //adding menus
test() // Returning nothing
console.log('Ending Strapi bootstrap')
next()
}
I would like to toggle those checkboxes to TRUE, CHECKED or whatever its called. so that we don't have to manually do it through the UI everytime we dump the database.
I learn best from examples...I hope you can help. Thank you!
So the code below is from a file called PROJECT/STRAPI/config/functions/bootstrap.js
This automates creating the content types and content with information we keep in an excel spreadsheet. But in order to use those content types, there are roles and permissions that have to be activated so the web ui can access them.
Basically, we do not want to manually go into the Strapi UI to create your user, create content types, create content or update the permissions. We want a script to do all of that.
'use strict'
Our Environment Variables
require('dotenv').config({ path:'../.env' })
Excel Spreadsheet Holding our data (attached)
const XLSX = require('xlsx')
const BOOTSTRAP_DATA = XLSX.readFile(process.env.BOOTSTRAP_DATA).Sheets
Variables pulled from .env
const ADMIN_USERNAME = process.env.ADMIN_USERNAME
const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD
const ADMIN_EMAIL = process.env.ADMIN_EMAIL
Reading in the XLSX
async function bootstrap_resource(resource_type, resource_service) {
strapi.log.info(`Bootstrapping ${resource_type}`)
const resources = XLSX.utils.sheet_to_json(BOOTSTRAP_DATA[resource_type])
for (let resource of resources) {
if (await resource_service.count(resource) === 0) {
strapi.log.warn(`Bootstrapping ${resource_type}: ${JSON.stringify(resource)}`)
await resource_service.create(resource)
}
}
}
Creating the initial USER for strapi
async function bootstrap_admin() {
strapi.log.info(`Bootstrapping Admin`)
const admin_orm = strapi.admin.queries('administrator', 'admin')
const admins = await admin_orm.find({username: ADMIN_USERNAME})
if ( admins.length === 0) {
const blocked = false
const username = ADMIN_USERNAME
const password = await strapi.admin.services.auth.hashPassword(ADMIN_PASSWORD)
const email = ADMIN_EMAIL
const user = { blocked, username, password, email }
const data = await admin_orm.create(user)
strapi.log.warn(`Bootstrapped Admin User: ${JSON.stringify(user)}`)
}
}
The following are get_roles() - required for get_permissions(), and get_permissions() is required for enable_permissions() This is where we turn on those content types so the web ui can see it.
async function get_roles() {
const role_orm = strapi.plugins['users-permissions'].queries('role', 'users-permissions')
const role_list = await role_orm.find({}, [])
const roles = {}
for (let role of role_list) {
roles[ role._id ] = role
roles[ role.name ] = role
}
return roles
}
async function get_permissions( selected_role, selected_type, selected_controller ) {
const roles = await get_roles()
const permission_orm = strapi.plugins['users-permissions'].queries('permission', 'users-permissions')
let permission_list = await permission_orm.find({_limit: 999}, [])
if ( selected_role ) permission_list = permission_list.filter( ({ role }) => `${role}` === `${roles[selected_role]._id}` )
if ( selected_type ) permission_list = permission_list.filter( ({ type }) => `${type}` === `${selected_type}` )
if ( selected_controller ) permission_list = permission_list.filter( ({ controller }) => `${controller}` === `${selected_controller}` )
return permission_list
}
async function enable_permissions(role, type, controller) {
strapi.log.info(`Setting '${controller}' permissions for '${role}'`)
const permission_orm = strapi.plugins['users-permissions'].queries('permission', 'users-permissions')
const permissions = await get_permissions(role, type, controller)
for (let { _id } of permissions) {
permission_orm.update({ _id }, { enabled: true })
}
}
Finally, we run the program
module.exports = async next => {
await bootstrap_admin()
await bootstrap_resource( 'Clients', strapi.services.client )
await bootstrap_resource( 'Menus', strapi.services.menu )
enable_permissions('Public', 'application', 'client' )
enable_permissions('Public', 'application', 'github' )
enable_permissions('Public', 'application', 'menu' )
enable_permissions('Public', 'application', 'confluence' )
next()
}
Take out my comments and you have the entire bootstrap.js file. The images below show the 3 tabs of the demo.xlsx workbook that is used to populate everything.
Finally, showing the results. Menus (content), permissions set and the public website using Nuxt.
Building on both of the previous answers, it seems you can get away with a single loop and in that you can set permissions for both public and authenticated users.
This was written against strapi 3.2.4 and I'm using NodeJS 12 so things like the spread operator ... are available.
const permOrm = strapi.query('permission', 'users-permissions')
const perms = await permOrm.find({ type: 'application' })
for (const curr of perms) {
if (curr.role.type === 'authenticated') {
strapi.log.info(
`Allowing authenticated to call ${curr.controller}.${curr.action}`,
)
permOrm.update({ id: curr.id }, { ...curr, enabled: true })
continue
}
// permission is for public
const isReadEndpoint = ['find', 'findone', 'count'].includes(curr.action)
if (isReadEndpoint) {
strapi.log.info(
`Allowing public to call ${curr.controller}.${curr.action}`,
)
permOrm.update({ id: curr.id }, { ...curr, enabled: true })
continue
}
// TODO add custom logic for any non-standard actions here
strapi.log.info(
`Disallowing public from calling ${curr.controller}.${curr.action}`,
)
permOrm.update({ id: curr.id }, { ...curr, enabled: false })
}
I find this simpler.
// In your bootstrap.js file
'use strict';
module.exports = async () => {
const authenticated = await strapi.query('role', 'users-permissions').findOne({ type: 'authenticated' });
authenticated.permissions.forEach(permission => {
if (permission.type === 'application'){ // Whatever permissions you want to change
let newPermission = permission;
newPermission.enabled = true; // Editing permission as needed
strapi.query('permission', 'users-permissions').update( { id: newPermission.id }, newPermission ); // Updating Strapi with the permission
}
});
return;
};
For strapi 3.3.x and probably future versions
Slightly efficient way (queries role one time, uses query to filter permissions)
'use strict';
module.exports = async () => {
const publicRole = await getRoleByName('Public')
await grantPermissions(publicRole, 'application', 'images', ['upload', 'remove']) // upload, remove in 'images' controller
await grantPermissions(publicRole, 'application', 'project') // any action in 'project' controller
};
async function getRoleByName(name) {
return strapi.query('role', 'users-permissions').findOne({ name }, [])
}
async function getPermissions(role, permissionType, controller, actions = null) {
const permissionQuery = strapi.query('permission', 'users-permissions')
const permissionRequest = {
_limit: 1000,
role: role.id,
type: permissionType,
controller: controller
}
if (actions) {
permissionRequest.action_in = Array.isArray(actions) ? actions : [actions]
}
return permissionQuery.find(permissionRequest, [])
}
async function grantPermissions(role, permissionType, controller, actions) {
if (actions && !Array.isArray(actions)) {
actions = [ actions ]
}
strapi.log.info(`Setting '${controller}' [${actions ? actions.join(', ') : '*'}] permissions for '${role.name}'`)
const permissionQuery = strapi.query('permission', 'users-permissions')
const permissions = await getPermissions(role, permissionType, controller, actions)
if (permissions.length === 0) {
throw new Error(`Error enabling permissions: ${role.name}, ${permissionType}, ${controller}, ${actions}`)
}
for (const { id } of permissions) {
await permissionQuery.update({ id }, { enabled: true })
}
}

Categories