Hash routing in production throwing file not found error - reactjs

I am using the electron-react-boilerplate and want to open a component in a new BrowserWindow. There are multiple questions and answers on how to do this, but none of them are working after packaging the app.
Questions / Answers I've Found:
How to handle multiple windows in Electron application with React.JS?
electron-react-boilerplate :sub window on clicking a button
https://stackoverflow.com/a/47926513/3822043
In my component I have tried to use the following lines to open a new window to a different route.
wind.loadURL(`file://${__dirname}/app.html#/video`)
wind.loadURL(`file://${Path.join(__dirname, '../build/app.html#/video')}`)
wind.loadURL(`file://${Path.join(__dirname, '../build/index.html#/video')}`)
The first one works when running yarn dev, but not in production. They all throw ERR_FILE_NOT_FOUND for the respective paths.
This is how my routes are set up:
export default function Routes() {
return (
<App>
<HashRouter>
<Route exact path={routes.HOME} component={HomePage} />
<Route path={routes.VIDEO} component={VideoPage} />
</HashRouter>
</App>
);
}
Is there an easy way to allow routing when opening a new BrowserWindow using React's router?

Great timing. I was in the same boat the past few days, just figured it out. Except, I didn't change to HashRouter like you did. Rather I left all the routing stuff as the default electron-react-boilerplate comes with, like ConnectedRouter. Maybe either way works.
https://github.com/electron-react-boilerplate/electron-react-boilerplate/issues/1853#issuecomment-674569009
__dirname only works in dev. I used DebugTron to check what URLs were being loaded for each resource, and it was file://path/to/app.asar/something. Then I figured out how to get the path to the asar. This worked for me in both dev and prod, regardless of where the application is located. You also need to set nodeIntegration: true. Tested on macOS.
const electron = require("electron")
//...
win.loadURL(`file://${electron.remote.app.getAppPath()}/app.html#/yourroutename`)
Fuller example in case anyone is also wondering how to load another page and pass arguments to it:
import routes from '../constants/routes.json'
const electron = require("electron")
// ...
var win = new BrowserWindow({
width: 400, height: 400,
webPreferences: { nodeIntegration: true }
})
win.loadURL(`file://${electron.remote.app.getAppPath()}/app.html#${routes["ROOM"]}?invitationCode=${encodeURIComponent(code)}`)
win.show()
and in the component the route loads:
const queryString = require('query-string')
// ...
constructor(props) {
super(props)
const params = queryString.parse(location.hash.split("?")[1])
this.invitationCode = params.invitationCode
}

Related

Cannot use gatsby-plugin-intl for single page site

I'm using Gatsby and I want build a single page site, so without create pages. For achieve this I edited gatsby-node.js with the following code:
exports.onCreatePage = async ({ page, actions }) => {
const { createPage } = actions
if (page.path === "/") {
page.matchPath = "/*"
createPage(page)
}
}
in that case, each request is re-routed to the index.js page, which is the only one.
Then, in the index.js page I have:
const IndexPage = () => {
const intl = useIntl()
const locale = intl.locale
return (
<BGTState>
<BlogState>
<Layout>
<Router>
<Home path={`${locale}/`} />
<Section path={`${locale}/:sectionSlug`} />
<Collection path={`${locale}/:sectionSlug/:collectionSlug`} />
<Season
path={`${locale}/:categorySlug/:collectionSlug/:seasonSlug`}
/>
<Product
path={`${locale}/:categorySlug/:collectionSlug/:seasonSlug/:prodSlug`}
/>
<Blog path={`${locale}/blog`} />
<Article path={`${locale}/blog/:articleSlug`} />
<NotFound default />
</Router>
</Layout>
</BlogState>
</BGTState>
)
}
as you can see, I have different routers that load a specific component based on the url.
I have prefixed each path with the current locale to match the correct path.
This mechanism is working fine for the home page only, but for the other links doesn't work. Infact, if I visit something like:
http://localhost:3001/en/category-home/prod-foo
which must load the Collection component, the site simply redirect to:
http://localhost:3001/en
and display the Home component again.
What I did wrong?
UPDATE
Page Structure:
As you can see I have just the index.js which handle all requests as I configured in the gatby-node.js.
If I remove the localization plugin, at least using this configuration:
{
resolve: `gatsby-plugin-intl`,
options: {
// Directory with the strings JSON
path: `${__dirname}/src/languages`,
// Supported languages
languages: ["it", "en", "ci", "fr"],
// Default site language
defaultLanguage: `it`,
// Redirects to `it` in the route `/`
//redirect: true,
// Redirect SEO component
redirectComponent: require.resolve(
`${__dirname}/src/components/redirect.js`
),
},
},
and I don't prefix the url with intl.locale, everything is working fine. But adding redirect: true in the plugin configuration, and prefixing the link with the locale, the site redirect me to the home component.
If you are creating a SPA (Single Page Application, notice the single) you won't have any created pages but index. You are trying yo access to a /category page that's not created because of:
if (page.path === "/") {
page.matchPath = "/*"
createPage(page)
}
That's why your routes don't work (or in other words, only the home page works).
Adapt the previous condition to your needs to allow creating more pages based on your requirements.
I'm using Gatsby and I want build a single page site, so without
create pages. For achieve this I edited gatsby-node.js with the
following code:
It's a non-sense trying to build a SPA application with Gatsby (without creating pages) but then complaining because there's not collection page created.
Make sure that you understand what you are doing, it seems clearly that you need to create dynamically pages for each collection, season, and product so your approach to create SPA won't work for your use-case.
It's possible to keep just index.js without overcomplicating thing? I
just want to understand why my code isn't working 'cause I've passed
the correct url... Removing the localization Gatsby works, so I
suspect there is a localization problem
The only way that http://localhost:3001/category-home/prod-foo (removing the localization) could be resolved is by creating a folder structure such /pages/category-home/prod-foo.js (since Gatsby extrapolates the folder structure as URLs), so, if you want to use localization using your approach, add a structure such en/pages/category-home/prod-foo.js and es/pages/category-home/prod-foo.js (or the whatever locale), and so on. In my opinion, this is overcomplexitying stuff since, for every category, you'll need to create 2 (even more depending on the locales) files.
Gatsby allows you to create dynamic pages and interpolate the locale automatically using built-in plugins on the process, creating each file for the specifically defined locales.

How do I use cookies in a react electron app?

I'm trying to use cookies in my electron react app.
At the moment I am completely unable to get it to work.
Requiring electron or importing it in a react component throws "TypeError: fs.existsSync is not a function".
Example code that breaks (in App.js for example):
import { WebContent } from 'electron';
or
const { session } = require('electron');
What I got to work was the following code in the Main.js file:
const { app, BrowserWindow, session } = require('electron');
session.defaultSession.cookies.get({}).then(cookies => {
console.log(cookies)
});
I followed this tutorial Using Electron with React: The Basics to set up my electron react app.
I also followed this answer to a similar question but when i do this:
const { session } = window.require('electron');
console.log(session.defaultSession.cookies);
It says that session is undefined.
I guess, you want to use cookies within the renderer process (the js files used for rendering the UI). Since the cookies are normally only accessible within the main process and 'remote' access is turned off by default since version 10 of electron, this has to be turned on again by setting enableRemoteModule: true in main.js (main process) within the creation of the browser window. For me this looks like that
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true,
}
});
(Also, nodeIntegration: true is necessary in my case but maybe not in your case to prevent errors of process being undefined.)
Setting that, you can access the session object just by doing
import electron from 'electron';
const {remote} = electron;
const {session} = remote;
console.log(session.defaultSession.cookies);
in your renderer code. Also, you can access all the others objects usually only accessible in the main process via electron.remote.
Cheers.

Images won't load after build when using React with Electron

I'm developing an app using React and Electron.
I'm storing the images in src/assets/images. When I run the app in development using react-scripts start and electron . everything works fine.
The problem occurs when the react app is built using react-scripts build. When the first view is loaded the path is correctly resolved, for example file:///D:/Projects/app-name/build/static/media/logo.e99ed458.png and the image is displayed.
Now, when the route changes, the image no longer works. In the network tab in devTools the request URL is file:///D:/main/static/media/logo.e99ed458.png which is obviously incorrect.
This is my component code:
import React from "react";
import Logo from '../../assets/images/ad.png';
const Logo = () => {
return (
<React.Fragment>
<img src={Logo} alt="no image" />
</React.Fragment >
);
};
export default Logo;
And in electron.js
mainWindow.loadURL(url.format({
protocol: 'file',
slashes: true,
pathname: require('path').join(__dirname, '../build/index.html')
}));
I've been trying to solve this problem for two days now. Does anyone know a solution?
EDIT:
I have also tried using PUBLIC_URL according to https://create-react-app.dev/docs/using-the-public-folder/
But the result is the same, the path is resolved correctly when the first view is displayed and after that it resolves to file:///D:/assets/images/logo.png.
When I log process.env.PUBLIC_URL it says that PUBLIC_URL is equal to ".".
I solved this issue by changing the routers instead of using BrowserRouter you should use HashRouter
This article https://www.freecodecamp.org/news/building-an-electron-application-with-create-react-app-97945861647c/ has a good example but Does not have the configurations of routers

What type of navigation in React works well with login authentication?

I wonder what type of navigation works well with login authentication? Right now i use conditional rendering for certain pages or components to display and through
if (this.state.loggedIn) {
return <UI loggedIn={this.state.loggedIn} showUser=
{this.state.showUser} logout={this.logout.bind(this)} />;
};
i can render something after the validation. What would it look like if i wanted to render a couple of more different pages? Should i put a state on each page that will change on onclicks and cause the app to render desired page?
Thank you lads
This is an issue which nearly every modern application must tackle. Because of this, many libraries have already solved these issues for you. Take this code for example which uses react-router:
In my example I am showing you what the routes would look like in a routes.js file and then a separate file for what the acl would look like. The acl is a function which is passed into the onEnter of each route you want to protect. You can call it anything you like.
routes.js
import React from 'react';
import { hashHistory, Router, Route, IndexRoute } from 'react-router';
import { acl } from './util/acl-util';
import AppContainer from './containers/app-container';
import DashboardPage from './pages/dashboard-page';
export default class Routes extends React.Component {
render() {
return (
<Router history={hashHistory}>
<Route path="/" component={AppContainer}>
{/* NON-AUTH ROUTES */}
<Route
path="login"
components={{
main: LoginPage,
aside: null,
header: this.getHeader,
}}
/>
{/* AUTH-REQUIRED ROUTES */}
<Route
onEnter={acl}
path="dashboard"
components={{ main: DashboardPage }}
/>
</Router>
);
}
}
acl-util.js
import { hasAuth } from './auth-util';
export function acl(nextState, replace) {
const { pathname, search } = nextState.location;
if (!hasAuth(theState)) {
window.alert(
'Please Log in!'
);
replace(`/login?loginRedirect=${encodeURIComponent(pathname + search)}`);
}
}
I threw this example together from cutting out part of my code that won't apply directly to this concept - and therefore this code won't run as is. You'll need to define your pages, and set up a store etc.
You'd need to define a hasAuth function which can look into your state and determine whether a user is authenticated. For my hasAuth function I am looking for a jwt token and parsing the date, if the date is still in the future, I know they are still authed and any subsequent rest api calls will work.
I know you weren't asking for a certain library, but I recommend this because the app I took this code from has dozens of routes and the acl function also implements a role matrix which looks at what a user can and cannot do based on their jwt token. Our application is pretty massive and this approach keeps it organized.
Without having something like react-router, you're right, you'd need to manually manage which page is showing and manually check for auth state in each component or make a parent component to do it. In my example the "parent component to manage it" is react-router and my onEnter method called acl. In traditional applications acl stands for access control list - you can expand the code in whichever way you like.
edit:
as someone mentioned in a comment about . Your frontend application is only as secure as the backend service it is grabbing data from or posting data to. In my example, the react code attempts to mirror the auth state in the jwt token. But, at the end of the day, your real security will only be provided by your back end services. Just because the frontend thinks a user can be logged in, shouldn't mean the backend should assume they are - you need backend authentication since all frontend application state can be modified by technical users.

Why does webpack include a module in my first entry as well as my page bundle

It seems like I'm having some sort of configuration issue with webpack and my react-router app. I'm trying to break up the bundle by pages, but I'm using react-router and I have a single "main" which does a router.run to route the app paths. It looks like when webpack runs that main file it will recursively write all the modules from main to the resulting bundle.
Here's a clonable repo which demonstrates the behavior
git clone https://github.com/adjavaherian/webpack-react-router
cd webpack-react-router
npm install
webpack
This will create a dist directory and you can check that the module is included twice using grep
$ grep -R zzz dist
dist/main.bundle.js: 'zzzzzz'
dist/mobileAppsPage.bundle.js: 'zzzzzz'
I'm expecting that because I'm using multiple webpack entries, only the mobileAppsPage.bundle.js should have the "zzzzzz" because that's the only place where the React TestClass.jsx is included.
Any ideas as to what I'm doing wrong?
https://github.com/adjavaherian/webpack-react-router
I haven't dug deep into Webpacks bundle splitting, but both your main.js and your MobileAppsPage.jsx has TestClass.jsx in it's direct dependency graph.
Since main.js requires AppRoutes.jsx which requires MobileAppsPage.jsx, both main.js and MobileAppsPage.jsx depend on TestClass.jsx. So there's not much to win in splitting those two.
What you probably want to do is to not require your route components at startup, but instead wait for the route to get hit and then load that bundle asynchronously using require.ensure (http://webpack.github.io/docs/code-splitting.html).
Something like this (haven't tested it):
var MobileAppsPage = null;
var MobileAppsPageLoader = React.createClass({
componentDidMount() {
require.ensure(['./pages/MobileAppsPage'], () => {
MobileAppsPage = require('./pages/MobileAppsPage');
this.forceUpdate();
});
},
render() {
if (MobileAppsPage) {
return <MobileAppsPage {...this.props} />;
} else {
return <div className="spinner" />;
}
}
});
var AppRoutes = (
<Route path='/' handler={AppTemplate}>
<Route name="FrontPage" path='/' handler={FrontPage}/>
<Route name="MobileAppsPage" path="mobile-apps" handler={MobileAppsPageLoader}/>
</Route>
);
You might also want to look at the example on code splitting in Webpack to split out modules from node_modules into a separate bundle (http://webpack.github.io/docs/code-splitting.html#split-app-and-vendor-code), since all your bundles will probably depend on React.

Resources