custom PWA version management - versioning

Is is possible to get the onload event for the pwa application in general. I meant we had implemented the a custom versioning logic in-order to keep the app version based on database field.(ie clearing the service worker cache). The issues here is the logic almost works but when ever a new version is updated in the database, then we need to clear the cache of the respective browser in-order to trigger the update. On more investigation I found that when once the pwa app is opened, it is keeping the some sort of cache image, on reopening the pwa app again won't trigger the start-up code of the app, but load app from cache.
So is it possible to get an onload sort of event for pwa ?
For testing purpose I added some alert() in the app component, but didn't fired, on reopening a pwa app
this.httpService.GetAppVersion(ver).subscribe(
res => {
if (res != null || res !== undefined) {
this.version = res.versionNumber;
ver = localStorage.getItem("appVersion");
if (ver === null || ver === undefined) {
localStorage.setItem("appVersion", "1.0");
ver = "1.0";
}
let localVersion = ver.split(".");
let incomingVersion = this.version.split(".");
let result = this.helperService.compareVersion(
localVersion,
incomingVersion
);
//alert("result : " + result);
if (result === 1) {
const snackBarRef = this.snackBar.open(
"New version available. Load New Version?",
"Yes",
{ duration: 50000000 }
);
snackBarRef.afterDismissed().subscribe(() => {
console.log("The snack-bar was dismissed");
});
snackBarRef.onAction().subscribe(() => {
localStorage.setItem("appVersion", this.version.toString());
this.helperService.Update(); // which clears the cache
setTimeout(() => {
window.location.reload(true);
}, 500);
});
}
}
},
error => {
alert("http error" + JSON.stringify(error));
}
);
at least the code in the app component's constructor will execute every time when the app is reopened after closing.

See: How to display a "new version available" for a Progressive Web App

I know this question is very old, but what I'm doing now (and I'm trying to find a better approach because I don't really like this one) is storing the version on the service worker code.
Then, when the window.onload fires, the main JavaScript code sends a message to the service worker (using postMessage()) and the service worker replies with the version number.
It's not exactly what you need, but it's an approximation.
Still, and as I said, I'm looking for a better, more maintenable approach. If I find one I'll post it here, just in case someone is searching for this (as I did).

Related

Need alternative JS Server Side Rendering, since Prerender IO recent changes to their subscription

I have been using prerenderIO for the past 3 years, but with recent changes to their subscription plan, it has become too expensive. I'm in need of a new solution as soon as possible. I use MEAN stack (MongoDB, Express, AngularJS) and don't have the time to learn server-side rendering, which is why I initially used prerenderIO.
Despite my attempts to return a static .html file, it still runs my AngularJS code and retrieves data from MongoDB.
I have tried debugging the crawl using the Facebook Debugger, but the issue remains unresolved.
app.get('/blog/best-place-in-xxx', (req, res, next) => {
if (!isCrawler(req.headers['user-agent'])) {
// Serve the pre-rendered HTML file
res.sendFile(__dirname + '/app/public/best-place-in-xxx.html');
return;
} else {
next();
}
});
function isCrawler(userAgent) {
const crawlers = [
'googlebot','bingbot','yandexbot','baidubot','facebot'
];
for (const crawler of crawlers) {
if (userAgent.toLowerCase().indexOf(crawler) !== -1) {
return true;
}
}
return false;
}
Can someone help me find a good alternative that won't negatively impact my Google ranking?"

Handling OAuth with React 18 useEffect hook running twice

Background
I have recently upgraded a fairly sizeable React app to React 18 and for the most part it has been great. One of the key changes is the new double mount in development causing useEffect hooks to all run twice, this is clearly documented in their docs.
I have read their new effect documentation https://beta.reactjs.org/learn/lifecycle-of-reactive-effects and although it is quite detailed there is a use case I believe I have found which is not very well covered.
The issue
Essentially the issue I have run into is I am implementing OAuth integration with a third-party product. The flow:
-> User clicks create integration -> Redirect to product login -> Gets redirected back to our app with authorisation code -> We hit our API to finalise the integration (HTTP POST request)
The problem comes now that the useEffect hook runs twice it means that we would hit this last POST request twice, first one would succeed and the second would fail because the integration is already setup.
This is not potentially a major issue but the user would see an error message even though the request worked and just feels like a bad pattern.
Considered solutions
Refactoring to use a button
I could potentially get the user to click a button on the redirect URL after they have logged into the third-party product. This would work and seems to be what the React guides recommend (Although different use case they suggested - https://beta.reactjs.org/learn/you-might-not-need-an-effect#sharing-logic-between-event-handlers).
The problem with this is that the user has already clicked a button to create the integration so it feels like a worse user experience.
Ignore the duplicate API call
This issue is only a problem in development however it is still a bit annoying and feels like an issue I want to explore further
Code setup
I have simplified the code for this example but hopefully this gives a rough idea of how the intended code is meant to function.
const IntegrationRedirect: React.FC = () => {
const navigate = useNavigate();
const organisationIntegrationsService = useOrganisationIntegrationsService();
// Make call on the mount of this component
useEffect(() => {
// Call the method
handleCreateIntegration();
}, []);
const handleCreateIntegration = async (): Promise<void> => {
// Setup request
const request: ICreateIntegration = {
authorisationCode: ''
};
try {
// Make service call
const setupIntegrationResponse = await organisationIntegrationsService.createIntegration(request);
// Handle error
if (setupIntegrationResponse.data.errors) {
throw 'Failed to setup integrations';
}
// Navigate away on success
routes.organisation.integrations.navigate(navigate);
}
catch (error) {
// Handle error
}
};
return ();
};
What I am after
I am after suggestions based on the React 18 changes that would handle this situation, I feel that although this is a little specific/niche it is still a viable use case. It would be good to have a clean way to handle this as OAuth integration is quite a common flow for integration between products.
You can use the useRef() together with useEffect() for a workaround
const effectRan = useRef(false)
useEffect(() => {
if (effectRan.current === false) {
// do the async data fetch here
handleCreateIntegration();
}
//cleanup function
return () => {
effectRan.current = true // this will be set to true on the initial unmount
}
}, []);
This is a workaround suggested by Dave Gray on his youtube channel https://www.youtube.com/watch?v=81faZzp18NM

Using the paypalhere sdk in react web app

I'm building a web app for inventory management. I've got React on the frontend, and Nodejs+mongodb on the backend. Our company vends at local events and most of our sales are paid with cards. To process card payments we use the Paypal Here app on our phones which connects to a card reader and we manually type in the payment amount. Since we have over 200 different products (custom art), we decided to build this application so that we can quickly search for the product(s) being purchased, add them to the "cart" where the total price plus tax will be automatically calculated, and then a total of 3 payment option buttons will be present, one for cash, one for venmo, and one for card. At first, I figured the card selection button could link externally to the Paypal Here app and the payment amount would be automatically filled in when redirected, but then I realized I could actually integrate a Paypalhere sdk in the application, which sounded better than a redirect. There's three different sdks, one for ios, one for android, and one for the web, and the one for the web is what I need. I looked for an npm package, no luck, then I tried manually inserting the script and src into the document via react helment, no luck, on componentDidMount, no luck. I'm not used to not having an npm package to use, so my question today is how can I integrate this sdk into my React app?
Heres a link to the web integration documentation: https://developer.paypal.com/docs/integration/paypal-here/sdk-dev/web/#integration
Heres an the code I used to manually insert the script onComponentDidMount, I don't know if it worked, but even if it did, I don't know how to access it...
useEffect(() => {
const script = document.createElement("script");
script.src = "https://www.paypalobjects.com/pph/websdk/js/pphwebsdk-1.1.14.min.js";
script.async = true;
document.body.appendChild(script);
return () => {
document.body.removeChild(script);
};
}, []);
Don't remove the script after adding it.
You can set a callback function to have your code that uses PPH run after the script loads. Here's an example with a callback function, it's for regular PayPal buttons rather than PPH, but you can adapt it to your needs.
function loadAsync(url, callback) {
var s = document.createElement('script');
s.setAttribute('src', url); s.onload = callback;
document.head.insertBefore(s, document.head.firstElementChild);
}
loadAsync('https://www.paypal.com/sdk/js?client-id=sb&currency=USD', function() {
paypal.Buttons({
// Set up the transaction
createOrder: function(data, actions) {
return actions.order.create({
purchase_units: [{
amount: {
value: '0.01'
}
}]
});
},
// Finalize the transaction
onApprove: function(data, actions) {
return actions.order.capture().then(function(details) {
// Show a success message to the buyer
alert('Transaction completed by ' + details.payer.name.given_name);
});
}
}).render('body');
});
Alternatively, you can just load the SDK statically from in the index <head> of your application, and it'll always be there ready for use.

window, document and local Storage in React server side rendering

In my React application, I am using window object , document object and localStorage.
To avoid errors, I have set it up like:
var jsdom = require("jsdom");
var doc = jsdom.jsdom("");
if (typeof localStorage === "undefined" || localStorage === null) {
var LocalStorage = require('node-localstorage').LocalStorage;
localStorage = new LocalStorage('./scratch');
global.localStorage = localStorage;
}
var win = doc.defaultView
console.log("document default viewwwwwwwwwwwwwwwwww", doc);
global.document = doc
global.window = win
function propagateToGlobal (window) {
for (let key in window) {
if (!window.hasOwnProperty(key)) continue
if (key in global) continue
global[key] = window[key]
}
}
propagateToGlobal(win)
But in my application, I want real window, ,real localStorage and real document to be used instead of what I have set up above.
localStorage created this directory scratch.Does that mean browser localStorage would not be used now?
Also, the console statement gives this if I try to console doc variable and is being used in place of document variable which is creating problem:
Document { location: [Getter/Setter] }
This is the script I have :
<script dangerouslySetInnerHTML={{__html:(function(w,d,s,l,i){
console.log(d.getElementsByTagName(s)[0]);
w[l]=w[l]||[];
w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});
var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';
j.async=false;
j.src= '//www.googletagmanager.com/gtm.js?id='+i+dl;
console.log("f is",f);
f.parentNode ? f.parentNode.insertBefore(j,f) : false;
})(window,document,'script','dataLayer','ID')}}/>
Here getElementByTagName returns undefined and not an element as it should. How do I fix this?
basically, JSDom and the such should only be used if you would like to fake the window and document of the browser inside NodeJS. This is valid when running tests. I've not seen node-localstorage before, but i suspect the same is true of this package also.
You certainly do not want any of those packages to run within your app when on the client (in the browser).
You haven't specified which errors you have but I can only guess you are trying to run your app in node?
I would recommend removing all of them from your app completely and seeing where you get the errors. Then tackle the errors one by one. To start with ensure you only run that code on the client by using componentDidMount or something similar.
Once the app is working on the client and on the server, you could then look at how to improve / increase the amount the is rendered on the server.

Off-line Chrome Packaged App: how to manage Prod and Dev modes

To switch between dev/stage/prod on the server, I set an ENV variable. This is pretty standard.
With an Off-line Chrome App, how do I switch between dev/stage/prod? Especially around REST API URL's?
During development my app is installed in chrome as an "unpacked" app.
SOLUTION:
I combined these answers. Here's what I did:
On install, if unpacked extension, I set a value in localStorage.
On app run, I set a variable to the localstorage value, or to production if undefined.
FWIW, here's the code:
background.js:
chrome.runtime.onInstalled.addListener(function () {
console.log('onInstalled');
// Note: this event is fired every time the "Reload" link is clicked in
// 'Chrome Apps & Extensions Developer Tool'. So only set if not set.
// If unpacked extension,
if(!chrome.runtime.getManifest().update_url) {
// Load existing value
chrome.storage.local.get('APIBaseURL', function(data) {
// Has value already been set?
if (!data.hasOwnProperty('APIBaseURL')) {
// set API server to localhost
chrome.storage.local.set({'APIBaseURL': DEV_APIBASEURL }, function() {
// Ok, notify the console.
console.log('Installed in dev mode: APIBaseURL = '+DEV_APIBASEURL);
} );
}
});
}
});
App.js (this is Angular, but you should see the pattern. Promises are ES6)
var PROD_APIBASEURL = 'https://production.com';
angular.module('wmw', ['wmw.routes'])
// Listen for online/offline events and set status in $rootScope
.run(['$rootScope', function($rootScope){
// Determine which server to run on
$rootScope.isDev = chrome.runtime.getManifest().hasOwnProperty('update_url');
// Async init code is in a Promise
$rootScope.promiseAppReady = new Promise(function(resolve, reject) {
// Get the Base URL
chrome.storage.local.get('APIBaseURL', function(data) {
// Apply it to our scope. If not set, use PROD.
$rootScope.$apply(function() {
if (data.hasOwnProperty('APIBaseURL')) {
$rootScope.APIBaseURL = data.APIBaseURL;
} else {
$rootScope.APIBaseURL = PROD_APIBASEURL;
}
resolve($rootScope.APIBaseURL);
});
});
});
}]);
$rootScope.promiseAppReady let's me know when the code is done and the app is ready.
$rootScope.$apply() bubbles changes up to other scopes. If you're not using Angular, you can remove this.
I also included this code with some debug tools:
var debugTools = {
setAPIBaseURL: function (url) {
chrome.storage.local.set({'APIBaseURL': url});
},
showAPIBaseURL: function() {
chrome.storage.local.get('APIBaseURL', function(data) {console.log(data)});
}
}
so it was easy to change in the console.
In the console chrome.runtime.getManifest().update_url will have a value if installed from the store. Undefined if not.
See How to distinguish between dev and production in a Firefox extension I'm building?
And Check if Chrome extension installed in unpacked mode
From your description, I don't think you want the Chrome App to only talk to the remote server when it's installed from the Chrome Web Store and only talk to the local server when it's installed unpacked. I would think that you'd want the option of talking to either server no matter how it's installed.
So, I'd program the app to choose its server based on a key in Local Storage. You can then easily set that key from the Developer Tools panel (the Resources) tab. If the key is undefined, it uses the remote server.

Resources