NextJS SSR in Capacitor - reactjs

we use Next.js with server-side rendering (SSR) and we'd like to wrap our app with Capacitor.js so that we can ship it to both Android and iOS devices. Sadly, this only seems to be possible when using static site generation (SSG) and I cannot find anything on how to even attempt to make SSR work.
A couple of threads seem to hint that this is possible: How to package a hosted web app with Ionic Capacitor (not sure what type of rendering is used here) and https://github.com/MicrosoftDocs/cordova-docs/blob/master/articles/getting-started/create-a-hosted-app.md (this was for Cordova). I have never used Nuxt.js before but some hints that it could be possible with that framework as well here: Using Capacitor 3 with Nuxtjs SSR
Essentially, is it possible to use Capacitor's Webview to display a hosted app instead of having to first build it statically? Could this be something that Capacitor 3 now allows?
Should this be impossible, what would be your recommendation for having a mobile app knowing that we have built our web (and mobile responsive) product in Next.js, with heavy usage of getServerSideProps (i.e. SSR). Any help would be greatly appreciated.

I am currently investigating the same.
Looking to wrap an existing responsive web application based on nextjs using SSR.
in this blog I read in the comments that you can essentially point capacitor to any existing URL (so also your already running website).
I have not tested it though yet. My biggest questions right now would be:
Could we still use native functions (such as push notifications, which is one of the main reasons of bundling it as an app) then? problably would need to integrate the required libs in my existing web app and check if we are running in capacitor environment somehow.
How does Apple handle such cases upon submissions? I have read that they do reject apps that mainly just wrap existing web sites without offering any app-like advantages.

I managed to do it in react and accomplish it in next in react I had to find injected native-bridge.js which is in npm package #capacitor/android/capacitor/src/main/java/com/getcapacitor/Bridge.java
in the function below and after coping output of the below function I had to disable bridgeJS or if you want the whole function
private JSInjector getJSInjector() {
try {
String globalJS = JSExport.getGlobalJS(context, config.isLoggingEnabled(), isDevMode());
String bridgeJS = "";
// String bridgeJS = JSExport.getBridgeJS(context);
String pluginJS = JSExport.getPluginJS(plugins.values());
String cordovaJS = JSExport.getCordovaJS(context);
String cordovaPluginsJS = JSExport.getCordovaPluginJS(context);
String cordovaPluginsFileJS = JSExport.getCordovaPluginsFileJS(context);
String localUrlJS = "window.WEBVIEW_SERVER_URL = '" + localUrl + "';";
return new JSInjector(globalJS, bridgeJS, pluginJS, cordovaJS, cordovaPluginsJS, cordovaPluginsFileJS, localUrlJS);
} catch (Exception ex) {
Logger.error("Unable to export Capacitor JS. App will not function!", ex);
}
return null;
}
in my js files then I import it which is hard but works
const script = document.createElement('script')
script.onload = rendertodom
script.onerror = rendertodom
script.src = '/native-bridge.js'
document.body.append(script)
function rendertodom () {
import('./App').then(({default:App})=> {
ReactDOM.render(
<App/>,
document.getElementById('root')
)
})
}
the struggling part in next is rendering dynamically where a plugin is needed like the app above or importing native bridge if on native before importing rest of app
I use a function for knowing its android from capacitor which I can't do before native so function is in index
if(isNativ(window)) load capcitor // sudo code from real code above
const isNative = (win) => {
let _a, _b;
if (win === null || win === void 0 ? void 0 : win.androidBridge) {
return true;
}
else return !!((_b = (_a = win === null || win === void 0 ? void 0 : win.webkit) === null || _a === void 0 ? void 0 : _a.messageHandlers) === null || _b === void 0 ? void 0 : _b.bridge);
};

Related

process is undefined in React

I am building a simple React app that generates a QR code from data. I am interested in inspecting the memory usage when the QR code is generated. I am using the built process.memoryUsage() function but the app throws and exception
Uncaught TypeError: process__WEBPACK_IMPORTED_MODULE_1__.process is undefined
I have tested some different solution, i tried to rollback the react script version to "4.0.3" i tried to download the npm polyfill webpack but there is no success.
I am currently using these imports
import React, { useState, useEffect } from 'react';
import process from 'process';
import './App.css';
const QRCode = require('qrcode');
The function looks like this
let stringData = JSON.stringify(qrData);
console.log("Number of chars in data" + " " + stringData.length);
QRCode.toDataURL(stringData, function (err, url) {
if(err) return console.log("error occured")
//window.location.href = url;
})
const used = process.memoryUsage();
for (let key in used) {
console.log(`${key} ${Math.round(used[key] / 1024 / 1024 * 100) / 100} MB`);
}
}
process is a Node.js API and is not available in the browser where your React app is running, which is why you see that error. If there is an available global process object, it is being polyfilled by something in your build tools, and will not have the memoryUsage method.
There is no equivalent API for the browser, but some related APIs do exist. Note that this is an evolving space and some are non-standard, so be sure to read both the spec and documentation before considering any usage:
Device Memory API (MDN)
Performance.memory (MDN)
You are using a built in nodejs package in a react app. Node executes on the server and has access to system level resources. React runs in the browser and does not. See this article for some tips measuring performance in React.

custom PWA version management

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).

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.

Implement File Upload like Dropbox by Appcelerator

I want to implement File Upload function like Dropbox. I use Appcelerator. This function can upload file from Acrobat, iBook, Work, Excel, Drive etc. And make in on iOS.
I have researched on Appcelerator but could not find any solution for this.
I dont know how to access to local storage on iOS or working with another app by Appcelerator.
Can you give me any suggestion about this problem
Thank you very much
You should register CFBundleDocumentTypes in your tiapp.xml ios element, which works the same as modifying the Info.plist in Xcode would for an Obj-C or Swift app. Once you have that completed, you can listen for the resume event in your app, and look at Ti.App.getArguments() to see if your app was launched by choosing "Open In" from another app. You can also look at the folder Inbox inside of Ti.Filesystem.applicationDataDirectory to see if there are any new files in there -- that's where iOS will place them when sharing them to your app.
Your code for handling the document could look like this (in your resume handler):
var cmd = Ti.App.getArguments(),
inboxFiles = Ti.Filesystem.getFile(Ti.Filesystem.applicationDataDirectory, 'Inbox').getDirectoryListing() || [];
if (inboxFiles.length > 0) {
inboxFiles = inboxFiles.sort(byLastCreated).map(toTiFile);
if (!cmd.url) {
cmd.url = inboxFiles[0].getNativePath();
}
if (inboxFiles.length > 1) {
for (var i = inboxFiles.length - 1; i >= 1; i--) {
inboxFiles[i].deleteFile();
}
}
}
if (cmd && cmd.url && cmd.url.indexOf('file://') === 0) {
// TODO: Do something interesting with cmd.url.
}

Hosting nodeJS app with firebase

So I have this web-app using angularJS and nodeJS. I don't want to just use localhost to demo my project because it doesn't looks cool at all when I type "node server.js" and then go to localhost.....
Since I intend to use Firebase for the data, I have noticed that Firebase provides hosting. I tried it, but it seems to only host the index.html and not through/using server.js. I have customized files for the server to use/update. So, how can I tell Firebase Hosting to use my server and related files when hosting?
Is it possible to tell Firebase, hey, run "node server.js" to host my index.html?
I'm guessing by the way you are wording the question you want to see this site from "the internet".
Two routes you could go here.
a) Serve your index through Firebase hosting. Firebase only hosts assets. If your Angular app is being served through Node then you will need to change your architecture to be more SPA-ish
SPA-ish would be like an index bootstrap that interacts with the backend purely through API's.
You would host the API server on something more appropriate like through Nodejitsu.
b) Serve the whole thing through something like Nodejitsu (hosting platform) or your very own VM managed by a different kind of hosting company like BuyVM.net.
Another idea, is if your nodejs app is independent of the angularjs app (however they use shared data, and perform operations on that data model) you could separate the two and connect them only via firebase.
Firebase hosting -> index.html and necessary angularjs files.
Locally (your PC) -> server.js which just connects to firebase and trigger on changed data.
I have done this for a few projects and it's a handy way to access the outside world (internet) while maintaining some semblence of security by not opening ports blindly.
I was able to do this to control a chromecast at my house while at a friends house
Here's an example from my most recent project (I'm trying to make a DVR).
https://github.com/onaclov2000/webdvr/blob/master/app.js
var FB_URL = '';
var Firebase = require('firebase');
var os = require('os')
var myRootRef = new Firebase(FB_URL);
var interfaces = os.networkInterfaces();
var addresses = [];
for (k in interfaces) {
for (k2 in interfaces[k]) {
var address = interfaces[k][k2];
if (address.family == 'IPv4' && !address.internal) {
addresses.push(address.address)
}
}
}
// Push my IP to firebase
// Perhaps a common "devices" location would be handy
var ipRef = myRootRef.push({
"type": "local",
"ip": addresses[0]
});
myRootRef.on('child_changed', function(childSnapshot, prevChildName) {
// code to handle child data changes.
var data = childSnapshot.val();
var localref = childSnapshot.ref();
if (data["commanded"] == "new") {
console.log("New Schedule Added");
var schedule = require('node-schedule');
var date = new Date(data["year"], data["month"], data["day"], data["hh"], data["mm"], 0);
console.log(date);
var j = schedule.scheduleJob(date, function(channel, program, length){
console.log("Recording Channel " + channel + " and program " + program + " for " + length + "ms");
}.bind(null, data["channel"], data["program"], data["length"]));
localref.update({"commanded" : "waiting"});
}
});
When I change my "commanded" data at the FB_URL, to "new" (which can be accomplished by angularjs VERY Simply, using an ng-click operation for example) it'll schedule a recording for a particular date and time (not all actually functional at the moment).
I might be late but since 3 years have passed there is an solution available now from Firebase in the form of cloud functions
Its not straight forward but looks promising if one can refactor their code a bit

Resources