I have a cordova-based app that runs on Android, iOS and Windows Phone. The starting point in my app is the index.html page, which will not only be loaded on app start, but you can redirect to it from inside the app.
I want to execute some code if and only if the app was just started (so when the index.html was displayed the first time) and not if it was redirected to it. I tried to use cookies that expire when the session ends, but cordova does not work with cookies.
Also I do not want to use session storage because some older Androids (as well as Internet Explorer) cannot handle this.
My used cordova version is 4.0.0
EDIT:
I forgot to mention that it is not a single-page app, but I use multiple pages that can be accessed, so the deviceready-event does not work, because it would be fired every time I access the index.html-page
Try smth like this:
function documentReady() {
document.addEventListener("deviceready", handleDeviceReady, false);
}
function handleDeviceReady(event) {
//cordova api is ready for use
if (!localStorage.getItem('alreadyStarted')) {
// App just started!
localStorage.setItem('alreadyStarted', true);
}
}
UPD. Also you need to set the flag at first startup (i.e. alreadyStarted = true in sessions or LocalStorage) and check it in handleDeviceReady() later.
Write to the database at first run. Everytime the app starts, check the database to see if that string is present. If it it, ignore. Else, create and write.
Related
This questions concernes a web app build in React that will be accessed with smartphones through their browsers. I use iPhone with both Safari and Chrome for testing.
One step involves opening a native authentication app.
According to the docs of the native app, it can be open from the browser by doing this:
const openAuthApp = () =>
(window.location = "https://app.bankid.com/?autostarttoken=&redirect=");
This works fine if I call the function when clicking a button, like this:
<button onClick={openAuthApp}>Open</button>
The above code triggers the opening of the authentication app immediately when clicking the button.
But when I trigger the function immediately after page has loaded, without using a button, like this
useEffect(() => {
openAuthApp();
}, []);
I get an error in the browser saying that the app was not found on this device.
Idk much about how browsers work but my first guess was that it takes some time for the browser to acquire information about all installed apps, so I added a timeout before executing the method:
useEffect(() => {
setTimeout(openAuthApp, 5000);
}, []);
It still failed. It works if I press the button less than 5 seconds after page load, so the time of initiation after page load shouldn't be the factor here.
I don't know how to proceed with this, and would appreciate ideas on how to move forward.
I suspect that your problem is a missing user gesture, which is common when using Claimed HTTPS Schemes - eg see this Chrome browser requirement.
There is a similar problem when using OAuth flows and the AppAuth pattern with HTTPS redirect URIs, which occurs for both iOS and Android. See the sections titled Problems Receiving Redirect Responses in my iOS and Android blog posts.
The solution for my mobile samples was to add an interstitial web page and if you do a view source you will see that it has an onclick handler that invokes a deep link after the user gesture:
document.getElementById('continueButton').onclick = () => {
const redirectUri = 'https://mobile.authsamples.com/basicmobileapp/oauth/callback';
window.location.href = redirectUri;
};
You won't need to go to these lengths of course, but I think you will need a user gesture to invoke the BankID app and do an external login reliably. In some ways this is a reasonable UX also, since you are keeping the user informed before you switch apps, rather than doing so abruptly. I would put a positive spin on it like this:
You will now be redirected to the BankID app to perform strong authentication and to provide consent. Please click Next to proceed.
Option 1:
Use window.location.href instead of window.location
const openAuthApp = () =>
(window.location.href = "https://app.bankid.com/?autostarttoken=&redirect=");
If your web app have the same domain as bankid.com use window.location.assign instead.
const openAuthApp = () =>
(window.location.assign= "https://app.bankid.com/?autostarttoken=&redirect=");
Option 2:
this will take 5 minutes, use branch.io for links (you do not need to install the SDK)
signup create new app, write the name of the app
got to "Configuration" from left menu select "I have an Android App"
add the link for your app and select it then fill other options if you like
it will give you a link "https://[YOUR_APP_ID ].app.link"
use this link instead of the default link
This should work without problem
I serve the static webapp on aws s3 that distributed through CDN (aws cloudfront). The files are ES6 based build version with rollup. For short info, except index.html rollup will generate new hash files every build the webapp. So the files are always unique every update, except index.html. Then on the aws cloudfront I put the index.html into invalidation cache list.
Well then, I will expect users always request the latest version of the webapp with that approach. Yes it works, but with a little note!
So once there is new updates, the browser is still loading the old index.html file on the first time. I have to refresh page to push the browser get the latest index.html. It's not good for the end users. They doen't want to know about refreshing, most will not know right?
One last experiment, I added small script inside on index.html to perform version validation like so :
<script>
fetch('/version.json').then(r => {
if (r.status == 200) {
return r.json()
} else {
alert("Found server updated, let us resync the contents!")
location.reload(true)
}
}).then(j => {
if (window.localStorage.getItem("app-version")) {
if (j.version != window.localStorage.getItem("app-version")) {
alert("Found server updated, let us sync the contents!")
window.localStorage.setItem("app-version", j.version)
location.reload(true)
}
} else {
window.localStorage.setItem("app-version", j.version)
}
})
</script>
That script worked as expected but wondering whether I will have better solution out there? Kindly to have another idea, please?
Expected behavior
Browser have knowledge the latest index.html immediately without any refresh/reload page from end users.
Thank you
Just use a timestamp in the URL to force the browser to get the latest version.
(()=>{
// Get the current timestamp
var now = new Date().getTime();
// Check for a ts parameter in the url (index.html?ts=2345234523)
const urlParams = new URLSearchParams(location.search);
var ts = urlParams.get('ts');
// If there is no timestamp parameter,
// or the timestamp parameter is older than 1 minute
// redirect the page to the latest version
if(!ts || now - ts > 60000){
location.href = `index.html?ts=${now}`;
}
})();
You'll want to put that near the top so you don't have a lot of things loading before the redirect.
I would take advantage of a service-worker to make it easier and more efficient to load and cache a cohesive version of the app.
The service worker has a built in mechanism for checking whether the app has been updated, which is does each time the app is launched.
What it won't do is trigger that check for you while the app is running. While you could poll regularly to check for updates, it's more efficient for both your server and users to have them subscribe for updates which you can do using something like Firebase RTDB (Real Time Database):
When you publish a new version and want to immediately force all running instances to update, you change the version or timestamp that they are subscribing to and have that trigger the service-worker update check, which then refreshes and reloads the app.
There are lots of available patterns for prompting users about any update in case they are in the middle of completing a form etc...
Goal:
I have a ReactJs web app called "ImageGallery" that's currently integrated in our business suite system(which is a huge system contains older technologies, mainly web forms). In our system, we need to add a button to launch a browser window to spin the app.
ImageGallery app rely on the current system to pass data to it to show, which means, when the ImageGallery app loads, it calls a function on the business suite system side like
window.parent.loadData()
Since the data is passed in by JavaScript, there is no ajax call.
Code:
function showImage() {
var imageGalleryWindow = window.open('/ImageGallery', '_blank', 'height=' + window.screen.availHeight + ',width=' + window.screen.availWidth + ',scrollbars=yes,menubar=no,resizable=yes,toolbar=no,location=no,status=no');
imageGalleryWindow.loadData= loadData;
}
loadData() function is available on the current JavaScript file and will be passed to the new window so it can be used by the ReactJS app.
Problem:
The app works perfectly with Chrome/Firefox/Edge, but not IE 11. IE 11 sometimes loads, sometimes not. After taking a closer look, I found there's a race condition.
Basically whenever the app failed, loadData() function has not been passed to the newly created window. Again, that's only for IE11, all other browsers seems fine.
What I've tried:
I tried the following:
1> Tried to put a wait function in ImageGallery app like this:
static resolveDocuments(){
if(typeof window.parent.loadData === 'function'){
// do the work to show image
} else {
setTimeout(this.resolveDocuments, 2000);
}
}
I am using redux thunk, I didn't get any luck in getting this to work.
2> put imageGalleryWindow.loadData= loadData; into window.onload(), bad idea but tried anyways
3> put imageGalleryWindow.loadData= loadData; into imageViewerWindow.document.addEventListener("DOMContentLoaded", function (event) {} still bad idea but tried anyways
4> created a new ImageGallery.html, use an iframe to host ImageGallery app. In section, include the JavaScript file from parent. No luck
5> created a webform page ImageGallery.aspx, use an iframe to host ImageGallery app. In section, include the JavaScript file from parent. No luck.
Thanks for taking time to finish reading this problem, and please let me know if you have an idea I can try.
Thanks so much!
Ray
The solution I end up having is html 5 local storage:
Before calling window.open() to populate the new window, create a entry in html 5 local storage with all the data I need to pass in.
When the image gallery app loads, check if local storage items exists, if so, pull the data. The data will always be there, since it sets before new window generated, and since both pages are from the same domain, they could both access the same local storage.
To avoid security issues, after image gallery app read data from the local storage, I keep a copy of the data within the app, then delete the local storage entry.
Hope this can be helpful to other people.
I'm using the MEAN stack (mongo, express, angular and node). I'm deploying relatively frequently to production...every couple of days. My concern is that I'm changing the client side code and the API at times and I would rather not have to ensure backwards compatibility of the API with previous versions of the client code.
In such a scenario, what is the most effective way of ensuring that all clients reload when I push to production? I have seen that Evernote for example has a pop-up that says something along the lines of please reload your browser for the latest version of Evernote. I would like to do something similiar...do I need to go down the path of socket.io or sock.js or am I missing something simple and there is a simpler way to achieve this?
Update:
AppCache was deprecated summer 2015 so the below is no longer the best solution. The new recommendation is to use Service Workers instead. However, Service Workers are currently still experimental with sketchy (read: probably no) support in IE and Safari.
Alternatively, many build tools now seamlessly incorporate cache-busting and file "versioning" techniques to address OPs question. WebPack is arguably the current leader in this space.
This might be a good use case for using HTML5's AppCache
You'd probably want to automate some of these steps into your deployment scripts, but here is some code you might find useful to get you started.
First, create your appcache manifest file. This will also allow you to cache resources in the client's browser until you explicitly modify the appcache manifest file's date.
/app.appcache:
CACHE MANIFEST
#v20150327.114142
CACHE:
/appcache.js
/an/image.jpg
/a/javascript/file.js
http://some.resource.com/a/css/file.css
NETWORK:
*
/
In app.appcache, the comment on line #v20150327.114142 is how we indicate to the browser that the manifest has changed and resources should be reloaded. It can be anything, really, as long as the file will look different to the browser from the previous version. During deployment of new code in your application, this line should be modified. Could also use a build ID instead.
Second, on any pages you want to use the appcache, modify the header tag as such:
<html manifest="/app.appcache"></html>
Finally, you'll need to add some Javascript to check the appcache for any changes, and if there are, do something about it. Here's an Angular module. For this answer, here's a vanilla example:
appcache.js:
window.applicationCache.addEventListener('updateready', function(e) {
if (window.applicationCache.status == window.applicationCache.UPDATEREADY) {
// Browser downloaded a new app cache.
// Swap it in and reload the page to get the latest hotness.
window.applicationCache.swapCache();
if (confirm('A new version of the application is available. Would you like to load it?')) {
window.location.reload();
}
}
else {
// Manifest didn't changed. Don't do anything.
}
}, false);
Alternatively, if AppCache won't work for your situation, a more ghetto solution would be to create a simple API endpoint that returns the current build ID or last deployment date-time. Your Angular application occasionally hits this endpoint and compares the result to it's internal version, and if different, reloads itself.
Or, you may consider a live-reload script (example), but, while very helpful in development, I'm not sure how good of an idea it is to use live/in-place-reloading of assets in production.
I will tell you my problem first then I will recommend a tentative solution. I wanted to force my user to log out and then log in when a production build is been deployed. At any point in time, there will be two versions of software deployed on production. A version which software which FE knows and a version which Backend knows. Most of the time they would be the same. At any point in time if they go out of sync then we need to reload the client to let the client know that a new production build has been pushed.
I am assuming 99.99% of the time the backend would have the knowledge of the latest version of the deployed software on production.
following are the two approaches which I would love to recommend:-
The backend API should always return the latest version of the software in the response header. On the frontend, we should have a common piece of code that would check if the versions returned by the API and that present on the FE are the same. if not then reload.
Whenever a user logs in. the BE should encode the latest software version in the JWT. And the FE should keep sending this as a bearer token along with every API request. The BE should also write a common interceptor for every API request. which would compare the software version in the JWT received from the API request and the
Maybe you can add hash to your client code file name. eg app-abcd23.js.
So the browser will reload the file instead of get it from cache. or you can just add the hash to url.eg app.js?hash=abcd23 but some browser may still use the cached version.
i know rails has assets-pipline to handle it, but i am not familiar with MEAN stack. there should be some package in npm for that purpose.
And i dont think it is really necessary to use socket.io if you want to notify the user their client code is out of date. you can define your version in both html meta tag and js file,if mismatch, show a popup and tell the user to refresh.
Try to limit your js/files to expire within smaller periodic time, ie: 1 days.
But in case you want something that pop-out and tell your user to reload (ctrl+f5) their browser, then simply make a script that popup that news if you just changed some of your files, mark the ip/session who have just reload/told to reload, so they will not be annoyed with multiple popup.
I was facing the same problem recently. I fixed this by appending my app's build number with my js/css files. All my script and style tags were included by a script in a common include files so it was trivial to add a 'build number' at the end of the js/css file path like this
/foo/bar/main.js?123
This 123 is a number that I keep track of in my same header file. I increment it whenever I want the client to force download all the js files of the app. This gives me control over when new versions are downloaded but still allows the browser to leverage cache for every request after the first one. That is until I push another update by increment the build number.
This also means I can have a cache expiry header of however long I want.
Set a unique key to local storage during the build process
I am using react static and loading up my own data file, in there i set the ID each time my content changes
Then the frontend client reads the key with from local storage
(if the key does not exist it must be the first visit of the browser)
if the key from local storage does not match it means the content has changed
fire line below to force reload
window.replace(window.location.href + '?' + key)
in my case i had to run this same line again a second latter
like
setTimeout( (window.replace(window.location.href + '?' + key))=> {} , 1000)
full code below:
const reloadIfFilesChanged = (cnt: number = 0, manifest: IManifest) => {
try {
// will fail if window does not exist
if (cnt > 10) {
return;
}
const id = localStorage.getItem('id');
if (!id) {
localStorage.setItem('id', manifest.id);
} else {
if (id !== manifest.id) {
// manifest has changed fire reload
// and set new id
localStorage.setItem('id', manifest.id);
location.replace(window.location.href + '?' + manifest.id);
setTimeout(() => {
location.replace(window.location.href + '?' + manifest.id + '1');
}, 1000);
}
}
} catch (e) {
// tslint:disable-next-line:no-parameter-reassignment
cnt++;
setTimeout(() => reloadIfFilesChanged(cnt, manifest), 1000);
}
};
I am trying to work with Meteor. Now I have the entire setup running in my localmachine with apache2 and the meteor.js also works when browsing the same URL from Android Emulator's Browser . Now the main problem is that I need the functionality in my android app from a local URL and here the page is not able to load the remote js. I am loading the following html using WebViews loadURL method after setting the javascript as enabled .The js embedded in the html will be something like this
<script type="text/javascript" src="http://meteor.mywebserver.com/meteor.js"></script>
<script type="text/javascript">
window.onload = function()
{
Meteor.host = "meteor.mywebserver.com";
alert(textStatus);
// Call the test() function when data arrives
Meteor.registerEventCallback("process", commentsUpdate);
// Join the demo channel and get last five events, then stream
Meteor.joinChannel("demo", 0);
Meteor.mode = 'longpoll';
// Start streaming!
Meteor.connect();
// Handle incoming events
function commentsUpdate(data)
{
alert(data);
};});
After searching around a lot I tried this stackoverflow answer
To no avail . Can anybody help me find a work around here , I cant use a local meteor.js as it wont work.
Thanks
This has since been addressed in Meteor by way of integrated Cordova, which you can read about here. Basically, you tell Meteor that you want to add the Android platform to your app, and it builds the Android project files for you. Your app will look as if it's running native, but it's really just running in a light app surrounding a "web view". In iOS this is done using WebKit, but I think in Android it depends on the version of the OS.
You will still need to deploy your app to the Play store, which requires signing the app and all.