How to create BrowserWindow with data, and get that data in vue app? - vue.js

I have a Electron-Vue project, and in this project i want to pass data to window when I want to show or create it, then get that data and pass to VUE application, to specify state for application.
And the data which i want to pass is a string to specify application state something like :
download or main or etc
then with above strings I'll set state in my application to render layouts base on specific state on creating window. basically i want to pass main for win and download for downloadWin
Here's background.js which i want to pass my data in new BrowserWindow(), i don't want to use ipcRendeder or ipcMain.
let win
let downloadWin
function createDownloadWindow() {
// Create the browser window.
downloadWin = new BrowserWindow({ // if it's win i wanna pass main if it's downloadWin i wanna pass download
title: 'Manage Downloads',
width: 1200,
height: 700,
minWidth: 1200,
minHeight: 700,
frame: false,
titleBarStyle: 'hiddenInset',
webPreferences: {
webSecurity: false,
devTools: true,
nodeIntegration: false,
nodeIntegrationInWorker: false,
contextIsolation: true, // protect against prototype pollution
enableRemoteModule: false, // turn off remote
preload: path.join(__dirname, "preload.js"), /* eng-disable PRELOAD_JS_CHECK */
}
})
}
Now i want to get above string from creating window in main.js to pass received data from mainProcess to vue app.
new Vue({
router,
store,
vuetify,
mounted() {
this.$store.dispatch('window/setWindowStat', '//SET STRING HERE')
},
render: h => h(App)
}).$mount('#app')

You can pass any data you want via IPC:
in the Main process: https://www.electronjs.org/docs/api/ipc-main
in the Renderer process: https://www.electronjs.org/docs/api/ipc-renderer
Here is an example:
// In main process.
const { ipcMain } = require('electron')
ipcMain.on('hey', (event, arg) => {
console.log('hey from win', arg) // prints "{a: 2}" in main process console
})
// send message to your window when it ready (win is your window)
win.webContents.send('hi', {data: 'is here'})
import { ipcRenderer } from 'electron'
ipcRenderer.on('hi', (e, payload) => {
console.log('hi from main', payload) // prints: {data: 'is here'} in dev tools
})
ipcRenderer.send('hey', 'ping', {a: 2})

Related

Add express server and dockerize app built with vue-electron-builder

I built an electron vue app, When started the project I used Vue CLI Plugin Electron Builder
I`ve been asked to add an express REST server for all the main business logic functionality.
The express app should be stand-alone app running on docker container.
What I did until now is add the basic express server just for POC, I changed my backgraound.ts like this:
Before:
async function createWindow() {
// Create the browser window.
win = new BrowserWindow({
width: 1200,
height: 800,
useContentSize: true,
frame: false,
webPreferences: {
// Use pluginOptions.nodeIntegration, leave this alone
// See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info
nodeIntegration: process.env
.ELECTRON_NODE_INTEGRATION as unknown as boolean,
contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION,
preload: path.join(__dirname, "preload.js"),
enableRemoteModule: true,
},
});
win.maximize();
if (process.env.WEBPACK_DEV_SERVER_URL) {
// Load the url of the dev server if in development mode
await win.loadURL(process.env.WEBPACK_DEV_SERVER_URL as string);
if (!process.env.IS_TEST) win.webContents.openDevTools();
} else {
createProtocol("app");
// Load the index.html when not in development
win.loadURL("app://./index.html");
}
}
After:
...
import "./express-app/index.ts";
async function createWindow() {
// Create the browser window.
win = new BrowserWindow({
width: 1200,
height: 800,
useContentSize: true,
frame: false,
webPreferences: {
// Use pluginOptions.nodeIntegration, leave this alone
// See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info
nodeIntegration: process.env
.ELECTRON_NODE_INTEGRATION as unknown as boolean,
contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION,
preload: path.join(__dirname, "preload.js"),
enableRemoteModule: true,
enableBlinkFeatures: "Serial",
},
});
win.maximize();
win.hide();
win.loadURL("http://localhost:3000/");
// if (process.env.WEBPACK_DEV_SERVER_URL) {
// // Load the url of the dev server if in development mode
// await win.loadURL(process.env.WEBPACK_DEV_SERVER_URL as string);
// if (!process.env.IS_TEST) win.webContents.openDevTools();
// } else {
// createProtocol("app");
// // Load the index.html when not in development
// win.loadURL("app://./index.html");
// }
}
My express index.ts
import express from "express";
const app = express();
app.get("/", () => {
console.log("Get Request");
});
app.listen(3000, () => {
// tslint:disable-next-line:no-console
console.log(`server started at http://localhost:${3000}`);
});
So I just changed the part of win.loadURL() in the createWindow() boilerplate of the cli-plugin
I wanted to know how to add a dockerfile suitable for my scenario

VueJS getting "undefined" data from ipcRenderer (ElectronJS)

When trying to get a message from ipcMain to ipcRenderer (without node integration and with contextIsolation), it's received but as undefined. Not only that, but if I were to reload the VueComponent (regardless of what change I make to it), the number of responses gets doubled.
For example, the first time I start my application, I get 1x undefined at a time every time I click the button. If I reload the component, I start getting 2x undefined every time I click the button. I reload again and get 4x undefined every time I click the button... and it keeps doubling. If I restart the application, it goes back to 1x.
SETUP
ElectronJS + VueJS + VuetifyJS has been set up as described here.
preload.js as per the official documentation.
import { contextBridge, ipcRenderer } from 'electron'
window.ipcRenderer = ipcRenderer
// Expose protected methods that allow the renderer process to use
// the ipcRenderer without exposing the entire object
contextBridge.exposeInMainWorld('ipcRenderer', {
send: (channel, data) => {
// whitelist channels
let validChannels = ['toMain']
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, data)
}
},
receive: (channel, func) => {
let validChannels = ['fromMain']
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`
ipcRenderer.on(channel, (event, ...args) => func(...args))
}
}
})
background.js (main process) as per the official documentation for the preload.js file. The omitted code via ... is the default project code generated upon creation.
...
const path = require('path')
const { ipcMain } = require('electron')
async function createWindow() {
// Create the browser window.
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
// Use pluginOptions.nodeIntegration, leave this alone
// See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info
nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION,
contextIsolation: true,
preload: path.join(__dirname, 'preload.js'),
},
icon: 'src/assets/icon.png',
})
ipcMain.on('toMain', (event, data) => {
console.log(data)
event.sender.send('fromMain', 'Hello IPC Renderer')
// The two lines below return 'undefined' as well in the 'ipcRenderer'
//win.webContents.send('fromMain', "Hello IPC Renderer")
//event.reply('fromMain', 'Hello IPC Renderer')
})
...
}
...
vue.config.js file:
module.exports = {
...
pluginOptions: {
electronBuilder: {
preload: 'src/preload.js',
}
}
}
main.js (renderer process) contains only the default project code generated upon creation.
VueComponent.vue
<template>
<div id="vue-component">
<v-btn #click="sendMessageToIPCMain()">
</div>
</template>
<script>
export default {
name: "VueComponent",
components: {
//
},
data: () => ({
myData: null,
}),
methods: {
// This works. I get 'Hello IPC Main' in the CMD console.
sendMessageToIPCMain() {
var message = "Hello IPC Main"
window.ipcRenderer.send("toMain", message);
}
},
mounted() {
window.ipcRenderer.receive('fromMain', (event, data) => {
// this.myData = data // 'myData' is not defined error
this.$refs.myData = data;
console.log('myData variable: ' + this.$refs.myData) // undefined
console.log(data) // undefined
})
},
}
</script>
The VueComponent.vue's mounted() has been set up as described here, though If I try to send the data to a variable using this.myData = data, I get an error saying that myData has not been defined - using this.$refs.myData works, though it's still undefined.
P.S. myData has not been defined error =/= undefined. The former is a proper error in red letters while the latter is as seen in the image above.
For solving the first problem (doubling of function calls) you have to remove window.ipcRenderer = ipcRenderer. In contextIsolation mode the approach is to use contextBridge.exposeInMainWorld() only. Using both implementation definitely causes issues.
For the second problem, the callback to receive in ipcRenderer is called with only ...args from main (no event passed to func). see:
ipcRenderer.on(channel, (event, ...args) => func(...args)) <-- func() is called with only args
The only thing you should change is your function in mounted, to accept only data:
window.ipcRenderer.receive('fromMain', (data) => {
console.log(data) // should log you data
})

Receive WebSockets data from vuex and Vue-native-websocket plugin

I am currently using the Quasar V1 framework which includes Vue and Vuex.
Today I was looking at this plugin:
https://www.npmjs.com/package/vue-native-websocket/v/2.0.6
I am unsure on how to setup this plugin and make it work and would require a little bit of help to make sure I am doing this right as it will be the first time I use WebSockets with Vue.
I have first installed vue-native-websocket via npm and created a boot file called src\boot\websocket.js
via this command:
npm install vue-native-websocket --save
websocket.js
import VueNativeSock from 'vue-native-websocket';
export default async ({ Vue }) => {
Vue.use(VueNativeSock, 'wss://echo.websocket.org', {
reconnection: true,
reconnectionAttempts: 5,
reconnectionDelay: 3000
});
};
In Quasar v1, I have then created a module called "websockets" in:
src\store\websockets
This module has:
actions.js
getters.js
index.js
mutations.js
state.js
I need to use the websocket with format: 'json' enabled
My question is:
Let's say I have a page where I would like my websocket connection to be created and receive the live data, shall I do this?
Code for the module:
websockets/mutations.js:
export function SOCKET_ONOPEN (state, event) {
let vm = this;
vm.prototype.$socket = event.currentTarget;
state.socket.isConnected = true;
}
export function SOCKET_ONCLOSE (state, event) {
state.socket.isConnected = false;
}
export function SOCKET_ONERROR (state, event) {
console.error(state, event);
}
// default handler called for all methods
export function SOCKET_ONMESSAGE (state, message) {
state.socket.message = message;
}
// mutations for reconnect methods
export function SOCKET_RECONNECT (state, count) {
console.info(state, count);
}
export function SOCKET_RECONNECT_ERROR (state) {
state.socket.reconnectError = true;
}
Code for the module:
websockets/state.js
export default {
socket: {
isConnected: false,
message: '',
reconnectError: false
}
};
But the issue now is in my vue page.
Let's say I would like to show only the data from the websocket that has a specific event, how do I call this from the vue page itself please? I am very confused on this part of the plugin.
What is very important for me to understand if how to separate the receive and send data.
ie: I may want to receive the list of many users
or I may want to receive a list of all the news
or I may add a new user to the database.
I keep hearing about channels and events and subscriptions......
From what I understand, you have to first subscribe to a channel(ie: wss://mywebsite.com/news), then listen for events, in this case I believe the events is simply the data flow from this channel).
If I am correct with the above, how to subscribe to a channel and listen for events with this plugin please, any idea?
If you had a very quick example, it would be great, thank you.
I have developed a chat application using Vue-native-websocket plugin. Here i am showing how you can register the pulgin in the vuex store and how to call it from your vue component.
Step 1: Define these methods in your index.js file
const connectWS = () => {
vm.$connect()
}
const disconnectWS = () => {
vm.$disconnect()
}
const sendMessageWS = (data) => {
if (!Vue.prototype.$socket) {
return
}
Vue.prototype.$socket.send(JSON.stringify(data))
}
Step 2: Write the socket state and mutations
SOCKET_ONOPEN (state, event) {
if (!state.socket.isConnected) {
Vue.prototype.$socket = event.currentTarget
state.socket.isConnected = true
let phone = state.config.selectedChatTicket.phone
sendMessageWS({type: WSMessageTypes.HANDSHAKE, data: {id: window.ACCOUNT_INFO.accId, phone: phone, agentId: USER_NAME}})
}
},
SOCKET_ONCLOSE (state, event) {
console.log('SOCKET_ONCLOSE', state, event)
state.socket.isConnected = false
Vue.prototype.$socket = null
},
// NOTE: Here you are getting the message from the socket connection
SOCKET_ONMESSAGE (state, message) {
state.data.chatCollection = updateChatCollection(state.data.chatCollection,message)
},
STEP 3 : Write Action, you can call it from your vue component
NOTE:: socket actions to connect and disconnect
WSConnect ({commit, state}) {
connectWS()
},
WSDisconnect ({commit, state}) {
disconnectWS()
},
STEP 4: Register the plugin in the end as it requires the store object
Vue.use(VueNativeSock, `${window.location.protocol === 'https:' ? 'wss' : 'ws'}://www.example.com/socketserver`,
{ store: store, format: 'json', connectManually: true })
STEP 5: call your action from your vue component
buttonClick (rowData) {
const tickCount = this.ticketClickCounter
if (tickCount === 0) {
this.$store.dispatch('WSConnect')
} else {
this.$store.dispatch('WSDisconnect')
setTimeout(() => {
this.$store.dispatch('WSConnect')
}, 1000)
}
this.ticketClickCounter = tickCount + 1
},
Now you are connected to the socket
STEP 6: write a action method in your vuex file
sendChatMessageAction ({commit, state}, data) {
// NOTE: Here, you are sending the message through the socket connection
sendMessageWS({
type: WSMessageTypes.MESSAGE,
data: {
param1: abc,
param2: xyz,
param3: 123,
param4: $$$
}
})
},
STEP 7: you can define a input text box and on-enter evenlisterner you can call the action method
onEnter (event) {
if (event.target.value !== '') {
let newValue = {
param1: Date.now(),
param2: xyz,
param3: 123,
}
this.$store.dispatch('sendChatMessageAction', newValue) // Action
}
},

How to find/replace specific object in Vuex?

I have simple image upload via action and mutation. The main thing is that I firstly want to store preloader and then, when the image is uploaded to the server show the image and remove preloader.
export default new Vuex.Store({
state: {
images: [],
},
mutations: {
addImage(state, image) {
state.images.push(image);
}
},
actions: {
saveImage({commit}, image) {
commit('addImage', {url: 'http://via.placeholder.com/100x100?text=loading...'});
let form_data = new FormData();
form_data.append('file', image);
return new Promise((resolve, reject) => {
jQuery.ajax({
url : '/api/v1/products/files/save',
data : form_data,
type : 'POST',
cache: false,
contentType: false,
processData: false,
success: response => {
// commit('addImage', response.file);
// here I somehow need to replace/readd the response.file
resolve(response.file)
}
}).fail(() => reject())
})
}
},
strict: debug
})
I basically could use array.find method to look for url:http://via.placeholder.com/100x100?text=loading... remove it and then add new one but not sure if it's is the right method...
You give vuex action too much to do. Let it do one single task - upload the file. The placeholder/preloader should be handled by the component even without a vuex state. All the component needs to know is whether the image is uploading or not in order to show the loading indicator. Since your action returns a Promise your component will know when it's ready.

get Geo Coordinate in vue js

I want client current geolocation, for accuracy, i put 10000 timeout
var options = {
timeout: 10000
};
This is js function
function getCoordinates() {
return new Promise(function(resolve, reject) {
if (
!("geolocation" in navigator) ||
!("getCurrentPosition" in navigator.geolocation)
) {
return Promise.reject(new Error("geolocation API not available"));
}
var options = {
timeout: 10000
};
// browser prompts for permission
navigator.geolocation.getCurrentPosition(
getPositionCallBack,
reject,
options
);
function getPositionCallBack(position) {
var coords = "";
try {
coords = {
lat: position.coords.latitude,
long: position.coords.longitude
};
} catch (err) {
return reject(err);
}
return resolve(coords);
}
});
}
Store:
var Ui = new WebUi({
coordincates:'',//initialise
});
export default {
name: "app",
store: Ui.store,
components: { Page },
data() {
return {
favIcon: getFavIcon()
};
}
};
updates WebUi coordinate after Promise completed.
I am updating store again after get coordinate then getting following error in vue js.
Do not mutate vuex store state outside mutation handlers.
I did modified store/index.js
updates
strict: false,
now issue is how do i update my coordinate of store from javascript.
If you set scrict to false, you will be able to mutate the store's state from any component like below:
// use this code inside a component (only throws no warning if strict: false)
this.$store.state.coordinates = {x: 1, y: 2};
Notice the name is coordinates, in you example your store was coordincates, I'm assuming this was a typo.
If coordinates doesn't exist in the store, use:
// use this code inside a component (only throws no warning if strict: false)
// Use Vue.set() when the property may not exist inside the store yet
Vue.set(this.$store.state, 'coordinates', {x: 1, y: 2});
That being said, the best would be to leave strict to true and create mutations. Since you are using WebUi, it may have specific ways to do it.