branch.initSessionWithLaunchOptions handler getting triggered on app exit - branch.io

By setting breakpoints, i noticed that the handler was getting called when the app is exiting.
Am I doing something wrong? If not, why would branch want to do this?
// This is in app delegate
branch.initSessionWithLaunchOptions(launchOptions, automaticallyDisplayDeepLinkController: true, deepLinkHandler: { params, error in
// breakpoint
if error == nil {
self.handleDeepLink(params)
}
else {
error?.showInToast()
}
})
}

Alex from Branch here: this is not expected behavior from our SDK. Are you overriding any of the other app lifecycle methods (e.g., applicationDidBecomeActive or applicationWillResignActive)?

Related

Scheduled Push Notification action onClick/onPress event in react native push notification?

I'm using this package for implementing local push notification:
https://github.com/zo0r/react-native-push-notification
I'm using action button like this to show buttons in my notification along with a text and a title:
PushNotification.localNotification({
...
actions: '["Yes", "No"]'
})
I wanted to know how I can call a function when user clicks on of these actions and app becomes visible?
I've tried PushNotification.configure in my componentDidMount method in my home screen like this but nothing comes up in the console:
PushNotification.configure({
// (required) Called when a remote or local notification is opened or received
onNotification: function(notification) {
console.log("NOTIFICATION:", notification);
if (notification.userInteraction) {
console.log("NOTIFICATION:");
}
// process the notification
}
});
I got it working.
In your App.js you need to set popInitialNotification to true. Something like this:
async componentDidMount() {
PushNotification.configure({
// (required) Called when a remote or local notification is opened or received
onNotification: function(notification) {
console.log("NOTIFICATION:", notification.action);
},
// IOS ONLY (optional): default: all - Permissions to register.
permissions: {
alert: true,
badge: true,
sound: true
},
// Should the initial notification be popped automatically
// default: true
popInitialNotification: true,
/**
* (optional) default: true
* - Specified if permissions (ios) and token (android and ios) will requested or not,
* - if not, you must call PushNotificationsHandler.requestPermissions() later
*/
requestPermissions: true
});
}
notification.action will gibe you the label of the button clicked.
In your button/app active event you forgot to call to schedule the notification and actually set when it will arise, so you need to
PushNotification.localNotificationSchedule(details: Object)
Schedule it for now with the same id, then your notification will come up immediately.
See all options for scheduling here
import PushNotificationAndroid from 'react-native-push-notification'
(function() {
// Register all the valid actions for notifications here and add the action handler for each action
PushNotificationAndroid.registerNotificationActions(['Accept','Reject','Yes','No']);
DeviceEventEmitter.addListener('notificationActionReceived', function(action){
console.log ('Notification action received: ' + action);
const info = JSON.parse(action.dataJSON);
if (info.action == 'Accept') {
// Do work pertaining to Accept action here
} else if (info.action == 'Reject') {
// Do work pertaining to Reject action here
}
// Add all the required actions handlers
});
})();
DO NOT USE .configure() INSIDE A COMPONENT, EVEN App
If you do, notification handlers will not fire, because they are not loaded. Instead, use .configure() in the app's first file, usually index.js.
It's mentioned in the documentation.
Try to follow their example for implementation It will help you to setup in your project.

OpenTok: Video Element Paused warning, can't unpublish

I'm trying to unpublish a video but keep getting the warning
"Video element paused, auto-resuming. If you intended to do this, use publishVideo(false) or subscribeToVideo(false) instead."
I'm unpublishing with:
clientPublisher.publishAudio(false);
clientPublisher.publishVideo(false);
clientSession.unpublish(clientPublisher, handleError);
The "streamDestoryed" function is firing so it seems like it should be unpublished. But if I subscribe to it using:
o.subscriber = clientSession.subscribe(stream, vid, {... subscribeToAudio: false...}, function(error) {
if (error) {
}
else {
o.subscriber.subscribeToAudio(false);
}
}
I still get audio. What exactly does this message mean and how do I stop publishing?
EDIT:
If I subscribe first WITHOUT audio, I can toggle it on and off and it works fine. However, if the client publishes and I don't set subscribeToAudio to false, I can no longer toggle the audio on and off... it's always on.

Service Worker - Wait for clients.openWindow to complete before postMessage

I am using service worker to handle background notifications. When I receive a message, I'm creating a new Notification using self.registration.showNotification(title, { icon, body }). I'm watching for the click event on the notification using self.addEventListener('notificationclick', ()=>{}). On click I'm checking to see if any WindowClient is open, if it is, I'm getting one of those window clients and calling postMessage on it to send the data from the notification to the app to allow the app to process the notification. Incase there is no open window I'm calling openWindow and once that completes I'm sending the data to that window using postMessage.
event.waitUntil(
clients.matchAll({ type: 'window' }).then((windows) => {
if (windows.length > 0) {
const window = windows[0];
window.postMessage(_data);
window.focus();
return;
}
return clients.openWindow(this.origin).then((window) => {
window.postMessage(_data);
return;
});
})
);
The issue I am facing is that the postMessage call inside the openWindow is never delivered. I'm guessing this is because the postMessage call on the WindowClient happens before the page has finished loading, so the eventListener is not registered to listen for that message yet? Is that right?
How do I open a new window from the service worker and postMessage to that new window.
I stumble this issue as well, using timeout is anti pattern and also might cause delay larger then the 10 seconds limit of chrome that could fail.
what I did was checking if I need to open a new client window.
If I didn't find any match in the clients array - which this is the bottle neck, you need to wait until the page is loaded, and this can take time and postMessage will just not work.
For that case I created in the service worker a simple global object that is being populated in that specific case for example:
const messages = {};
....
// we need to open new window
messages[randomId] = pushData.message; // save the message from the push notification
await clients.openWindow(urlToOpen + '#push=' + randomId);
....
In the page that is loaded, in my case React app, I wait that my component is mounted, then I run a function that check if the URL includes a '#push=XXX' hash, extracting the random ID, then messaging back to the service worker to send us the message.
...
if (self.location.hash.contains('#push=')) {
if ('serviceWorker' in navigator && 'Notification' in window && Notification.permission === 'granted') {
const randomId = self.locaiton.hash.split('=')[1];
const swInstance = await navigator.serviceWorker.ready;
if (swInstance) {
swInstance.active.postMessage({type: 'getPushMessage', id: randomId});
}
// TODO: change URL to be without the `#push=` hash ..
}
Then finally in the service worker we add a message event listener:
self.addEventListener('message', function handler(event) {
if (event.data.type === 'getPushMessage') {
if (event.data.id && messages[event.data.id]) {
// FINALLY post message will work since page is loaded
event.source.postMessage({
type: 'clipboard',
msg: messages[event.data.id],
});
delete messages[event.data.id];
}
}
});
messages our "global" is not persistent which is good, since we just need this when the service worker is "awaken" when a push notification arrives.
The presented code is pseudo code, to point is to explain the idea, which worked for me.
clients.openWindow(event.data.url).then(function(windowClient) {
// do something with the windowClient.
});
I encountered the same problem. My error was that I registered event handler on the window. But it should be registered on service worker like this:
// next line doesn't work
window.addEventListener("message", event => { /* handler */ });
// this one works
navigator.serviceWorker.addEventListener('message', event => { /* handler */ });
See examples at these pages:
https://developer.mozilla.org/en-US/docs/Web/API/Clients
https://developer.mozilla.org/en-US/docs/Web/API/Client/postMessage
UPD: to clarify, this code goes into the freshly opened window. Checked in Chromium v.66.

How to report different errors in React Native application?

Is there a solution for reporting different errors in React Native application (iOS and Android) as a global handler?
I am interested in following cases:
Unhandled rejections
Unhandled exceptions
Errors on the native side
By reporting, I mean sending them to some third-party service where you can track errors.
In RN there is a ErrorUtils global handler, that handle uncaught and caught exceptions for your RN JS layer. You can use this to set a handler like:
if (ErrorUtils._globalHandler) {
instance.defaultHandler = ErrorUtils.getGlobalHandler && ErrorUtils.getGlobalHandler() || ErrorUtils._globalHandler;
ErrorUtils.setGlobalHandler(instance.wrapGlobalHandler); //feed errors directly to our wrapGlobalHandler function
}
And handler method
async wrapGlobalHandler(error, isFatal){
const stack = parseErrorStack(error);
//Add this error locally or send it your remote server here
//*> Finish activity
setTimeout (() => {
instance.defaultHandler(error, isFatal); //after you're finished, call the defaultHandler so that react-native also gets the error
if (Platform.OS == 'android') {
NodeModule.reload()
}
}, 1000);
}
Notice in above code you need to create a node module for android only and write a React Native bridge method there in your ReactContextBaseJavaModule:
#ReactMethod
public void reload() {
Activity activity = getCurrentActivityInstance();
Intent intent = activity.getIntent();
activity.finish();
activity.startActivity(intent);
}
Thanks!

how to catch uncaught exception (globally) in react native

Does anyone know what is the best way to catch all uncaught exception (globally) so that I can send a crash report back to the server? I don't seem to be able to find any information on the react native docs or on github.
You could possibly override the exception logging that React Native uses for development:
ErrorUtils.setGlobalHandler(function() {
// your handler here
});
https://github.com/facebook/react-native/blob/522fd33d6f3c8fb339b0dde35b05df34c1233306/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js#L46
You may then need to write some Obj-C which you expose to JS, depending on your exact requirements.
This is how I'd do it:
Step 1: We intercept react-native error handler like so:
//intercept react-native error handling
if (ErrorUtils._globalHandler) {
this.defaultHandler = ErrorUtils.getGlobalHandler && ErrorUtils.getGlobalHandler() || ErrorUtils._globalHandler;
ErrorUtils.setGlobalHandler(this.wrapGlobalHandler); //feed errors directly to our wrapGlobalHandler function
}
Step 2: Now our wrapGlobalHandler gets called whenever theres an unhandled error. So do anything you want with the error within this function.
Then do something with the error:
async function wrapGlobalHandler(error, isFatal){
const stack = parseErrorStack(error);
//do anything with the error here
this.defaultHandler(error, isFatal); //after you're finished, call the defaultHandler so that react-native also gets the error
}
Full code here:
import stacktraceParser from 'stacktrace-parser';
const parseErrorStack = (error) => {
if (!error || !error.stack) {
return [];
}
return Array.isArray(error.stack) ? error.stack :
stacktraceParser.parse(error.stack);
};
// intercept react-native error handling
if (ErrorUtils._globalHandler) {
this.defaultHandler = (ErrorUtils.getGlobalHandler
&& ErrorUtils.getGlobalHandler())
|| ErrorUtils._globalHandler;
ErrorUtils.setGlobalHandler(this.wrapGlobalHandler); // feed errors directly to our wrapGlobalHandler function
}
async function wrapGlobalHandler(error, isFatal) {
const stack = parseErrorStack(error);
//do anything with the error here
this.defaultHandler(error, isFatal); //after you're finished, call the defaultHandler so that react-native also gets the error
}
Thats it!
You can try https://github.com/master-atul/react-native-exception-handler.
A react native module that lets you to register a global error handler that can capture fatal/non fatal uncaught exceptions. The module helps prevent abrupt crashing of RN Apps without a graceful message to the user.
There is a native way.
RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:_scriptURL
moduleProvider:^{
id<RCTExceptionsManagerDelegate> customDelegate = ...
return #[[RCTExceptionsManager initWithDelegate:customDelegate];
}
launchOptions:nil];
Just put your report logic in the customDelegate.
There's now react-native-error-reporter, which pretty much does the trick in a very simple way:
npm i react-native-error-reporter --save
rnpm link
Then add this lines to your code:
import ErrorReporter from 'react-native-error-reporter';
ErrorReporter.init("vanson#vanportdev.com", "My App's Crash Report");
In case you're using Redux, you might wanna try redux-catch middleware.