Skip to content

Instantly share code, notes, and snippets.

@Ethan-Arrowood
Created July 2, 2021 22:09
Show Gist options
  • Save Ethan-Arrowood/977920344afedb43ff5bf1b1378ba04a to your computer and use it in GitHub Desktop.
Save Ethan-Arrowood/977920344afedb43ff5bf1b1378ba04a to your computer and use it in GitHub Desktop.
Connection Monitor WIP
const { Machine, interpret, send, sendParent } = require("xstate");
const https = require('https');
const CONNECTION_MONITOR_IPC_CHANNEL = 'connection-monitor'
const PING_IPC_CHANNEL = 'ping'
const CONNECTION_MONITOR_EVENTS = {
TOGGLE_PING: 'toggle ping',
CONNECT: 'connect',
DISCONNECT: 'disconnect'
}
const ping = () => {
return new Promise((resolve, reject) => {
const request = https.request("https://login.microsoftonline.com", { method: "HEAD" }, response => {
if (response.statusCode === 200) {
resolve()
} else {
reject()
}
})
request.end()
});
};
const PING_STATES = {
IDLE: 'idle',
PINGING: 'pinging',
TIMEOUT: 'timeout'
}
const PING_EVENTS = {
TOGGLE: 'toggle'
}
const pingMachine = Machine({
id: "pingMachine",
initial: "idle",
states: {
[PING_STATES.IDLE]: {
on: {
[PING_EVENTS.TOGGLE]: PING_STATES.PINGING
}
},
[PING_STATES.PINGING]: {
on: {
[PING_EVENTS.TOGGLE]: PING_STATES.IDLE
},
invoke: {
id: "pingService",
src: ping,
onDone: {
actions: sendParent(CONNECTION_MONITOR_EVENTS.CONNECT),
target: PING_STATES.TIMEOUT
},
onError: {
actions: sendParent(CONNECTION_MONITOR_EVENTS.DISCONNECT),
target: PING_STATES.TIMEOUT
}
}
},
[PING_STATES.TIMEOUT]: {
on: {
[PING_EVENTS.TOGGLE]: PING_STATES.IDLE
},
after: {
5000: { target: PING_STATES.PINGING }
}
}
}
});
const CONNECTION_MONITOR_STATES = {
CONNECTED: 'connected',
DISCONNECTED: 'disconnected'
}
const PING_SERVICE_ID = 'pingService'
const CONNECTION_MONITOR_TRANSITIONS = {
[CONNECTION_MONITOR_EVENTS.TOGGLE_PING]: {
actions: send({ type: PING_EVENTS.TOGGLE }, { to: PING_SERVICE_ID })
},
[CONNECTION_MONITOR_EVENTS.CONNECT]: {
target: CONNECTION_MONITOR_STATES.CONNECTED
},
[CONNECTION_MONITOR_EVENTS.DISCONNECT]: {
target: CONNECTION_MONITOR_STATES.DISCONNECTED
}
}
const connectionMachine = Machine({
id: "connectionMonitor",
initial: CONNECTION_MONITOR_STATES.DISCONNECTED,
invoke: { id: PING_SERVICE_ID, src: pingMachine },
states: {
[CONNECTION_MONITOR_STATES.DISCONNECTED]: {
on: {
[CONNECTION_MONITOR_EVENTS.TOGGLE_PING]: CONNECTION_MONITOR_TRANSITIONS[CONNECTION_MONITOR_EVENTS.TOGGLE_PING],
[CONNECTION_MONITOR_EVENTS.CONNECT]: CONNECTION_MONITOR_TRANSITIONS[CONNECTION_MONITOR_EVENTS.CONNECT]
}
},
[CONNECTION_MONITOR_STATES.CONNECTED]: {
on: {
[CONNECTION_MONITOR_EVENTS.TOGGLE_PING]: CONNECTION_MONITOR_TRANSITIONS[CONNECTION_MONITOR_EVENTS.TOGGLE_PING],
[CONNECTION_MONITOR_EVENTS.DISCONNECT]: CONNECTION_MONITOR_TRANSITIONS[CONNECTION_MONITOR_EVENTS.DISCONNECT]
}
}
}
});
const service = interpret(connectionMachine, { devTools: true })
module.exports = {
CONNECTION_MONITOR_IPC_CHANNEL,
CONNECTION_MONITOR_EVENTS,
PING_SERVICE_ID,
PING_IPC_CHANNEL,
connectionMonitor: service
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
<meta http-equiv="X-Content-Security-Policy" content="default-src 'self'; script-src 'self'">
<title>Hello World!</title>
</head>
<body>
<h1>connection monitor with xstate</h1>
<button id="toggle-ping">Toggle Ping</button>
<p>Pinging Status: <span id="ping-status"></span></p>
<p>Connection Status: <span id="connection-status"></span></p>
<!-- You can also require other files to run in this process -->
<script src="./renderer.js"></script>
</body>
</html>
// Modules to control application life and create native browser window
const {app, BrowserWindow, ipcMain} = require('electron')
const path = require('path')
const {
connectionMonitor,
CONNECTION_MONITOR_IPC_CHANNEL,
CONNECTION_MONITOR_EVENTS,
PING_IPC_CHANNEL,
PING_SERVICE_ID
} = require('./connectionMonitor.js')
function createWindow () {
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})
// and load the index.html of the app.
mainWindow.loadFile('index.html')
// Open the DevTools.
mainWindow.webContents.openDevTools()
connectionMonitor.onTransition(state => {
mainWindow.webContents.send(CONNECTION_MONITOR_IPC_CHANNEL, state.value)
})
connectionMonitor.start();
const pingService = connectionMonitor.children.get(PING_SERVICE_ID)
pingService.onTransition(state => {
mainWindow.webContents.send(PING_IPC_CHANNEL, state.value)
})
ipcMain.handle(CONNECTION_MONITOR_IPC_CHANNEL, (_, event) => {
connectionMonitor.send(event)
})
}
// 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.whenReady().then(() => {
createWindow()
app.on('activate', function () {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit()
})
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.
const { contextBridge, ipcRenderer } = require('electron')
const { CONNECTION_MONITOR_IPC_CHANNEL, CONNECTION_MONITOR_EVENTS, PING_IPC_CHANNEL } = require('./connectionMonitor.js')
contextBridge.exposeInMainWorld('connectionMonitor', {
toggle: () => ipcRenderer.invoke(CONNECTION_MONITOR_IPC_CHANNEL, CONNECTION_MONITOR_EVENTS.TOGGLE_PING),
addConnectionTransitionListener: (listener) => {
ipcRenderer.on(CONNECTION_MONITOR_IPC_CHANNEL, listener)
return () => {
ipcRenderer.removeListener(CONNECTION_MONITOR_IPC_CHANNEL, listener)
}
},
addPingTransitionListener: (listener) => {
ipcRenderer.on(PING_IPC_CHANNEL, listener)
return () => {
ipcRenderer.removeListener(PING_IPC_CHANNEL, listener)
}
}
})
document.getElementById('toggle-ping').onclick = () => {
window.connectionMonitor.toggle()
}
window.connectionMonitor.addConnectionTransitionListener((_, stateValue) => {
document.getElementById('connection-status').innerHTML = stateValue
})
window.connectionMonitor.addPingTransitionListener((_, stateValue) => {
document.getElementById('ping-status').innerHTML = stateValue === 'timeout' ? 'pinging' : stateValue
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment