My electron app is using the remote isolation approach (mentioned in this article). My remote web app is a Ruby on Rails app. Now, I want to push notifications from a remote web app to the electron app using WebSocket. To send the message to users even if they shut off the app, I got an idea. In the Rails server, I use ActionCable to broadcast the notifications to a channel. In the main process, I subscribe to this channel by using the actioncable package.
import ActionCable from "actioncable";
const websocketUrl = "wss://rt.custom-domain.dev/_/cable";
const cable = ActionCable.createConsumer(websocketUrl);
cable.subscriptions.create(
{ channel: "WallChannel", wall_id: "106" },
{
received(data: any): void {},
disconnect(): void {},
connected(): void {},
disconnected(): void {},
present(): void {},
absent(): void {},
}
);
But I got an error:
ReferenceError: window is not defined
Then I dive deep into the source code of the actioncable package and I found that the package using WebSocket API of the browser so that why the error appeared.
I tried to use ws package to subscribe to the websocket instead by following this post. But I couldn't connect to the websocket server. When I call App.ws.sendmessage
I got an error:
Error: WebSocket is not open: readyState 0 (CONNECTING)
Has anyone tried to push notifications from the remote web app by using websocket like what I've trying? Did you got the same problem? or If you got a better solution for my case, please share with me your idea. Thanks a lot.
This is my main.ts file of the electron app
import { app, BrowserWindow, ipcMain, Notification } from "electron";
import { createWindow } from "src/main/CreateWindow";
import { showNotification } from "./helpers";
import appConfigs from "src/AppConfigs";
let mainWindow: BrowserWindow;
const createAppWindow = () => {
mainWindow = createWindow(appConfigs.targetUrl, { interop: true });
// Open the DevTools.
if (!app.isPackaged) {
mainWindow.webContents.openDevTools();
}
};
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
if (require("electron-squirrel-startup")) {
// eslint-disable-line global-require
app.quit();
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on("ready", () => {
createAppWindow();
});
var App: { ws: any; param: any; connect_server: any; sendmessage: any } = {
ws: null,
param: null,
connect_server: () => {},
sendmessage: () => {},
};
App.sendmessage = function (send: any) {
let data = {
message: send,
action: "speak",
};
let message = {
command: "message",
identifier: JSON.stringify(App.param),
data: JSON.stringify(data),
};
App.ws.send(JSON.stringify(message));
};
App.connect_server = function () {
const WebSocket = require("ws");
App.ws = new WebSocket("wss://rt.custom-domain.dev/_/cable", [
"actioncable-v1-json",
"actioncable-unsupported",
]);
console.log("connect_server", App.ws);
App.param = { channel: "WallChannel", wall_id: 106 };
App.ws.on("open", function open() {
console.log("open channel");
let data = {
command: "subscribe",
identifier: JSON.stringify(App.param),
};
App.ws.send(JSON.stringify(data));
console.log("send JSON");
});
App.ws.on("message", function (event: any) {
console.log("message", event);
});
App.ws.on("error", function (err) {
console.log("Found error: " + err);
});
};
App.connect_server();
CreateWindow.ts
import { BrowserWindow } from "electron";
import appConfigs from "src/AppConfigs";
declare const MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY: any;
interface BrowserWindowOption {
title?: string;
height?: string;
width?: string;
interop?: boolean;
}
export const createWindow = (
url: string,
options: BrowserWindowOption = {}
): BrowserWindow => {
// Create the browser window.
const mainWindow = new BrowserWindow({
title: options.title || appConfigs.name,
height: options.height || appConfigs.height,
width: options.width || appConfigs.width,
webPreferences: options.interop
? {
preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY,
}
: {},
});
// and load the targetUrl.
mainWindow.loadURL(url || appConfigs.targetUrl);
return mainWindow;
};
package.json
{
"name": "electron-forge-app",
"productName": "electron-forge-app",
"version": "1",
"description": "My Electron application description",
"main": ".webpack/main",
"scripts": {
"start": "electron-forge start",
"package": "electron-forge package",
"make": "electron-forge make",
"publish": "electron-forge publish",
"lint": "eslint --ext .ts ."
},
"keywords": [],
"author": {
...
},
"license": "MIT",
"config": {
"forge": {
"packagerConfig": {},
"makers": [
{
"name": "@electron-forge/maker-squirrel",
"config": {
"name": "electron_furge"
}
},
{
"name": "@electron-forge/maker-zip",
"platforms": [
"darwin"
]
},
{
"name": "@electron-forge/maker-deb",
"config": {}
},
{
"name": "@electron-forge/maker-rpm",
"config": {}
}
],
"plugins": [
[
"@electron-forge/plugin-webpack",
{
"mainConfig": "./webpack.main.config.js",
"renderer": {
"config": "./webpack.renderer.config.js",
"entryPoints": [
{
"html": "./src/index.html",
"js": "./src/renderer.ts",
"name": "main_window",
"preload": {
"js": "./src/interop/preload.ts"
}
}
]
}
}
]
]
}
},
"devDependencies": {
"@electron-forge/cli": "^6.0.0-beta.54",
"@electron-forge/maker-deb": "^6.0.0-beta.54",
"@electron-forge/maker-rpm": "^6.0.0-beta.54",
"@electron-forge/maker-squirrel": "^6.0.0-beta.54",
"@electron-forge/maker-zip": "^6.0.0-beta.54",
"@electron-forge/plugin-webpack": "6.0.0-beta.54",
"@marshallofsound/webpack-asset-relocator-loader": "^0.5.0",
"@types/actioncable": "^5.2.4",
"@typescript-eslint/eslint-plugin": "^4.0.1",
"@typescript-eslint/parser": "^4.0.1",
"css-loader": "^4.2.1",
"electron": "12.0.5",
"eslint": "^7.6.0",
"eslint-plugin-import": "^2.20.0",
"fork-ts-checker-webpack-plugin": "^5.0.14",
"node-loader": "^1.0.1",
"style-loader": "^1.2.1",
"ts-loader": "^8.0.2",
"typescript": "^4.0.2"
},
"dependencies": {
"actioncable": "^5.2.6",
"electron-squirrel-startup": "^1.0.0",
"ws": "^7.4.5"
}
}