Strange memory leaks with ionic while initializing push notifications cause freezing - ios

I'm facing some very strange memory issues since a couple of days.
The problem is, that ocassionally the apps is stuck and starts to increase the memory usage very fast until it crashes. While the memory is increasing, the app is freezing completly.
After some debugging I identified that this code is causing the error:
angular.module('app.shared').factory('PushNotificationService', PushNotificationService);
PushNotificationService.$inject = [
'$q',
'MessagingService'
];
function PushNotificationService($q, MessagingService) {
var me = this;
initialize();
return {
getStartupMessage: getStartupMessage,
fetchToken: fetchToken
};
/**
* Constructor
* #return {[type]} [description]
*/
function initialize() {
me.pusher = null;
me.deviceToken = null;
me.startupMessage = null;
}
/**
* Fetches the push token through device interface
*
* #return {$q} Promises
*/
function fetchToken() {
if (me.pusher != null) {
return $q(function(resolve, reject) {
console.log('PushService.fetchToken(): Got pusher', me.pusher);
// when pusher was already initialized, we do not need to fetch it again
console.log('PushService.fetchToken(): Token was already retrieved', me.deviceToken);
resolve(me.deviceToken);
});
}
console.log('PushService.fetchToken(): Needs to fetch token bc not retrieved yet');
return $q(function(resolve, reject) {
document.addEventListener('deviceready', function() {
console.log('PushService.fetchToken(): Device is ready', typeof resolve, typeof reject);
resolve(true);
}, false);
}).then(function() {
console.log('PushService.fetchToken(): No pusher retrieved yet, do it now');
return __initialize();
}).then(function(push) {
console.log('PushService.fetchToken(): Got pusher and start attaching events to it', push);
return $q(function(resolve, reject) {
push.on('error', function(error) {
console.log('PushNotificationService.fetchToken(): Error while retrieving token', error.message, error);
reject(error);
});
push.on('registration', function(data) {
console.log('PushNotificationService.fetchToken(): Successfully retrieved token', data);
me.deviceToken = data.registrationId;
resolve(data.registrationId);
});
console.log('PushNotificationService.fetchToken(): Eventhandlers of pusher', push._handlers);
});
});
}
/**
* Initializes the push notifications and tries to fetch the Push token
* #return {Cordova.Pusher} [description]
*/
function __initialize() {
me.pusher = PushNotification.init({
android: {
senderID: "288503736094"
},
ios: {
alert: true,
badge: true,
sound: true,
clearBadge: true
},
windows: {}
});
me.pusher.on('notification', __incomingNotification);
return me.pusher;
}
// additional code which is not relevant here...
// .....
// .....
}
It happens only on iOS and is completly random, there is no way of finding a system in the crashes.
Debugging log looks like that:
PushService.fetchToken(): Needs to fetch token bc not retrieved yet
PushService.fetchToken(): Device is ready function function
PushService.fetchToken(): No pusher retrieved yet, do it now
PushService.fetchToken(): Got pusher and start attaching events to it {"_handlers":{"registration":[],"notification":[null],"error":[]},"options":{"android":{"senderID":"288503736094"},"ios":{"alert":true,"badge":true,"sound":true,"clearBadge":true},"windows":{}}}
PushNotificationService.fetchToken(): Eventhandlers of pusher {"registration":[null],"notification":[null],"error":[null]}

It was an issue in the cordova-ios which caused massive memory leaks in random locations.
Upgrading to latest version fixed it!

Related

Safari dropping Web Socket connection due to idle/inactivity when page not in focus

We are facing this issues with our app, only in safari browser, especially on iOS devices.
Current behavior
Not sure if this is a known issue (I tried searching but found nothing). Safari for Mac appears to be silently dropping web socket connections due to inactivity/idle if the page/tab is not in focus.
The biggest issue is that in mobile iOS X is very persistent.
Steps to reproduce
Open Safari > Website loads > Put Safari in Idle and open any application or lock the device.
On wake up, Safari is closing the connection and the data is not displayed anymore, we get infinite loading of the modules where we request the data.
Expected behavior
Websockets should be kept alive via the heartbeat functionality. Not seeing this behavior in other browsers so unlikely to be the code.
Is this possibly some sort of power-saving feature that is overriding/ignoring the heartbeats?
import 'whatwg-fetch';
import Config from "../config/main";
import WS from "./websocket";
import Helpers from "./helperFunctions";
var Zergling = (function (WS, Config) {
'use strict';
var Zergling = {};
var subscriptions = {}, useWebSocket = false, sessionRequestIsInProgress = false, loginInProgress = false,
uiLogggedIn = false, // uiLogggedIn is the login state displayed in UI (sometimes it differs from real one, see delayedLogoutIfNotRestored func)
authData, session, connectionAvailable, isLoggedIn, longPollUrl;
Zergling.loginStates = {
LOGGED_OUT: 0,
LOGGED_IN: 1,
IN_PROGRESS: 2
};
Zergling.codes = { // Swarm response codes
OK: 0,
SESSION_LOST: 5,
NEED_TO_LOGIN: 12
};
function getLanguageCode (lng) {
if (Config.swarm.languageMap && Config.swarm.languageMap[lng]) {
return Config.swarm.languageMap[lng];
}
return lng;
}
//helper func for fetch
function checkStatus (response) {
if (response.status >= 200 && response.status < 300) {
return response;
} else {
var error = new Error(response.statusText);
error.response = response;
throw error;
}
}
//helper func for fetch
function parseJSON (response) {
return response.json();
}
/**
* #description returns randomly selected(taking weight into consideration) long poll url
* #returns {String} long polling URL
*/
function getLongPollUrl () {
if (!longPollUrl) {
longPollUrl = Helpers.getWeightedRandom(Config.swarm.url).url;
console.debug('long Polling URL selected:', longPollUrl);
}
return longPollUrl;
}
/**
* #description
* Applies the diff on object
* properties having null values in diff are removed from object, others' values are replaced.
*
* Also checks the 'price' field for changes and adds new field 'price_change' as sibling
* which indicates the change direction (1 - up, -1 down, null - unchanged)
*
* #param {Object} current current object
* #param {Object} diff received diff
*/
function destructivelyUpdateObject (current, diff) {
if (current === undefined || !(current instanceof Object)) {
throw new Error('wrong call');
}
for (var key in diff) {
if (!diff.hasOwnProperty(key)) continue;
var val = diff[key];
if (val === null) {
delete current[key];
} else if (typeof val !== 'object') {
current[key] = val;
} else { // diff[key] is Object
if (typeof current[key] !== 'object' || current[key] === null) {
current[key] = val;
} else {
var hasPrice = (current[key].price !== undefined);
var oldPrice;
if (hasPrice) {
oldPrice = current[key].price;
}
destructivelyUpdateObject(current[key], val);
if (hasPrice) {
current[key].price_change = (val.price === oldPrice) ? null : (oldPrice < val.price) * 2 - 1;
}
}
}
}
}
This is and iOS feature that protects users against code draining their battery...
Push notifications for background applications should be performed using iOS's push notification system rather than by keeping an open connection alive.
There are hacks around this limitation, but the truth is that the limitation is good for the users and shouldn't be circumvented.
Read the technical note in the link for more details.

No cloud-to-device message received on a module

I had created a module to receive cloud to device message, but no message was received on the module, is there any coding I missing?
var Transport = require('azure-iot-device-mqtt').Mqtt;
var Client = require('azure-iot-device').ModuleClient;
Client.fromEnvironment(Transport, function (err, client) {
if (err) {
throw err;
} else {
client.on('error', function (err) {
throw err;
});
// connect to the Edge instance
client.open(function (err) {
if (err) {
throw err;
} else {
console.log('IoT Hub module client initialized');
client.on('message', function (msg) {
client.complete(msg, printResultFor('Receiving message'));
var message = msg.getBytes().toString('utf8');
console.log('----');
console.log(message);
console.log('----');
var outputMsg = new Message(message);
client.sendOutputEvent('output1', outputMsg, printResultFor('Sending received message'));
});
client.on('error', function (err) {
console.error(err.message);
});
console.log('now listening for C2D messages...');
}
});
}
});
Monitor
[C2DMessageMonitor] Message Received: "[{\"machine\":{\"temperature\":40.750164436176497,\"pressure\":1.0854617712099808},\"ambient\":{\"temperature\":40.450729128416036,\"humidity\":26},\"timeCreated\":\"2018-08-06T13:28:52.0375008Z\"}]"
[C2DMessageMonitor] Status: MessageCompleted
C2D messages are not yet officially supported by IoT edge. It may work for some protocols as the support is being built out but nothing has been validated at this time.

Cordova app with SQLite database freezes after first run in iOS but works fine in Android

I use SQlite in a Cordova app built with AngularJS. I bootstrap the app onDeviceReady() and then check if a database and a specific table exist and based on the result do certain things. The app works as expected in Android but in iOS it runs the fist time and then it gets blocked on the white page of the simulator or device! Can anyone give me an insight on how to resolve this issue?
var db = window.sqlitePlugin.openDatabase(
// options
{
name: "users_info.db",
location: 2
},
// success callback
function (msg) {
// console.log("success: " + msg);
// console.log(msg);
},
// error callback
function (msg) {
// console.log("error: " + msg);
}
);
db.transaction(function (tx) {
tx.executeSql("SELECT name FROM sqlite_master WHERE type='table' AND name='user'", [], function (tx, result) {
if (result.rows.length == 0) {
console.log('theres no table with this name ');
$scope.$apply(function () {
$location.path('/login');
});
} else {
tx.executeSql(
"select * from user;",
[],
function (tx, res) {
var row = res.rows.item(0);
console.log(row.role);
if (row.role == 4) {
$scope.$apply(function () {
userService.roleid = '4';
userService.userid = row.userid;
$location.path('/student');
});
}
});
console.log('this table exists');
}
});
});
I have also tried the following codes to open the database but again in iOS the app freezes after the first run
var db = $cordovaSQLite.openDB({ name: "users_info.db", location: 1, iosDatabaseLocation: 'default' },
// success callback
function (msg) {
// console.log("success: " + msg);
console.log(msg);
console.log('success');
},
// error callback
function (msg) {
console.log("error: " + msg);
console.log('error');
}
);
I found out that the reason of the issue was that my page containing the code snippet for the SQLite database refreshed twice when loading which caused the codes to read from and write to the SQlite database twice simultaneously which cause the database and consequently the iOS app to freeze. I made sure the codes run only once and it worked like a charm!

Is there a way to enforce single device login on parse.com?

I am developing an app where I need to be very API request frugal, the less requests the better. The problem is every user has settings and messages and I want to avoid to pull for possible changes on every wake up. And I can't rely on that every user enables push notifications.
My approach is as a compromise to enforce that a user can only be logged in with one device. If they try to login with another device (via facebook) they get an error message where they can choose to either cancel the login or go ahead and logout the other device remotely.
Is this possible?
I found a solution to this problem.
Query number of sessions after login
If the number is greater than 1 ask user what do
logout other device (and go ahead) -> call "deleteAllOtherSessions"
cancel login (and go back to login screen) -> call "deleteLastSession"
Cloud code:
Parse.Cloud.define("getSessionCount", function(request, response) {
if (request.user == null) {
reportError("findSessions", "userCheck", 0);
response.error("invalid user");
return
}
var query = new Parse.Query(Parse.Session);
query.find({
success: function(results) {
response.success(results.length);
},
error: function(error) {
response.error(error);
}
});
});
Parse.Cloud.define("deleteAllOtherSessions", function(request, response) {
if (request.user == null) {
reportError("deleteAllOtherSessions", "userCheck", 0);
response.error("invalid user");
return
}
var sessionToken = request.params.sessionToken;
var query = new Parse.Query(Parse.Session);
// this query will find only sessions owned by the user since
// we are not using the master key
query.find().then(function(results) {
var promises = [];
_.each(results, function (result) {
if(result.get("sessionToken") != sessionToken) {
promises.push(result.destroy());
}
});
return Parse.Promise.when(promises);
}).then(function() {
response.success(true);
},
function(error) {
response.error(error)
});
});
Parse.Cloud.define("deleteLastSession", function(request, response) {
if (request.user == null) {
reportError("deleteLastSession", "userCheck", 0);
response.error("invalid user");
return
}
var sessionToken = request.params.sessionToken;
var query = new Parse.Query(Parse.Session);
query.descending("createdAt");
// this query will find only sessions owned by the user since
// we are not using the master key
query.find().then(function(results) {
var promises = [];
console.log(results);
promises.push(results[0].destroy());
return Parse.Promise.when(promises);
}).then(function() {
response.success(true);
},
function(error) {
response.error(error)
});
});
Hope that helps somebody.

Loading script after page fully loaded

I am building a firefox addon that loads javascript at every page load. I'm using progress listener function I found on this page: https://developer.mozilla.org/en/Code_snippets/Progress_Listeners
My problem is that the code seems to execute to early before the page is fully loaded which causes my script to not run. Here is my code.
var PageLoad = {
browser: null,
domain: null,
oldURL: null,
init: function() {
gBrowser.addProgressListener(urlBarListener,Components.interfaces.nsIWebProgress.NOTIFY_LOCATION);
},
uninit: function() {
gBrowser.removeProgressListener(urlBarListener);
},
processNewURL: function(aURI) {
//if (aURI.spec == this.oldURL)
//return;
MyObject.function();
this.oldURL = aURI.spec;
}
};
var urlBarListener = {
locChange: false,
QueryInterface: function(aIID) {
if (aIID.equals(Components.interfaces.nsIWebProgressListener) ||
aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
aIID.equals(Components.interfaces.nsISupports))
return this;
throw Components.results.NS_NOINTERFACE;
},
onLocationChange: function(aProgress, aRequest, aURI) {
PageLoad.processNewURL(aURI);
},
onStateChange: function(aWebProgress, aRequest, aFlag, aStatus) {},
onProgressChange: function(a, b, c, d, e, f) {},
onStatusChange: function(a, b, c, d) {},
onSecurityChange: function(a, b, c) {}
};
window.addEventListener("load",
function() {
PageLoad.init()
}, false);
var MyObject = {
function : function() {
var script = PageLoad.browser.createElement('script');
script.src = 'url_to_script.js';
PageLoad.browser.getElementsByTagName('head')[0].appendChild(script);
}
};
With this code I get this error message on the console:
PageLoad.browser.getElementByTagName("head")[0] is undefined
If I add a timeout then the script does work intermittently but if the page loads slow I get the same error, here's what works sometimes setTimeout(MyObject.function, 1000);
I need a more reliable way of making sure it's executing the script after the page is loaded.
Unrelated, and it doesn't seem to cause any problems but I also see this error message:
gBrowser.addProgressListener was called with a second argument, which is not supported. See bug 608628.
If you want to load javascript at every page load - the best way is subscribing to DOMContentLoaded event:
var MyObject = {
processDOMContentLoaded: function(doc) {
var script = doc.createElement('script');
script.src = 'url_to_script.js';
script.type = 'text/javascript';
doc.getElementsByTagName('head')[0].appendChild(script);
}
};
window.addEventListener('load', function() {
var appcontent = document.getElementById('appcontent');
if(appcontent != null) {
appcontent.addEventListener('DOMContentLoaded', function(event) {
var doc = event.originalTarget;
if(doc instanceof HTMLDocument) {
MyObject.processDOMContentLoaded(doc);
}
}, true);
}
}, false);
Have not tested, but should work.
You are using onLocationChange method - but if you look at how the browser behaves, the location in the address bar changes as soon as a connection with the server is established. You should look at state changes instead:
onStateChange: function(aWebProgress, aRequest, aFlag, aStatus)
{
if ((aFlag & Components.interfaces.nsIWebProgressListener.STATE_STOP) &&
(aFlag & Components.interfaces.nsIWebProgressListener.STATE_IS_WINDOW))
{
// A window finished loading
PageLoad.windowLoaded(aWebProgress.DOMWindow);
}
},
Note that the window that finished loading is explicitly passed to PageLoad.windowLoaded() - you will be receiving notifications from different tabs and you cannot assume that the notification comes from the foreground tab.
As to the warning you are getting, just leave out the second parameter in the call to gBrowser.addProgressListener():
gBrowser.addProgressListener(urlBarListener);
tabbrowser.addProgressListener() only accepts one parameter, unlike nsIWebProgress.addProgressListener() which has a second parameter.
Actually its a great question.
You should use event listener, but carefully, because if you trigger for every page load its can trigger you more than one time (in the worst case about dozens of times).
So how you can do that?
window.addEventListener("load", function load(event){
window.removeEventListener("load", load, false); //remove listener, no longer needed
myExtension.init();
},false);
var myExtension = {
init: function() {
var appcontent = document.getElementById("appcontent"); // browser
if(appcontent){
appcontent.addEventListener("DOMContentLoaded", myExtension.onPageLoad, true);
}
},
onPageLoad: function(aEvent) {
var doc = aEvent.originalTarget; // doc is document that triggered the event
var win = doc.defaultView; // win is the window for the doc
if (doc.nodeName != "#document") return;
if (win != win.top) return;
if (win.frameElement) return;
alert("the main page has been loaded");
},
};
get notice that we check for the type of the trigger every pageload triggering to prevent multi load.
The answers that were provided were acceptable but I found yet another solution that works perfectly.
var PageLoad = {
init: function() {
if(gBrowser) gBrowser.addEventListener("DOMContentLoaded", this.onPageLoad, false);
},
onPageLoad: function(aEvent) {
var doc = aEvent.originalTarget; // doc is document that triggered the event
var win = doc.defaultView; // win is the window for the doc
if (doc.nodeName != "#document") return;
if (win != win.top) return;
if (win.frameElement) return;
MyAddon.function();
}
}
window.addEventListener("load", function load(event){
window.removeEventListener("load", load, false); //remove listener, no longer needed
PageLoad.init();
},false);

Resources