How do I use cookies in a react electron app? - reactjs

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.

Related

Fetching a github repo in react gives a "Module "stream" has been externalized for browser compatibility and cannot be accessed in client code" error

I am currently stuck with a problem trying to fetch github repo data using the octokit npm package.
I use vite to run a dev server and when I try to make a request, the error that i get is:
Uncaught Error: Module "stream" has been externalized for browser compatibility and cannot be accessed in client code.
My React .tsx file looks like this:
import { Octokit, App } from 'octokit'
import React from 'react'
const key = import.meta.env.GITHUB_KEY
const octokit = new Octokit({
auth: key
})
await octokit.request('GET /repos/{owner}/{repo}', {
owner: 'OWNER',
repo: 'REPO'
})
export default function Repos() {
return (
<>
</>
)
}
I have redacted the information for privacy purposes.
If anyone knows how to resolve this issue with vite, please let me know!
Check first if this is similar to octokit/octokit.js issue 2126
I worked around this problem by aliasing node-fetch to isomorphic-fetch. No idea if it works for all usages within octokit, but works fine for my project.
You'll need to install the isomorphic-fetch dependency before making this config change.
// svelte.config.js
const config = { // ... kit: {
// ...
vite: {
resolve: {
alias: {
'node-fetch': 'isomorphic-fetch',
},
},
},
},
};
export default config;
Note: there are still questions about the support/development of octokit: issue 620.

Hash routing in production throwing file not found error

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
}

Can't connect to NeDB on render process Electron

I have problem with connect NeDB to my react-electron app. Now I install NeDB on my project and connect him to electron.js file.
const Datastore = require('nedb');
let db = {};
db.students = new Datastore({
filename:'./students.json',
autoload: true
})
db.students.insert({name : "Putin V.V.", year: 1952});
Now I need connect this db to my app.js file.
How I can manipulate with this file on render part?
GitHub code
You can achive your idea by using ipc at Electron.
I posted an answer before. Please check the below.
how to communicate between react and electron
But here is the pre-requirements.
You should enable the nodeintegration when you are creating the BrowserWindow
So at your code, it should be like this
mainWindow = new BrowserWindow({
width: 1280,
height: 720,
minWidth: 900,
minHeight: 600,
show: false,
icon: "",
webPreferences: {
nodeIntegration: true
}
});
After this, you can use this ipcRenderer at renderer(your react app).
If you don't set this option. Then you will face the similar issue as below
ipcRenderer not receiving message from main process

React application with external plugins

I'm building a React application bundled using Parcel or Webpack.
The application should be able to embed external React components
developed by third-parties and hosted elsewhere as modern javascript modules:
// https://example.com/scripts/hello-plugin.js
import React from 'react';
export default class HelloPlugin extends React.Component {
render() {
return "Hello from external plugin!";
}
}
Host application loads these components using asynchronous import like this, for example:
// createAsyncComponent.tsx
import * as React from 'react';
import { asyncComponent } from 'react-async-component';
export default function createAsyncComponent(url: string) {
return asyncComponent({
resolve: () => import(url).then(component => component.default),
LoadingComponent: () => <div>Loading {url}....</div>,
ErrorComponent: ({ error }) => <div>Couldn't load {url}: {error.message}</div>,
})
}
But looks like bundlers don't allow importing arbitrary urls as external javascript modules.
Webpack emits build warnings: "the request of a dependency is an expression" and the import doesn't work. Parcel doesn't report any errors, but fails when import(url) occurs at runtime.
Webpack author recommends using scriptjs or little-loader for loading external scripts.
There is a working sample that loads an UMD component from arbitrary URL like this:
public componentDidMount() {
// expose dependencies as globals
window["React"] = React;
window["PropTypes"] = PropTypes;
// async load of remote UMD component
$script(this.props.url, () => {
const target = window[this.props.name];
if (target) {
this.setState({
Component: target,
error: null,
})
} else {
this.setState({
Component: null,
error: `Cannot load component at ${this.props.url}`,
})
}
});
}
Also, I saw a similar question answered a year ago where the suggested approach also involves passing variables via a window object.
But I'd like to avoid using globals given that most modern browsers support modules out of the box.
I'm wondering if it's possible. Perhaps, any way to instruct the bundler that my import(url) is not a request for the code-split chunk of a host application, but a request for loading an external Javascript module.
In the context of Webpack, you could do something like this:
import(/* webpackIgnore: true */'https://any.url/file.js')
.then((response) => {
response.main({ /* stuff from app plugins need... */ });
});
Then your plugin file would have something like...
const main = (args) => console.log('The plugin was started.');
export { main };
export default main;
Notice you can send stuff from your app's runtime to the plugin at the initialization (i.e. when invoking main at the plugin) of the plugins so you don't end up depending on global variables.
You get caching for free as Webpack remembers (caches) that the given URL has already loaded so subsequent calls to import that URL will resolve immediately.
Note: this seems to work in Chrome, Safari & firefox but not Edge. I never bothered testing in IE or other browsers.
I've tried doing this same sort of load with UMD format on the plugin side and that doesn't seem to work with the way Webpack loads stuff. In fact it's interesting that variables declared as globals, don't end up in the window object of your runtime. You'd have to explicitly do window.aGlobalValue = ... to get something on the global scope.
Obviously you could also use requirejs - or similar - in your app and then just have your plugins follow that API.
Listen to the Webpack author. You can't do (yet) what you're trying to do with Webpack.
You will have to follow his suggested route.

How to write Autodesk.Viewing.Extensions with webpack and react?

I was write code within view3D v2.11, React, ES6 and webpack. But I don't know how to write Autodesk.Viewing.Extensions within webpack and React. Can anyone show me some examples?
Using webpack to write a viewer extension is no different than using webpack to write any other js application. Take a look at my extensions library repo, each extension is bundled into a separate .js or .min.js whether you build the project in dev or prod: library-javascript-viewer-extensions.
This is designed this way so each extension can be loaded dynamically independently, however if you build an entire application around the viewer, you may simply include the code for each extension along with the rest of the app and generate a single webpack bundle.
This React project contains multiple viewer extensions (some are extracted from the library mentioned above) and is bundling extensions code along with the rest of the app: forge-rcdb.
As far as React and the viewer are concerned, it's not very relevant because the viewer is a 3D component which is created dynamically, all WebGL canvas and viewer 2D elements are being generated after your app loads a model, whereas React lets you declare 2D components when the app is starting. I was playing a bit with injecting dynamically React components to the viewer div, you can take a look in that same project here:
this.viewer = new Autodesk.Viewing.Private.GuiViewer3D(this.viewerContainer)
// Experimental !
// This this to render dynamic components
// inserted after the viewer div has been created
const reactViewerNode = document.createElement('div')
this.viewer.container.appendChild(reactViewerNode)
this.viewer.react = {
node: reactViewerNode,
components: [],
addComponent: (component) => {
this.viewer.react.components.push(component)
},
render: (props) => {
ReactDOM.render(
<div>
{
React.Children.map(
this.viewer.react.components,
(child) => React.cloneElement(child, props))
}
</div>,
reactViewerNode)
}
}
I then render those dynamic components by overriding componentDidUpdate:
componentDidUpdate () {
if (this.viewer && this.viewer.react) {
if(this.viewer.react.components.length) {
this.viewer.react.render(this.props)
}
}
}
and there is an example of use:
viewer.react.addComponent(
<DBDropdown key="test" className="react-div">
</DBDropdown>
)
I actually haven't implemented any feature using that in the live version of the app, but it should give you an idea.
Hope that helps.

Resources