Refresh app when it's connected to the internet - javascript

At the moment I have an app with a "there's no internet conection" screen on it. This screen has a "Refresh" button that navigates the user to the previous screen, but it doesn't load anything, even when the internet connection is re-established.
onPressBack = () => {
const { navigation } = this.props;
navigation.goBack();
}
How can I refresh the app to force a componentDidMount() function to start over again to fetch all the stuff from the internet when the connection has been re-established?

You can use NetInfo to watch the internet connectivity. In the ComponentDidMount, you can addEventListener which takes an callback. Whenever the connection changes, the callback will be triggered. If the internet is connected, you can load your content/screen.
NetInfo.addEventListener(state => {
if(state.isConnected) {
// Load your content here
}
})

Related

Check connection loss in JavaScript on the browser [duplicate]

This question already has answers here:
navigator.onLine not always working
(2 answers)
Closed last month.
I referred to this Detect the Internet connection is offline? question to find out how to check if the browser is offline or online.
So I used window.navigator.onLine to find that out.
The problem is that, no matter what I do, window.navigator.onLine is always true.
I am using brave browser, but I'm not sure if that's related to the issue, it's chromium based.
I'm on Ubuntu Linux, desktop.
I just want to detect when the browser becomes offline to show a small message "connection lost".
In react the code looks as follows:
const online = window.navigator.onLine
useEffect(() => {
if (online) return
console.log("Connection lost!")
}, [online])
try to toggle your wifi on and on to see the console logs
Here's a stack blitz instance to try it out, it's a pretty small code, (Click me)
The property sends updates whenever the browser's ability to connect to the network changes. The update occurs when the user follows links or when a script requests a remote page.
https://developer.mozilla.org/en-US/docs/Web/API/Navigator/onLine
So the value won't update unless you make a request of some sort.
There are also some implementation specific notes on the same url.
In Chrome and Safari, if the browser is not able to connect to a local area network (LAN) or a router, it is offline; all other conditions return true.
In other words, if there is any sort of network access it will be true, even if you are not connected to the internet.
So the best way to check this is probably to just make a request to an API endpoint or other resource that is only available while online and base your status on if the request was successful or not. Since in most cases just being "online" isn't worth much if your API is inaccessible this would probably provide better information to your users as well.
Need to use the event listener for this: window.addEventListener('online', () => { ...});.
Inside the callback for listener, do setState to check online off-line toggle.
here is small hook i created in reactjs to handle online offline states:
import { useEffect, useState } from 'react';
// toastr alert messages
import { showOffline, showOnline } from 'utils/alerts';
const useNetworkStatus = () => {
const [state, setState] = useState(true);
async function isOnline() {
// if its offline and to check if window?.navigator is supported.
if (!window?.navigator?.onLine) {
setState(false);
return false;
}
// Failover case:
// navigator.onLine cannot be trusted: there's situation where you appear to be online (connect to a network with no internet)
// but still cannot access the internet.
// So to fix: we request to our own origin to avoid CORS errors
const url = new URL(window.location.origin);
// with random value to prevent cached responses
url.searchParams.set('rand', Date.now());
try {
const response = await fetch(url.toString(), { method: 'HEAD' });
setState(true);
return response.ok;
} catch {
setState(false);
return false;
}
}
useEffect(() => {
const setOnlineOnVisibleChange = async () => {
// if its page is visible and state was offline
if (!document?.hidden && !state) {
if (await isOnline()) showOnline();
}
};
// on visiting the page again if the state is offline and network is online, then show online alert
if ('hidden' in document)
document.addEventListener('visibilitychange', setOnlineOnVisibleChange, false);
return () => document.removeEventListener('visibilitychange', setOnlineOnVisibleChange, false);
}, [state]);
useEffect(() => {
async function changeStatus() {
if (await isOnline()) showOnline();
else showOffline();
}
// Listen for the page to be finished loading
window.addEventListener('load', () => {
// if its offline
if (!isOnline()) showOffline();
});
window.addEventListener('online', changeStatus);
window.addEventListener('offline', changeStatus);
return () => {
window.removeEventListener('online', changeStatus);
window.removeEventListener('offline', changeStatus);
};
}, []);
};
export default useNetworkStatus;

Is there a way to get Chrome to “forget” a device to test navigator.usb.requestDevice, navigator.serial.requestPort?

I'm hoping to migrate from using WebUSB to SerialAPI (which is explained nicely here).
Current Code:
try {
let device = await navigator.usb.requestDevice({
filters: [{
usbVendorId: RECEIVER_VENDOR_ID
}]
})
this.connect(device)
} catch (error) {
console.log(DEVICE_NAME + ': Permission Denied')
}
New Code:
try {
let device = await navigator.serial.requestPort({
filters: [{
usbVendorId: RECEIVER_VENDOR_ID
}]
})
this.connect(device)
} catch (error) {
console.log(DEVICE_NAME + ': Permission Denied')
}
The new code appears to work, but I think it's because the browser has already requested the device via the old code.
I've tried restarting Chrome as well as clearing all of the browsing history. Even closed the USB-claiming page and claimed the device with another app (during which it returns the DOMException: Unable to claim interface error), but Chrome doesn't seem to want to ask again. It just happily streams the data with the previous connection.
My hope was that using SerialAPI would be a way to avoid fighting over the USB with other processes, or at least losing to them.
Update
I had forgotten about:
Failed to execute 'requestPort' on 'Serial': "Must be handling a user gesture to show a permission request"
Does this mean that the user will need to use a button to connect to the device via SerialUSB? I think with WebUSB I was able to make the connect window automatically pop up.
For both APIs, as is noted in the update, a user gesture is required in order to call the requestDevice() or requestPort() method. It is not possible to automatically pop up this prompt. (If there is that's a bug so please let the Chrome team know so we can fix it.)
Permissions granted to a site through the WebUSB API and Web Serial API are currently tracked separately so permission to access a device through one will not automatically translate into the other.
There is not currently a way to programatically forget a device permission. That would require the navigator.permissions.revoke() method which has been abandoned. You can however manually revoke permission to access the device by clicking on the "lock" icon in the address bar while visiting the site or going to chrome://settings/content/usbDevices (for USB devices) and chrome://settings/content/serialPorts (for serial ports).
To get Chrome to "forget" the WebUSB device previously paired via navigator.usb.requestDevice API:
Open the page paired to the device you want to forget
Click on the icon in the address bar
Click x next to device. If nothing is listed, then there are no paired devices for this web page.
The new code was NOT working. It just appeared to be because Chrome was already paired with the port via the old code. There is no way the "new code" could have worked because, as noted in Kalido's comment, the SerialAPI (due to its power) requires a user gesture to connect.
The code I'm using to actually connect and get data is basically built up from a few pieces from the above links in the OP:
navigator.serial.addEventListener('connect', e => {
// Add |e.target| to the UI or automatically connect.
console.log("connected");
});
navigator.serial.addEventListener('disconnect', e => {
// Remove |e.target| from the UI. If the device was open the disconnection can
// also be observed as a stream error.
console.log("disconnected");
});
console.log(navigator.serial);
document.addEventListener('DOMContentLoaded', async () => {
const connectButton = document.querySelector('#connect') as HTMLInputElement;
if (connectButton) {
connectButton.addEventListener('click', async () => {
try {
// Request Keiser Receiver from the user.
const port = await navigator.serial.requestPort({
filters: [{ usbVendorId: 0x2341, usbProductId: not_required }]
});
try {
// Open and begin reading.
await port.open({ baudRate: 115200 });
} catch (e) {
console.log(e);
}
while (port.readable) {
const reader = port.readable.getReader();
try {
while (true) {
const { value, done } = await reader.read();
if (done) {
// Allow the serial port to be closed later.
reader.releaseLock();
break;
}
if (value) {
console.log(value);
}
}
} catch (error) {
// TODO: Handle non-fatal read error.
console.log(error);
}
}
} catch (e) {
console.log("Permission to access a device was denied implicitly or explicitly by the user.");
console.log(e);
console.log(port);
}
}
}
The device-specific Vendor and Product IDs would obviously change from device to device. In the above example I have inserted an Arduino vendor ID.
It doesn't answer the question of how to get Chrome to "forget", but I'm not sure if that's relevant when using SerialAPI because of the required gesture.
Hopefully someone with more experience will be able to post a more informative answer.

Workbox - Background Sync - Offline Post - Replay Events when the browser is back online doesn't get triggered

I am using workbox v4.3.1 to provide offline capability to the users of the web application.
While everything works perfectly in Chrome as you would expect a PWA to work (i.e everything is cached locally, all the updates from the APP are captured in IndexedDB and synced back to the server when the application is back online.
However the major use case for me is to provide support for iOS Safari and as a PWA.
While all the pages are cached locally using the Service Worker in Safari and all the offline updates are also captured in the indexed DB as shown below,
However, when the connection returns online, the sync event is not triggered by the browser (Safari in this case). While background sync is not supported natively by Safari, I would expect that when I refresh the page, SW initialisation should trigger the sync event manually if it finds some data to be refreshed to the server in the indexed DB.
But this is not happening and I tried to manually listen for the "message" - "replayRequests" and then replay the requests - that did not work as well.
Any help here would be appreciated. Here is the service worker code for reference.
// If we're not in the context of a Web Worker, then don't do anything
if ("function" === typeof importScripts) {
importScripts(
"https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js"
);
//Plugins
// Background Sync Plugin.
const bgSyncPlugin = new workbox.backgroundSync.Plugin("offlineSyncQueue", {
maxRetentionTime: 24 * 60
});
// Alternate method for creating a queue and managing the events ourselves.
const queue = new workbox.backgroundSync.Queue("offlineSyncQueue");
workbox.routing.registerRoute(
matchCb,
workbox.strategies.networkOnly({
plugins: [
{
fetchDidFail: async ({ request }) => {
await queue.addRequest(request);
}
}
]
}),
"POST"
);
// CacheKeyControlPlugin
const myCacheKeyPlugin = {
cacheKeyWillBeUsed: async ({ request, mode }) => {
normalizedUrl = removeTimeParam(request.url);
return new Request(normalizedUrl);
}
};
if (workbox) {
console.info("SW - Workbox is available and successfully installed");
} else {
console.info("SW - Workbox unavailable");
}
//Intercept all api requests
var matchCb = ({ url, event }) => {
// Filter out the presence api calls
return url.pathname.indexOf("somethingidontwanttocache") == -1;
};
function removeTimeParam(urlString) {
let url = new URL(urlString);
url.searchParams.delete("time");
return url.toString();
}
/* //Pre cache a page and see if it works offline - Temp code
workbox.precaching.precache(getPageAPIRequestURLs(), {
cleanUrls: false
}); */
workbox.routing.registerRoute(
matchCb,
new workbox.strategies.CacheFirst({
cacheName: "application-cache",
plugins: [myCacheKeyPlugin]
})
);
self.addEventListener("message", event => {
if (event.data === "replayRequests") {
queue.replayRequests();
}
});
}
workbox-background-sync emulates background sync functionality in browsers that lack native support by replaying queued requests whenever the service worker process starts up. The service worker process is meant to be lightweight and short lived, and is killed aggressively when there's a period of time without any events, and then is started up again in response to further events.
Reloading a web page may cause the service worker process to start up, assuming it had previously been stopped. But if the service worker is still running, then reloading the page will just cause a fetch event to be fired on the existing process.
The interval at which a service worker process can remain idle before it's killed is browser-dependent.
Chrome's DevTools offers a method of inspecting the state of a service worker and starting/stopping it on demand, but I don't believe Safari's DevTools offers that functionality. If you wanted to guanratee that a service worker was stopped and then start it up again, I would quit Safari, reopen it, and then navigate back to your web app.

How to use cookie obtained in BrowserWindow within webview

I have searched all available information on this and the documentation is not helping me.
I have an Electron app (React internals) where a web page is to be shown in a webview.
The domain e.g. https://root.domain.com stays the same but the rest of the URL will be different depending on props passed to the component showing the webview.
I found some code here linked from a Youtube video from BuildingXwithJS on testing which will use a BrowserWindow to allow a user login, then save the authenticated cookie for use within the app.
auth() {
const remote = electron.remote;
// Get a partitioned session to use with webview
const ses = remote.session.fromPartition("myPartition");
// create new electron browser window
const BrowserWindow = remote.BrowserWindow;
let win = new BrowserWindow({width: 800, height: 600});
// cleanup on close
win.on('closed', () => {
win = null;
});
// wait for page to finish loading
win.webContents.on('did-finish-load', () => {
// if auth was succesful
if (win.webContents.getURL() === 'https://root.domain.com/Home/') {
// get all cookies
win.webContents.session.cookies.get({}, async (error, cookies) => {
if (error) {
console.error('Error getting cookies:', error);
return;
}
// store cookies
cookies.forEach(c => {
if(c.domain.includes('root.domain.com')){
ses.cookies.set(c,(e) => e? console.log("Failed: %s",e.message):null})
}
// close window
win.close();
});
}
});
// Load login page
win.loadURL(loginURL);
}
Now when I view a page in webview in a separate component like this:
<webview
ref={a => (this.view = a)}
partition="myPartition"
src={`https://root.domain.com/${providedURL}`}
style={{
display: "flex",
width: "100%",
height: "100%"
}}
/>
I get the login page.
So I tried to following to set the cookies on the webview when it finishes loading, then reload:
this.view.addEventListener("did-finish-load", e => {
// Get partitioned session
const ses = remote.session.fromPartition("myPartition");
// Loop through cookies and add them to the webview
ses.cookies.get({}, (error, cookies) => {
cookies.forEach(cookie => {
// this.view doesn't return the webview in here so I use the event
e.target.getWebContents().session.cookies.set(cookie,(e)=> e? console.log(e):null);
});
// Reload with cookies
e.target.getWebContents().reload();
});
this.setState({ loading: false, failure: false });
});
But still I get login page every time!
The frustrating thing about this is that the auth() function defined above (in another wrapper component) shows a logged in screen every time it runs after the user logs in once so the login session is active and the cookie is stored somewhere - but where? And how can I make this webview see it? Would I be better with an Iframe ?? My requirement to show an external url inline is a must have for my stakeholders so I need some way to make it work.
I am far from proficient in cookie management and the session management is not clear to me in Electron because there are a number of ways of accessing sessions BrowserWindow.webContents().session orBrowserWindow.webContents().defaultSession or
import { session } from "electron".
Also, when I look at the application tab in devtools, these specific cookies are not showing anywhere (but others are). Why is this?

Adding mobile web app to home screen using button

I'm trying to add app to home screen via button located in "settings" page of my HTML5 mobile web app (Notice: It is working if I'm trying to add it via Chrome menu).
I've set up all of these steps:
The PWA must not already be installed
Web app must include a web app manifest.
Web app must be served over a secure HTTPS connection.
Web app has registered a service worker with a fetch event handler.
Currently I'm debugging it via Chrome Dev tools and listening to beforeinstallprompt with that code from official docs.
<script>
let deferredPrompt;
window.addEventListener('beforeinstallprompt', function(event) {
// Prevent Chrome 67 and earlier from automatically showing the prompt
e.preventDefault();
// Stash the event so it can be triggered later.
deferredPrompt = e;
});
// Installation must be done by a user gesture! Here, the button click
btnAdd.addEventListener('click', (e) => {
// hide our user interface that shows our A2HS button
btnAdd.style.display = 'none';
// Show the prompt
deferredPrompt.prompt();
// Wait for the user to respond to the prompt
deferredPrompt.userChoice
.then((choiceResult) => {
if (choiceResult.outcome === 'accepted') {
console.log('User accepted the A2HS prompt');
} else {
console.log('User dismissed the A2HS prompt');
}
deferredPrompt = null;
});
});
</script>
How to catch event on /home page, and pass it to /settings page so I could fire the event via (onClick) event?
Currently I'm using:
Angular CLI: 6.1.4
Node: 8.11.4
OS: win32 x64
Save it to global (window) object (or somewhere else with global access) and on /settings page in event handler call window.deferredPrompt.prompt()?

Categories