I'm trying to figure out the "correct" way to set up push notifications on an Ionic v3 app using the OneSignal SDK and native plugin (iOS is all I care about, for the moment). My goal is to set up an observer using the addSubscriptionObserver hook provided by the plugin, and update my database with the userId when the user opts in to notifications. I believe the problem I'm having has to do with the scope the callback is running in. I am trying to access the userService (injected into the component where the subscriptionObserver is added), but I am getting the following error when the callback runs:
2018-04-22 09:59:53.977213-0700 WodaX[2610:692168] Error in Success callbackId: OneSignalPush492317370 : TypeError: undefined is not an object (evaluating 'this.userService.updateUserOneSignalId')
Here is my code:
initializeApp() {
this.platform.ready().then(() => {
this.statusBar.styleDefault();
this.statusBar.overlaysWebView(false);
this.keyboard.disableScroll(false);
this.splashScreen.hide();
// OneSignal Code start:
// Enable to debug issues:
//window["plugins"].OneSignal.setLogLevel({logLevel: 4, visualLevel: 4});
let notificationOpenedCallback = function(jsonData) {
console.log('notificationOpenedCallback: ' + JSON.stringify(jsonData));
};
let iosSettings = {};
iosSettings["kOSSettingsKeyAutoPrompt"] = false;
window["plugins"].OneSignal
.startInit("[MY_ONE_SIGNAL_APP_ID]")
.handleNotificationOpened(notificationOpenedCallback)
.inFocusDisplaying(window["plugins"].OneSignal.OSInFocusDisplayOption.Notification)
.iOSSettings(iosSettings)
.endInit();
window["plugins"].OneSignal.addPermissionObserver(this.handlePermissionChange);
window["plugins"].OneSignal.addSubscriptionObserver(this.handleSubscriptionChange);
});
}
openPage(page) {
this.nav.setRoot(page.component);
}
private handleSubscriptionChange(data: any) {
console.log(data)
if (data.to.userSubscriptionSetting && data.to.userId) {
let userUuid = "testId"
this.userService.updateUserOneSignalId(userUuid, data.to.userId).subscribe((res) => {
console.log(res);
}, (err) => {
console.log(err);
});
}
}
The same call to the userService method works in a the getIds callback in a different component, so I'm guessing this has something to do with how the callbacks are registered by the addSubscriptionObserver method.
Here is the operative plugin code:
OneSignal.prototype.addSubscriptionObserver = function(callback) {
OneSignal._subscriptionObserverList.push(callback);
var subscriptionCallBackProcessor = function(state) {
OneSignal._processFunctionList(OneSignal._subscriptionObserverList, state);
};
cordova.exec(subscriptionCallBackProcessor, function(){}, "OneSignalPush", "addSubscriptionObserver", []);
}
I'd appreciate insight from anyone who may have successfully set this up. The goal is to be able to store individual OneSignal Ids so users can trigger notifications to other users. I can do this now, but only if I do it when the user accepts the prompt. I need to be able to do it anytime they opt in to push notifications, even from the settings after declining the original prompt.
So you have faced pretty common pitfall handling 'this' during a call of a function with a callback method.
Normally if you see those "undefined" type of errors you want to console.log 'this' (console.log(this)) to see which scope 'this' points at and then decide a fix.
In your case I would use fat arrow function assignment for handleSubscriptionChange method:
instead: handleSubscriptionChange(data:any) => {...}
do: handleSubscriptionChange = (data:any) => {...}
Fat arrow function doesn't create its own 'this' and in this case should solve you problem.
Related
I'll try to explain this weird situation as simple as I can.
I've created an operation hook "before save" and make it in a mixin to add it to some models.
this mixin uses context.options to get current userId to do something.
this mixin is working perfectly if I call the operation directly (like POST /Accounts for example).
But if I call it inside a remote method, the context.options is empty, for example, if we have a method called POST /Accounts/Signup, and inside it, we call Account.create(...), the "before save" hook receives the options as empty object {}
A sandbox project has been hosted here
https://github.com/mustafamagdy/loopback-sandbox-issue
the mixin code snippet is as follows:
module.exports = function(Model, options) {
Model.observe("before save", async function(ctx) {
if (ctx.instance.id) return;
const userId = ctx.options && ctx.options.accessToken && ctx.options.accessToken.userId;
if (userId) {
//... do stuff
}
else
{
console.error("Failed to scope " + Model.name + " to user (null)");
}
});
};
After the investigation, I found this issue that talks about similar behaviour, however, the comments are very destractive. So I thoughlt to write the conclusion here for anyone who are facing the same issue.
Loopback require you to pass the options you declared from the remote method to the model method(s) if you want to receive it on operation hook, so I ended up doing so.
module.exports = function(Note) {
Note.makeNew = makeNew;
async function makeNew(options) {
await Note.create(obj, options);
}
};
I have one function in my app getServerData() which I call from home page and passing Token as param in my API calling in this function.
if Token is valid API will return data otherwise it will return unauthorised access with token expired error at that time I am calling same function with new generated token form another API but some how recursive function calling not working in Observable.
Check below code for more detail :
/**
* Get Search result from server.
*/
getServerData(searchText: string): Observable<any> {
let self = this;
return Observable.create(function(observer) {
self.getToken().then((token) => {
console.log('Token : ', token);
self.httpPlugin.get(self.url + searchText, {}, {}).then((response) => {
console.log("Response Success : " + JSON.stringify(response));
observer.next(jsonResponse);
}).catch(error => {
if (error.status == 403) {
//Call Same method Again
self.getServerData(searchText);
} else {
console.log("Error : " + error);
console.log("Error " + JSON.stringify(error));
observer.error(error);
}
});
}).catch((error) => {
observer.error(error);
console.log("Error : " + error);
})
});
}
While calling same function no code execution done.
Edit based on comment:
I am subscribe like below:
this.subscription = this.api.getServerData(this.searchString.toUpperCase()).subscribe((response: any) => {
console.log("back with data :-",response);
}, error => {
console.log("InLine Error : ",error);
});
Not able to understand whats going wrong or Am I doing some mistake in calling function from Observable().
Guide me on this.
Thanks in advance!
It's not good practice to use promise in observable. Use Obserable.fromPromise and also use mergeMap. What will happen if you will use. Whenever any error will come Observable will throw error and you will able to catch. I will suggest to use Subject rather than creating your own observable and also remember one thing that don't subscribe in your service.
Hope it will help
Finally after lots of research I found solution of my issue.
1st thing was I need to update my rxjx library as my installed version of rxjx was 5.5.2 so I upgraded it to latest one 5.5.11.
2nd thing was I am calling Observable without subscribe() to that Observable so it will never return so I updated my recursive call from error block from where I call its subscriber() like below.
getSearchData(){
this.subscription = this.api.getServerData(this.searchString.toUpperCase()).subscribe((response: any) => {
console.log("back with data :-",response);
}, error => {
if (response.status == 403) {
this.getSearchData();
}else{
console.log("InLine Error : ",response);
this.showAlert('Error', 'Something went wrong. please try again.');
}
});
}
By doing above 2 things I am able to solve my issue.
Thanks all for your quick reply on my issue.
Hope this will help someone who is facing same issue like me.
I am using Redux and trying to make a call to Facebook API with their JS SDK. I've only ever used promises with Redux and so since the method FB.getLoginStatus just returns a simple JS object, I'm not sure how to ensure that the payload doesn't return undefined.
With redux-promise, you add it to the applyMiddleware(ReduxPromise)... and then it ensures nothing is returned until the promise resolves. But I don't know how to do that here.
I've also used async/await functions with React Native without an issue, but I tried using them here and for some reason the code still returns the payload, before the asynchronous request (await ...) is finished. So I tried working with redux-await, but couldn't get it to work.
export function getLoginStatus() {
var res = FB.getLoginStatus(function(res) {
console.log(res);
});
console.log("res ", res);
return {
type: GET_LOGIN_STATUS,
payload: res
}
}
Hm, things can get a little tricky as I've not used redux-promise. And I can't tell exactly what else you have tried. But this would be my first shot:
async function _getLoginStatus() {
var payload = new Promise( (resolve, fail) => {
FB.getLoginStatus((res)=>resolve(res));
});
return {
type: GET_LOGIN_STATUS,
payload: payload
}
}
// Last time I exported an async function I needed this HYMMV
export let getLoginStatus = _getLoginStatus;
And then elsewhere in the code:
import {getLoginStatus} from 'whatever.js';
var payloadResult = await getLoginStatus();
When user refresh a certain page, I want to set some initial values from the mongoDB database.
I tried using the onRendered method, which in the documentation states will run when the template that it is run on is inserted into the DOM. However, the database is not available at that instance?
When I try to access the database from the function:
Template.scienceMC.onRendered(function() {
var currentRad = radiationCollection.find().fetch()[0].rad;
}
I get the following error messages:
Exception from Tracker afterFlush function:
TypeError: Cannot read property 'rad' of undefined
However, when I run the line radiationCollection.find().fetch()[0].rad; in the console I can access the value?
How can I make sure that the copy of the mongoDB is available?
The best way for me was to use the waitOn function in the router. Thanks to #David Weldon for the tip.
Router.route('/templateName', {
waitOn: function () {
return Meteor.subscribe('collectionName');
},
action: function () {
// render all templates and regions for this route
this.render();
}
});
You need to setup a proper publication (it seems you did) and subscribe in the route parameters. If you want to make sure that you effectively have your data in the onRendered function, you need to add an extra step.
Here is an example of how to make it in your route definition:
this.templateController = RouteController.extend({
template: "YourTemplate",
action: function() {
if(this.isReady()) { this.render(); } else { this.render("yourTemplate"); this.render("loading");}
/*ACTION_FUNCTION*/
},
isReady: function() {
var subs = [
Meteor.subscribe("yoursubscription1"),
Meteor.subscribe("yoursubscription2")
];
var ready = true;
_.each(subs, function(sub) {
if(!sub.ready())
ready = false;
});
return ready;
},
data: function() {
return {
params: this.params || {}, //if you have params
yourData: radiationCollection.find()
};
}
});
In this example you get,in the onRendered function, your data both using this.data.yourData or radiationCollection.find()
EDIT: as #David Weldon stated in comment, you could also use an easier alternative: waitOn
I can't see your collection, so I can't guarantee that rad is a key in your collection, that said I believe your problem is that you collection isn't available yet. As #David Weldon says, you need to guard or wait on your subscription to be available (remember it has to load).
What I do in ironrouter is this:
data:function(){
var currentRad = radiationCollection.find().fetch()[0].rad;
if (typeof currentRad != 'undefined') {
// if typeof currentRad is not undefined
return currentRad;
}
}
I kept having this error when i deploy my app onto meteor cloud server.
Meteor code must always run within a Fiber
at _.extend.get (app/packages/meteor/dynamics_nodejs.js:14:13)
at _.extend.apply (app/packages/livedata/livedata_server.js:1268:57)
at _.extend.call (app/packages/livedata/livedata_server.js:1229:17)
at Meteor.startup.Meteor.methods.streamTwit (app/server/server.js:50:24)
however, I have already wrapped within Fibers
streamTwit: function (twit){
var userid = '1527228696';
twit.stream(
'statuses/filter',
{ follow: userid},
function(stream) {
stream.on('data', function(tweet) {
Fiber(function(){
if(tweet.user.id_str === userid)
{
Meteor.call('addQn', tweet);
}
}).run();
console.log(tweet);
console.log('---------------------------------------------------------');
console.log(tweet.user.screen_name);
console.log(tweet.user.name);
console.log(tweet.text);
});
}
);
}
I don't know what's the reason but someone suggested that i should wrap it with Meteor.bindEnvironment instead. Hence, I did this:
streamTwit: function (twit){
this.unblock(); // this doesn't seem to work
console.log('... ... trackTweets');
var _this = this;
var userid = '1527228696';
twit.stream(
'statuses/filter',
{ follow: userid},
function(stream) {
stream.on('data', function(tweet) {
Meteor.bindEnvironment(function () {
if(tweet.user.id_str === userid)
{
Meteor.call('addQn', tweet);
}
}, function(e) {
Meteor._debug("Exception from connection close callback:", e);
});
console.log(tweet);
console.log('---------------------------------------------------------');
console.log(tweet.user.screen_name);
console.log(tweet.user.name);
console.log(tweet.text);
});
}
);
}
//add question method
addQn:function(tweet){
questionDB.insert({'tweet': tweet, 'date': new Date()});
}
but now it doesn't even work. I realise that this only happened when I tried to insert some data into mongodb.
May I know what is the problem with my code? Thanks!
All these codes were written in app/server/server.js
You shouldn't need to use Meteor.call on the server side. That is for client-side code only. Just call addQn directly or better yet, inline it since it's just one line of code.