Repository: asticode/astilectron
Branch: master
Commit: 11d8d1f4eb0b
Files: 8
Total size: 46.1 KB
Directory structure:
gitextract_i0my_knd/
├── .gitignore
├── LICENSE
├── README.md
├── index.js
├── main.js
├── package.json
└── src/
├── client.js
└── consts.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
.idea/
node_modules
npm-debug.log
dist
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2017 Quentin RENARD
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
`astilectron` is an Electron app that provides an API over a TCP socket that allows executing Electron's method as well as capturing Electron's events.
# Warning
This project is not maintained anymore.
# Architecture
+-----------------------+ TCP +-------------+ IPC +---------------------+
+ Client App (any Lang) |<--------->+ Astilectron +<-------->+ win1: (HTML/JS/CSS) +
+-----------------------+ +-------------+ | +---------------------++
| | +---->+ win2: (HTML/JS/CSS) +
| +----------+ | | +---------------------++
+---------+ Electron +--------+ +-->+ win3: (HTML/JS/CSS) +
+----------+ +---------------------+
# Language bindings
Language bindings play a major role with `astilectron` as they allow communicating with its TCP socket and therefore interacting with its API in any language.
## I want to develop language bindings for a new language
Great! :)
Here's a few things you need to know:
- it's the responsibility of the language bindings to provision `astilectron` which usually consists of downloading and unzipping `astilectron` as well as `electron`
- the TCP addr is sent to `astilectron` through a command line argument therefore your command line when calling `astilectron` should look like `<path to electron executable> <path to astilectron directory>/main.js <tcp addr>`
## Language bindings for GO
Check out [go-astilectron](https://github.com/asticode/go-astilectron) for `astilectron` GO language bindings
# Features and roadmap
- [x] window basic methods (create, show, close, resize, minimize, maximize, ...)
- [x] window basic events (close, blur, focus, unresponsive, crashed, ...)
- [x] remote messaging (messages between GO and the JS in the webserver)
- [x] multi screens/displays
- [x] menu methods and events (create, insert, append, popup, clicked, ...)
- [x] dialogs (open or save file, alerts, ...)
- [ ] accelerators (shortcuts)
- [ ] file methods (drag & drop, ...)
- [ ] clipboard methods
- [ ] power monitor events (suspend, resume, ...)
- [ ] notifications (macosx)
- [ ] desktop capturer (audio and video)
- [ ] session methods
- [ ] session events
- [ ] window advanced options (add missing ones)
- [ ] window advanced methods (add missing ones)
- [ ] window advanced events (add missing ones)
- [ ] child windows
# Contribute
For now only GO has its official bindings with `astilectron`, but the more language has its bindings the better! Therefore if you feel like implementing bindings with `astilectron` in some other language feel free to reach out to me to get some more info and finally to get your repo listed here.
Also I'm far from being an expert in Node.JS therefore if you see anything that seems wrong in `astilectron` feel free to create an issue or even better contribute through a PR!
You know you want to! :D
# Cheers to
[thrust](https://github.com/breach/thrust) which is awesome but unfortunately not maintained anymore. It inspired this project.
================================================
FILE: index.js
================================================
// @ts-check
'use strict'
const electron = require('electron')
const {app, BrowserWindow, ipcMain, Menu, MenuItem, Tray, dialog, Notification, globalShortcut} = electron
const consts = require('./src/consts.js')
const client = require('./src/client.js')
const readline = require('readline')
let rl;
let callbacks = {};
let counters = {};
let elements = {};
let windowOptions = {};
let menus = {};
let quittingApp = false;
// Single instance
let lastWindowId = null;
// App is quitting
const beforeQuit = () => {
quittingApp = true;
client.write(consts.targetIds.app,consts.eventNames.appCmdQuit);
};
// App is ready
function onReady () {
// Init
const screen = electron.screen
Menu.setApplicationMenu(null)
// Listen to screen events
screen.on('display-added', function() {
client.write(consts.targetIds.app, consts.eventNames.displayEventAdded, {displays: {all: screen.getAllDisplays(), primary: screen.getPrimaryDisplay()}})
})
screen.on('display-metrics-changed', function() {
client.write(consts.targetIds.app, consts.eventNames.displayEventMetricsChanged, {displays: {all: screen.getAllDisplays(), primary: screen.getPrimaryDisplay()}})
})
screen.on('display-removed', function() {
client.write(consts.targetIds.app, consts.eventNames.displayEventRemoved, {displays: {all: screen.getAllDisplays(), primary: screen.getPrimaryDisplay()}})
})
const powerMonitor = electron.powerMonitor
// Listen to power events
powerMonitor.on('suspend', function() {
client.write(consts.targetIds.app, consts.eventNames.powerEventSuspend)
})
powerMonitor.on('resume', function() {
client.write(consts.targetIds.app, consts.eventNames.powerEventResume)
})
powerMonitor.on('on-ac', function() {
client.write(consts.targetIds.app, consts.eventNames.powerEventOnAC)
})
powerMonitor.on('on-battery', function () {
client.write(consts.targetIds.app, consts.eventNames.powerEventOnBattery)
})
powerMonitor.on('shutdown', function() {
client.write(consts.targetIds.app, consts.eventNames.powerEventShutdown)
})
powerMonitor.on('lock-screen', function() {
client.write(consts.targetIds.app, consts.eventNames.powerEventLockScreen)
})
powerMonitor.on('unlock-screen', function() {
client.write(consts.targetIds.app, consts.eventNames.powerEventUnlockScreen)
})
powerMonitor.on('user-did-become-active', function() {
client.write(consts.targetIds.app, consts.eventNames.powerEventUserDidBecomeActive)
})
powerMonitor.on('user-did-resign-active', function() {
client.write(consts.targetIds.app, consts.eventNames.powerEventUserDidResignActive)
})
// Listen on main ipcMain
ipcMain.on(consts.eventNames.ipcEventMessage, (event, arg) => {
let payload = {message: arg.message};
if (typeof arg.callbackId !== "undefined") payload.callbackId = arg.callbackId;
client.write(arg.targetID, consts.eventNames.windowEventMessage, payload)
});
ipcMain.on(consts.eventNames.ipcEventMessageCallback, (event, arg) => {
let payload = {message: arg.message};
if (typeof arg.callbackId !== "undefined") payload.callbackId = arg.callbackId;
client.write(arg.targetID, consts.eventNames.windowEventMessageCallback, payload)
});
// Read from client
rl.on('line', function(line){
// Parse the JSON
let json = JSON.parse(line)
// Switch on event name
let window;
switch (json.name) {
// App
case consts.eventNames.appCmdQuit:
app.quit();
break;
// Dock
case consts.eventNames.dockCmdBounce:
let id = 0;
if (typeof app.dock !== "undefined") {
id = app.dock.bounce(json.bounceType);
}
client.write(consts.targetIds.dock, consts.eventNames.dockEventBouncing, {id: id});
break;
case consts.eventNames.dockCmdBounceDownloads:
if (typeof app.dock !== "undefined") {
app.dock.downloadFinished(json.filePath);
}
client.write(consts.targetIds.dock, consts.eventNames.dockEventDownloadsBouncing);
break;
case consts.eventNames.dockCmdCancelBounce:
if (typeof app.dock !== "undefined") {
app.dock.cancelBounce(json.id);
}
client.write(consts.targetIds.dock, consts.eventNames.dockEventBouncingCancelled);
break;
case consts.eventNames.dockCmdHide:
if (typeof app.dock !== "undefined") {
app.dock.hide();
}
client.write(consts.targetIds.dock, consts.eventNames.dockEventHidden);
break;
case consts.eventNames.dockCmdSetBadge:
if (typeof app.dock !== "undefined") {
app.dock.setBadge(json.badge);
}
client.write(consts.targetIds.dock, consts.eventNames.dockEventBadgeSet);
break;
case consts.eventNames.dockCmdSetIcon:
if (typeof app.dock !== "undefined") {
app.dock.setIcon(json.image);
}
client.write(consts.targetIds.dock, consts.eventNames.dockEventIconSet);
break;
case consts.eventNames.dockCmdShow:
if (typeof app.dock !== "undefined") {
app.dock.show();
}
client.write(consts.targetIds.dock, consts.eventNames.dockEventShown);
break;
// Menu
case consts.eventNames.menuCmdCreate:
menuCreate(json.menu)
menus[json.menu.rootId] = json.targetID
setMenu(json.menu.rootId)
client.write(json.targetID, consts.eventNames.menuEventCreated)
break;
case consts.eventNames.menuCmdDestroy:
elements[json.targetID] = null
if (menus[json.menu.rootId] === json.targetID) {
menus[json.menu.rootId] = null
setMenu(json.menu.rootId)
}
client.write(json.targetID, consts.eventNames.menuEventDestroyed)
break;
// Menu item
case consts.eventNames.menuItemCmdSetChecked:
elements[json.targetID].checked = json.menuItemOptions.checked
client.write(json.targetID, consts.eventNames.menuItemEventCheckedSet)
break;
case consts.eventNames.menuItemCmdSetEnabled:
elements[json.targetID].enabled = json.menuItemOptions.enabled
client.write(json.targetID, consts.eventNames.menuItemEventEnabledSet)
break;
case consts.eventNames.menuItemCmdSetLabel:
elements[json.targetID].label = json.menuItemOptions.label
client.write(json.targetID, consts.eventNames.menuItemEventLabelSet)
break;
case consts.eventNames.menuItemCmdSetVisible:
elements[json.targetID].visible = json.menuItemOptions.visible
client.write(json.targetID, consts.eventNames.menuItemEventVisibleSet)
break;
// Notification
case consts.eventNames.notificationCmdCreate:
notificationCreate(json);
break;
case consts.eventNames.notificationCmdShow:
if (Notification.isSupported()) {
elements[json.targetID].show();
}
break;
// Session
case consts.eventNames.sessionCmdClearCache:
elements[json.targetID].clearCache().then(() => {
client.write(json.targetID, consts.eventNames.sessionEventClearedCache)
})
break;
case consts.eventNames.sessionCmdFlushStorage:
elements[json.targetID].flushStorageData();
client.write(json.targetID, consts.eventNames.sessionEventFlushedStorage)
break;
case consts.eventNames.sessionCmdLoadExtension:
elements[json.targetID].loadExtension(json.path).then(() => {
client.write(json.targetID, consts.eventNames.sessionEventLoadedExtension)
})
break;
// Sub menu
case consts.eventNames.subMenuCmdAppend:
elements[json.targetID].append(menuItemCreate(json.menuItem))
setMenu(json.menuItem.rootId)
client.write(json.targetID, consts.eventNames.subMenuEventAppended)
break;
case consts.eventNames.subMenuCmdClosePopup:
window = null
if (typeof json.windowId !== "undefined") {
window = elements[json.windowId]
}
elements[json.targetID].closePopup(window)
client.write(json.targetID, consts.eventNames.subMenuEventClosedPopup)
break;
case consts.eventNames.subMenuCmdInsert:
elements[json.targetID].insert(json.menuItemPosition, menuItemCreate(json.menuItem))
setMenu(json.menuItem.rootId)
client.write(json.targetID, consts.eventNames.subMenuEventInserted)
break;
case consts.eventNames.subMenuCmdPopup:
window = null
if (typeof json.windowId !== "undefined") {
window = elements[json.windowId]
}
json.menuPopupOptions.async = true
elements[json.targetID].popup(window, json.menuPopupOptions)
client.write(json.targetID, consts.eventNames.subMenuEventPoppedUp)
break;
// Tray
case consts.eventNames.trayCmdCreate:
trayCreate(json)
break;
case consts.eventNames.trayCmdDestroy:
elements[json.targetID].destroy()
elements[json.targetID] = null
client.write(json.targetID, consts.eventNames.trayEventDestroyed)
break;
case consts.eventNames.trayCmdSetImage:
elements[json.targetID].setImage(json.image);
client.write(json.targetID, consts.eventNames.trayEventImageSet)
break;
case consts.eventNames.trayCmdPopupContextMenu:
trayPopUpContextMenu(json);
client.write(json.targetID, consts.eventNames.trayEventPoppedUpContextMenu);
break;
// Web contents
case consts.eventNames.webContentsEventLoginCallback:
executeCallback(consts.callbackNames.webContentsLogin, json, [json.username, json.password]);
break;
// Window
case consts.eventNames.windowCmdBlur:
elements[json.targetID].blur()
break;
case consts.eventNames.windowCmdCenter:
elements[json.targetID].center()
break;
case consts.eventNames.windowCmdClose:
elements[json.targetID].close()
break;
case consts.eventNames.windowCmdCreate:
windowCreate(json)
break;
case consts.eventNames.windowCmdDestroy:
elements[json.targetID].destroy()
elements[json.targetID] = null
break;
case consts.eventNames.windowCmdFocus:
elements[json.targetID].focus()
break;
case consts.eventNames.windowCmdHide:
elements[json.targetID].hide()
break;
case consts.eventNames.windowCmdLog:
elements[json.targetID].webContents.send(consts.eventNames.ipcCmdLog, json.message)
break;
case consts.eventNames.windowCmdMaximize:
elements[json.targetID].maximize()
break;
case consts.eventNames.windowCmdMessage:
case consts.eventNames.windowCmdMessageCallback:
let m = {message: json.message}
if (typeof json.callbackId !== "undefined") m.callbackId = json.callbackId
const targetElement = elements[json.targetID]
if (targetElement) targetElement.webContents.send(json.name === consts.eventNames.windowCmdMessageCallback ? consts.eventNames.ipcCmdMessageCallback : consts.eventNames.ipcCmdMessage, m)
break;
case consts.eventNames.windowCmdMinimize:
elements[json.targetID].minimize()
break;
case consts.eventNames.windowCmdMove:
elements[json.targetID].setPosition(json.windowOptions.x, json.windowOptions.y, true)
break;
case consts.eventNames.windowCmdMoveTop:
elements[json.targetID].moveTop()
client.write(json.targetID, consts.eventNames.windowEventMovedTop)
break;
case consts.eventNames.windowCmdResize:
elements[json.targetID].setSize(json.windowOptions.width, json.windowOptions.height, true)
break;
case consts.eventNames.windowCmdResizeContent:
elements[json.targetID].setContentSize(json.windowOptions.width, json.windowOptions.height, true)
break;
case consts.eventNames.windowCmdSetAlwaysOnTop:
elements[json.targetID].setAlwaysOnTop(json.enable ? json.enable : false)
client.write(json.targetID, consts.eventNames.windowEventAlwaysOnTopChanged)
break;
case consts.eventNames.windowCmdSetBounds:
elements[json.targetID].setBounds(json.bounds, true);
break;
case consts.eventNames.windowCmdSetFullScreen:
elements[json.targetID].setFullScreen(json.enable ? json.enable : false)
break;
case consts.eventNames.windowCmdRestore:
elements[json.targetID].restore()
break;
case consts.eventNames.windowCmdShow:
elements[json.targetID].show()
break;
case consts.eventNames.windowCmdSetContentProtection:
elements[json.targetID].setContentProtection(json.enable ? json.enable : false)
client.write(json.targetID, consts.eventNames.windowEventContentProtectionSet)
break;
case consts.eventNames.windowCmdWebContentsCloseDevTools:
elements[json.targetID].webContents.closeDevTools()
break;
case consts.eventNames.windowCmdWebContentsOpenDevTools:
elements[json.targetID].webContents.openDevTools()
break;
case consts.eventNames.windowCmdUnmaximize:
elements[json.targetID].unmaximize()
break;
case consts.eventNames.windowCmdUpdateCustomOptions:
windowOptions[json.targetID] = json.windowOptions
client.write(json.targetID, consts.eventNames.windowEventUpdatedCustomOptions, json.windowOptions)
break;
case consts.eventNames.windowCmdWebContentsExecuteJavascript:
elements[json.targetID].webContents.executeJavaScript(json.code).then(() => client.write(json.targetID, consts.eventNames.windowEventWebContentsExecutedJavaScript));
break;
// Global Shortcut
case consts.eventNames.globalShortcutsCmdRegister:
const isRegistered = globalShortcut.register(json.globalShortcuts.accelerator, () => {
client.write(json.targetID, consts.eventNames.globalShortcutsEventTriggered, {globalShortcuts:{accelerator: json.globalShortcuts.accelerator}});
});
client.write(json.targetID, consts.eventNames.globalShortcutsEventRegistered, {globalShortcuts:{isRegistered: isRegistered}});
break;
case consts.eventNames.globalShortcutsCmdIsRegistered:
client.write(json.targetID, consts.eventNames.globalShortcutsEventIsRegistered, {globalShortcuts:{isRegistered: globalShortcut.isRegistered(json.globalShortcuts.accelerator)}});
break;
case consts.eventNames.globalShortcutsCmdUnregister:
globalShortcut.unregister(json.globalShortcuts.accelerator);
client.write(json.targetID, consts.eventNames.globalShortcutsEventUnregistered);
break
case consts.eventNames.globalShortcutsCmdUnregisterAll:
globalShortcut.unregisterAll();
client.write(json.targetID, consts.eventNames.globalShortcutsEventUnregisteredAll);
break
}
});
// Send electron.ready event
client.write(consts.targetIds.app, consts.eventNames.appEventReady, {
displays: {
all: screen.getAllDisplays(),
primary: screen.getPrimaryDisplay()
},
supported: {
notification: Notification.isSupported()
}
})
};
// start begins listening to go-astilectron.
function start(address = process.argv[2]) {
client.init(address);
rl = readline.createInterface({ input: client.socket });
app.on("before-quit", beforeQuit);
if (app.isReady()) {
onReady();
} else {
app.on("ready", onReady);
}
app.on("window-all-closed", app.quit);
}
// menuCreate creates a new menu
function menuCreate(menu) {
if (typeof menu !== "undefined") {
elements[menu.id] = new Menu()
for(let i = 0; i < menu.items.length; i++) {
elements[menu.id].append(menuItemCreate(menu.items[i]))
}
return elements[menu.id]
}
return null
}
// menuItemCreate creates a menu item
function menuItemCreate(menuItem) {
const itemId = menuItem.id
menuItem.options.click = function(menuItem) {
client.write(itemId, consts.eventNames.menuItemEventClicked, {menuItemOptions: menuItemToJSON(menuItem)})
}
if (typeof menuItem.submenu !== "undefined") {
menuItem.options.type = 'submenu'
menuItem.options.submenu = menuCreate(menuItem.submenu)
}
elements[itemId] = new MenuItem(menuItem.options)
return elements[itemId]
}
// menuItemToJSON returns the proper fields not to raise an exception
function menuItemToJSON(menuItem) {
return {
checked: menuItem.checked,
enabled: menuItem.enabled,
label: menuItem.label,
visible: menuItem.visible,
}
}
// setMenu sets a menu
function setMenu(rootId) {
let menu = null
if (typeof menus[rootId] !== "undefined" && typeof elements[menus[rootId]] !== "undefined") {
menu = elements[menus[rootId]]
}
if (rootId === consts.targetIds.app) {
Menu.setApplicationMenu(menu)
} else if (rootId === consts.targetIds.dock && typeof app.dock !== "undefined") {
app.dock.setMenu(menu)
} else if (elements[rootId].constructor === Tray) {
elements[rootId].setContextMenu(menu);
} else {
elements[rootId].setMenu(menu);
}
}
// notificationCreate creates a notification
function notificationCreate(json) {
if (Notification.isSupported()) {
elements[json.targetID] = new Notification(json.notificationOptions);
elements[json.targetID].on('action', (event, index) => { client.write(json.targetID, consts.eventNames.notificationEventActioned, {index: index}) })
elements[json.targetID].on('click', () => { client.write(json.targetID, consts.eventNames.notificationEventClicked) })
elements[json.targetID].on('close', () => { client.write(json.targetID, consts.eventNames.notificationEventClosed) })
elements[json.targetID].on('reply', (event, reply) => { client.write(json.targetID, consts.eventNames.notificationEventReplied, {reply: reply}) })
elements[json.targetID].on('show', () => { client.write(json.targetID, consts.eventNames.notificationEventShown) })
}
client.write(json.targetID, consts.eventNames.notificationEventCreated)
}
// trayCreate creates a tray
function trayCreate(json) {
elements[json.targetID] = new Tray(json.trayOptions.image);
if (typeof json.trayOptions.tooltip !== "undefined") {
elements[json.targetID].setToolTip(json.trayOptions.tooltip);
}
elements[json.targetID].on('click', (index, event) => { client.write(json.targetID, consts.eventNames.trayEventClicked, {"bounds":{x:event.x, y:event.y,width:event.width,height:event.height}})})
elements[json.targetID].on('double-click', (index, event) => { client.write(json.targetID, consts.eventNames.trayEventDoubleClicked, {"bounds":{x:event.x, y:event.y,width:event.width,height:event.height}})})
elements[json.targetID].on('right-click', (index, event) => { client.write(json.targetID, consts.eventNames.trayEventRightClicked, {"bounds":{x:event.x, y:event.y,width:event.width,height:event.height}})})
client.write(json.targetID, consts.eventNames.trayEventCreated)
}
// trayPopUpContextMenu pops up the context menu of the tray
function trayPopUpContextMenu(json) {
let menu = menuCreate(json.menu);
let position = json.menuPopupOptions;
if (!menu && !position) elements[json.targetID].popUpContextMenu();
else if (menu && !position) elements[json.targetID].popUpContextMenu(menu);
else if (!menu && position) elements[json.targetID].popUpContextMenu(position);
else elements[json.targetID].popUpContextMenu(menu, position);
}
// windowCreate creates a new window
function windowCreate(json) {
if (!json.windowOptions.webPreferences) {
json.windowOptions.webPreferences = {}
}
json.windowOptions.webPreferences.contextIsolation = false
json.windowOptions.webPreferences.nodeIntegration = true
elements[json.targetID] = new BrowserWindow(json.windowOptions)
windowOptions[json.targetID] = json.windowOptions
if (typeof json.windowOptions.proxy !== "undefined") {
elements[json.targetID].webContents.session.setProxy(json.windowOptions.proxy)
.then(() => windowCreateFinish(json))
} else {
windowCreateFinish(json)
}
}
// windowCreateFinish finishes creating a new window
function windowCreateFinish(json) {
elements[json.targetID].setMenu(null)
elements[json.targetID].loadURL(json.url, (typeof json.windowOptions.load !== "undefined" ? json.windowOptions.load : {}));
elements[json.targetID].on('blur', () => { client.write(json.targetID, consts.eventNames.windowEventBlur) })
elements[json.targetID].on('close', (e) => {
if (typeof windowOptions[json.targetID] !== "undefined" && typeof windowOptions[json.targetID].custom !== "undefined") {
if (typeof windowOptions[json.targetID].custom.messageBoxOnClose !== "undefined") {
let buttonId = dialog.showMessageBoxSync(null, windowOptions[json.targetID].custom.messageBoxOnClose)
if (typeof windowOptions[json.targetID].custom.messageBoxOnClose.confirmId !== "undefined" && windowOptions[json.targetID].custom.messageBoxOnClose.confirmId !== buttonId) {
e.preventDefault()
return
}
}
if (!quittingApp) {
if (windowOptions[json.targetID].custom.minimizeOnClose) {
e.preventDefault();
elements[json.targetID].minimize();
} else if (windowOptions[json.targetID].custom.hideOnClose) {
e.preventDefault();
elements[json.targetID].hide();
}
}
}
})
elements[json.targetID].on('closed', () => {
client.write(json.targetID, consts.eventNames.windowEventClosed)
delete elements[json.targetID]
})
elements[json.targetID].on('enter-full-screen', () => { client.write(json.targetID, consts.eventNames.windowEventEnterFullScreen, {windowOptions: {fullscreen: true}})} )
elements[json.targetID].on('focus', () => { client.write(json.targetID, consts.eventNames.windowEventFocus) })
elements[json.targetID].on('hide', () => { client.write(json.targetID, consts.eventNames.windowEventHide) })
elements[json.targetID].on('leave-full-screen', () => { client.write(json.targetID, consts.eventNames.windowEventLeaveFullScreen, {windowOptions: {fullscreen: false}})} )
elements[json.targetID].on('maximize', () => {
let bounds = elements[json.targetID].getBounds();
client.write(json.targetID, consts.eventNames.windowEventMaximize, {bounds: bounds});
})
elements[json.targetID].on('minimize', () => { client.write(json.targetID, consts.eventNames.windowEventMinimize) })
elements[json.targetID].on('move', () => {
let bounds = elements[json.targetID].getBounds();
client.write(json.targetID, consts.eventNames.windowEventMove, {bounds: bounds});
})
elements[json.targetID].on('moved', () => {
let bounds = elements[json.targetID].getBounds();
client.write(json.targetID, consts.eventNames.windowEventMoved, {bounds: bounds});
})
elements[json.targetID].on('ready-to-show', () => { client.write(json.targetID, consts.eventNames.windowEventReadyToShow) })
elements[json.targetID].on('resize', () => {
let bounds = elements[json.targetID].getBounds();
client.write(json.targetID, consts.eventNames.windowEventResize, {bounds: bounds});
})
elements[json.targetID].on('resize-content', () => {
let bounds = elements[json.targetID].getBounds();
client.write(json.targetID, consts.eventNames.windowEventResizeContent, {bounds: bounds})
})
elements[json.targetID].on('restore', () => { client.write(json.targetID, consts.eventNames.windowEventRestore) })
elements[json.targetID].on('show', () => { client.write(json.targetID, consts.eventNames.windowEventShow) })
elements[json.targetID].on('unmaximize', () => {
let bounds = elements[json.targetID].getBounds();
client.write(json.targetID, consts.eventNames.windowEventUnmaximize, {bounds: bounds});
})
elements[json.targetID].on('unresponsive', () => { client.write(json.targetID, consts.eventNames.windowEventUnresponsive) })
elements[json.targetID].on('will-move', () => {
let bounds = elements[json.targetID].getBounds();
client.write(json.targetID, consts.eventNames.windowEventWillMove, {bounds: bounds});
})
elements[json.targetID].webContents.on('did-finish-load', () => {
elements[json.targetID].webContents.executeJavaScript(
`const {ipcRenderer} = require('electron')
var astilectron = {
onMessageOnce: false,
onMessage: function(callback) {
if (astilectron.onMessageOnce) {
return
}
ipcRenderer.on('`+ consts.eventNames.ipcCmdMessage +`', function(event, message) {
let v = callback(message.message)
if (typeof message.callbackId !== "undefined") {
let e = {callbackId: message.callbackId, targetID: '`+ json.targetID +`'}
if (typeof v !== "undefined") e.message = v
ipcRenderer.send('`+ consts.eventNames.ipcEventMessageCallback +`', e)
}
})
astilectron.onMessageOnce = true
},
callbacks: {},
counters: {},
registerCallback: function(k, e, c, n) {
e.targetID = '`+ json.targetID +`';
if (typeof c !== "undefined") {
if (typeof astilectron.counters[k] === "undefined") {
astilectron.counters[k] = 1;
}
e.callbackId = String(astilectron.counters[k]++);
if (typeof astilectron.callbacks[k] === "undefined") {
astilectron.callbacks[k] = {};
}
astilectron.callbacks[k][e.callbackId] = c;
}
ipcRenderer.send(n, e);
},
executeCallback: function(k, message, args) {
if (typeof astilectron.callbacks[k][message.callbackId] !== "undefined") {
astilectron.callbacks[k][message.callbackId].apply(null, args);
}
},
sendMessage: function(message, callback) {
astilectron.registerCallback('` + consts.callbackNames.webContentsMessage + `', {message: message}, callback, '`+ consts.eventNames.ipcEventMessage +`');
}
};
ipcRenderer.on('`+ consts.eventNames.ipcCmdMessageCallback +`', function(event, message) {
astilectron.executeCallback('` + consts.callbackNames.webContentsMessage + `', message, [message.message]);
});
ipcRenderer.on('`+ consts.eventNames.ipcCmdLog+`', function(event, message) {
console.log(message)
});
` + (typeof json.windowOptions.custom !== "undefined" && typeof json.windowOptions.custom.script !== "undefined" ? json.windowOptions.custom.script : "") + `
document.dispatchEvent(new Event('astilectron-ready'))`
)
sessionCreate(elements[json.targetID].webContents, json.sessionId)
let bounds = elements[json.targetID].getBounds();
client.write(json.targetID, consts.eventNames.windowEventDidFinishLoad, {bounds: bounds})
})
elements[json.targetID].webContents.on('did-get-redirect-request', (event, oldUrl, newUrl) => {
client.write(json.targetID, consts.eventNames.windowEventDidGetRedirectRequest, {
newUrl: newUrl,
oldUrl: oldUrl
})
})
elements[json.targetID].webContents.on('login', (event, request, authInfo, callback) => {
event.preventDefault();
registerCallback(json, consts.callbackNames.webContentsLogin, {authInfo: authInfo, request: request}, consts.eventNames.webContentsEventLogin, callback);
})
elements[json.targetID].webContents.on('will-navigate', (event, url) => {
client.write(json.targetID, consts.eventNames.windowEventWillNavigate, {
url: url
})
})
if (typeof json.windowOptions.appDetails !== "undefined" && process.platform === "win32"){
elements[json.targetID].setThumbarButtons([]);
elements[json.targetID].setAppDetails(json.windowOptions.appDetails);
}
lastWindowId = json.targetID
}
function registerCallback(json, k, e, n, c) {
if (typeof counters[k] === "undefined") {
counters[k] = 1;
}
e.callbackId = String(counters[k]++);
if (typeof callbacks[k] === "undefined") {
callbacks[k] = {};
}
callbacks[k][e.callbackId] = c;
client.write(json.targetID, n, e);
}
function executeCallback(k, json, args) {
if (typeof callbacks[k][json.callbackId] !== "undefined") {
callbacks[k][json.callbackId].apply(null, args);
}
}
function sessionCreate(webContents, sessionId) {
elements[sessionId] = webContents.session
elements[sessionId].on('will-download', () => { client.write(sessionId, consts.eventNames.sessionEventWillDownload) })
}
function getLastWindow() {
if (elements[lastWindowId]) return elements[lastWindowId]
return null
}
module.exports = {
getLastWindow,
start,
client,
consts
}
================================================
FILE: main.js
================================================
"use strict";
const { app } = require("electron");
const { start, getLastWindow, client, consts } = require("./index");
// edge case when the program is launched without arguments
if (process.argv.length == 1) {
app.requestSingleInstanceLock();
app.quit();
return;
}
if (process.argv[3] === "true") {
// Lock
const singlesInstanceLock = app.requestSingleInstanceLock();
if (!singlesInstanceLock) {
app.quit();
return;
}
// Someone tried to run a second instance, we should focus our window.
app.on("second-instance", (event, commandLine, workingDirectory) => {
client.write(consts.targetIds.app, consts.eventNames.appEventSecondInstance, {secondInstance: {commandLine: commandLine, workingDirectory: workingDirectory}})
const lastWindow = getLastWindow()
if (lastWindow) {
if (lastWindow.isMinimized()) lastWindow.restore();
lastWindow.show();
}
});
}
// Command line switches
let idx = 4;
for (let i = idx; i < process.argv.length; i++) {
let s = process.argv[i].replace(/^[\-]+/g, "");
let v;
if (
typeof process.argv[i + 1] !== "undefined" &&
!process.argv[i + 1].startsWith("-")
) {
v = process.argv[i + 1];
i++;
}
app.commandLine.appendSwitch(s, v);
}
start();
================================================
FILE: package.json
================================================
{
"name": "astilectron",
"version": "0.57.0",
"description": "Electron-based cross-language application framework",
"license": "MIT",
"author": "Quentin Renard (https://github.com/asticode)",
"contributors": [
"fregie (https://github.com/fregie)",
"vahid-sohrabloo (https://github.com/vahid-sohrabloo)",
"cbairy (https://github.com/cbairy)",
"john dev (https://github.com/john-dev)",
"AONOSORA (https://github.com/ZEROKISEKI)",
"Spencer Brower (https://github.com/sbrow"
],
"bin": "main.js",
"main": "index.js",
"peerDependencies": {
"electron": ">=1.8.1"
}
}
================================================
FILE: src/client.js
================================================
"use strict";
const net = require("net");
const url = require("url");
// Client can read/write messages from a TCP server
class Client {
// init initializes the Client
init(addr) {
let u = url.parse("tcp://" + addr, false, false);
this.socket = new net.Socket();
this.socket.connect(u.port, u.hostname, function() {});
this.socket.on("close", function() {
process.exit();
});
this.socket.on("error", function(err) {
// Prevent Unhandled Exception resulting from TCP Error
console.error(err);
});
return this;
}
// write writes an event to the server
write(targetID, eventName, payload) {
if(this.socket.destroyed) return;
let data = { name: eventName, targetID: targetID };
if (typeof payload !== "undefined") Object.assign(data, payload);
this.socket.write(JSON.stringify(data) + "\n");
}
}
module.exports = new Client();
================================================
FILE: src/consts.js
================================================
'use strict'
module.exports = {
callbackNames: {
webContentsLogin: "web.contents.login",
webContentsMessage: "web.contents.message",
},
eventNames: {
appCmdQuit: "app.cmd.quit",
appEventReady: "app.event.ready",
appEventSecondInstance: "app.event.second.instance",
displayEventAdded: "display.event.added",
displayEventMetricsChanged: "display.event.metrics.changed",
displayEventRemoved: "display.event.removed",
dockCmdBounce: "dock.cmd.bounce",
dockCmdBounceDownloads: "dock.cmd.bounce.downloads",
dockCmdCancelBounce: "dock.cmd.cancel.bounce",
dockCmdHide: "dock.cmd.hide",
dockCmdSetBadge: "dock.cmd.set.badge",
dockCmdSetIcon: "dock.cmd.set.icon",
dockCmdShow: "dock.cmd.show",
dockEventBadgeSet: "dock.event.badge.set",
dockEventBouncing: "dock.event.bouncing",
dockEventBouncingCancelled: "dock.event.bouncing.cancelled",
dockEventDownloadsBouncing: "dock.event.download.bouncing",
dockEventHidden: "dock.event.hidden",
dockEventIconSet: "dock.event.icon.set",
dockEventShown: "dock.event.shown",
globalShortcutsCmdRegister: "global.shortcuts.cmd.register",
globalShortcutsCmdIsRegistered: "global.shortcuts.cmd.is.registered",
globalShortcutsCmdUnregister: "global.shortcuts.cmd.unregister",
globalShortcutsCmdUnregisterAll: "global.shortcuts.cmd.unregister.all",
globalShortcutsEventRegistered: "global.shortcuts.event.registered",
globalShortcutsEventIsRegistered: "global.shortcuts.event.is.registered",
globalShortcutsEventUnregistered: "global.shortcuts.event.unregistered",
globalShortcutsEventUnregisteredAll: "global.shortcuts.event.unregistered.all",
globalShortcutsEventTriggered: "global.shortcuts.event.triggered",
ipcCmdLog: "ipc.cmd.log",
ipcCmdMessage: "ipc.cmd.message",
ipcCmdMessageCallback: "ipc.cmd.message.callback",
ipcEventMessage: "ipc.event.message",
ipcEventMessageCallback: "ipc.event.message.callback",
menuCmdCreate: "menu.cmd.create",
menuCmdDestroy: "menu.cmd.destroy",
menuEventCreated: "menu.event.created",
menuEventDestroyed: "menu.event.destroyed",
menuItemCmdSetChecked: "menu.item.cmd.set.checked",
menuItemCmdSetEnabled: "menu.item.cmd.set.enabled",
menuItemCmdSetLabel: "menu.item.cmd.set.label",
menuItemCmdSetVisible: "menu.item.cmd.set.visible",
menuItemEventCheckedSet: "menu.item.event.checked.set",
menuItemEventClicked: "menu.item.event.clicked",
menuItemEventEnabledSet: "menu.item.event.enabled.set",
menuItemEventLabelSet: "menu.item.event.label.set",
menuItemEventVisibleSet: "menu.item.event.visible.set",
notificationCmdCreate: "notification.cmd.create",
notificationCmdShow: "notification.cmd.show",
notificationEventActioned: "notification.event.actioned",
notificationEventClicked: "notification.event.clicked",
notificationEventClosed: "notification.event.closed",
notificationEventCreated: "notification.event.created",
notificationEventReplied: "notification.event.replied",
notificationEventShown: "notification.event.shown",
powerEventSuspend: "power.event.suspend",
powerEventResume: "power.event.resume",
powerEventOnAC: "power.event.on.ac",
powerEventOnBattery: "power.event.on.battery",
powerEventShutdown: "power.event.shutdown",
powerEventLockScreen: "power.event.lock.screen",
powerEventUnlockScreen: "power.event.unlock.screen",
powerEventUserDidBecomeActive: "power.event.user.did.become.active",
powerEventUserDidResignActive: "power.event.user.did.resign.active",
sessionCmdClearCache: "session.cmd.clear.cache",
sessionCmdFlushStorage: "session.cmd.flush.storage",
sessionCmdLoadExtension: "session.cmd.load.extension",
sessionEventClearedCache: "session.event.cleared.cache",
sessionEventWillDownload: "session.event.will.download",
sessionEventFlushedStorage: "session.event.flushed.storage",
sessionEventLoadedExtension: "session.event.loaded.extension",
subMenuCmdAppend: "sub.menu.cmd.append",
subMenuCmdClosePopup: "sub.menu.cmd.close.popup",
subMenuCmdInsert: "sub.menu.cmd.insert",
subMenuCmdPopup: "sub.menu.cmd.popup",
subMenuEventAppended: "sub.menu.event.appended",
subMenuEventClosedPopup: "sub.menu.event.closed.popup",
subMenuEventInserted: "sub.menu.event.inserted",
subMenuEventPoppedUp: "sub.menu.event.popped.up",
trayCmdCreate: "tray.cmd.create",
trayCmdDestroy: "tray.cmd.destroy",
trayCmdSetImage: "tray.cmd.set.image",
trayCmdPopupContextMenu: "tray.cmd.popup.context.menu",
trayEventClicked: "tray.event.clicked",
trayEventCreated: "tray.event.created",
trayEventDestroyed: "tray.event.destroyed",
trayEventDoubleClicked: "tray.event.double.clicked",
trayEventImageSet: "tray.event.image.set",
trayEventRightClicked: "tray.event.right.clicked",
trayEventContextMenuPoppedUp: "tray.event.context.menu.popped.up",
webContentsEventLogin: "web.contents.event.login",
webContentsEventLoginCallback: "web.contents.event.login.callback",
windowCmdBlur: "window.cmd.blur",
windowCmdCenter: "window.cmd.center",
windowCmdClose: "window.cmd.close",
windowCmdCreate: "window.cmd.create",
windowCmdDestroy: "window.cmd.destroy",
windowCmdFocus: "window.cmd.focus",
windowCmdHide: "window.cmd.hide",
windowCmdLog: "window.cmd.log",
windowCmdMaximize: "window.cmd.maximize",
windowCmdMessage: "window.cmd.message",
windowCmdMessageCallback: "window.cmd.message.callback",
windowCmdMinimize: "window.cmd.minimize",
windowCmdMove: "window.cmd.move",
windowCmdMoveTop: "window.cmd.move.top",
windowCmdResize: "window.cmd.resize",
windowCmdResizeContent: "window.cmd.resize.content",
windowCmdSetAlwaysOnTop: "window.cmd.set.always.on.top",
windowCmdSetBounds: "window.cmd.set.bounds",
windowCmdSetFullScreen: "window.cmd.set.full.screen",
windowCmdRestore: "window.cmd.restore",
windowCmdSetContentProtection: "window.cmd.set.content.protection",
windowCmdShow: "window.cmd.show",
windowCmdUnmaximize: "window.cmd.unmaximize",
windowCmdUpdateCustomOptions: "window.cmd.update.custom.options",
windowCmdWebContentsCloseDevTools: "window.cmd.web.contents.close.dev.tools",
windowCmdWebContentsOpenDevTools: "window.cmd.web.contents.open.dev.tools",
windowCmdWebContentsExecuteJavascript: "window.cmd.web.contents.execute.javascript",
windowEventAlwaysOnTopChanged: "window.event.always.on.top.changed",
windowEventBlur: "window.event.blur",
windowEventClosed: "window.event.closed",
windowEventContentProtectionSet: "window.cmd.web.contents.execute.javascript",
windowEventDidFinishLoad: "window.event.did.finish.load",
windowEventDidGetRedirectRequest: "window.event.did.get.redirect.request",
windowEventEnterFullScreen: "window.event.enter.full.screen",
windowEventWillNavigate: "window.event.will.navigate",
windowEventFocus: "window.event.focus",
windowEventHide: "window.event.hide",
windowEventLeaveFullScreen: "window.event.leave.full.screen",
windowEventMaximize: "window.event.maximize",
windowEventMessage: "window.event.message",
windowEventMessageCallback: "window.event.message.callback",
windowEventMinimize: "window.event.minimize",
windowEventMove: "window.event.move",
windowEventMoved: "window.event.moved",
windowEventMovedTop: "window.event.moved.top",
windowEventReadyToShow: "window.event.ready.to.show",
windowEventResize: "window.event.resize",
windowEventResizeContent: "window.event.resize.content",
windowEventRestore: "window.event.restore",
windowEventShow: "window.event.show",
windowEventUnmaximize: "window.event.unmaximize",
windowEventUnresponsive: "window.event.unresponsive",
windowEventWebContentsExecutedJavaScript: "window.event.web.contents.executed.javascript",
windowEventWillMove: "window.event.will.move",
windowEventUpdatedCustomOptions: "window.event.updated.custom.options"
},
targetIds: {
app: 'app',
dock: 'dock'
}
}
gitextract_i0my_knd/
├── .gitignore
├── LICENSE
├── README.md
├── index.js
├── main.js
├── package.json
└── src/
├── client.js
└── consts.js
SYMBOL INDEX (18 symbols across 2 files)
FILE: index.js
function onReady (line 28) | function onReady () {
function start (line 387) | function start(address = process.argv[2]) {
function menuCreate (line 401) | function menuCreate(menu) {
function menuItemCreate (line 413) | function menuItemCreate(menuItem) {
function menuItemToJSON (line 427) | function menuItemToJSON(menuItem) {
function setMenu (line 437) | function setMenu(rootId) {
function notificationCreate (line 454) | function notificationCreate(json) {
function trayCreate (line 467) | function trayCreate(json) {
function trayPopUpContextMenu (line 479) | function trayPopUpContextMenu(json) {
function windowCreate (line 489) | function windowCreate(json) {
function windowCreateFinish (line 506) | function windowCreateFinish(json) {
function registerCallback (line 651) | function registerCallback(json, k, e, n, c) {
function executeCallback (line 663) | function executeCallback(k, json, args) {
function sessionCreate (line 669) | function sessionCreate(webContents, sessionId) {
function getLastWindow (line 674) | function getLastWindow() {
FILE: src/client.js
class Client (line 7) | class Client {
method init (line 9) | init(addr) {
method write (line 24) | write(targetID, eventName, payload) {
Condensed preview — 8 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (49K chars).
[
{
"path": ".gitignore",
"chars": 39,
"preview": ".idea/\nnode_modules\nnpm-debug.log\ndist\n"
},
{
"path": "LICENSE",
"chars": 1080,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2017 Quentin RENARD\n\nPermission is hereby granted, free of charge, to any person ob"
},
{
"path": "README.md",
"chars": 3192,
"preview": "`astilectron` is an Electron app that provides an API over a TCP socket that allows executing Electron's method as well "
},
{
"path": "index.js",
"chars": 31384,
"preview": "// @ts-check\n'use strict'\n\nconst electron = require('electron')\nconst {app, BrowserWindow, ipcMain, Menu, MenuItem, Tray"
},
{
"path": "main.js",
"chars": 1256,
"preview": "\"use strict\";\n\nconst { app } = require(\"electron\");\nconst { start, getLastWindow, client, consts } = require(\"./index\");"
},
{
"path": "package.json",
"chars": 610,
"preview": "{\n \"name\": \"astilectron\",\n \"version\": \"0.57.0\",\n \"description\": \"Electron-based cross-language application framework\""
},
{
"path": "src/client.js",
"chars": 905,
"preview": "\"use strict\";\n\nconst net = require(\"net\");\nconst url = require(\"url\");\n\n// Client can read/write messages from a TCP ser"
},
{
"path": "src/consts.js",
"chars": 8787,
"preview": "'use strict'\n\nmodule.exports = {\n callbackNames: {\n webContentsLogin: \"web.contents.login\",\n webContent"
}
]
About this extraction
This page contains the full source code of the asticode/astilectron GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 8 files (46.1 KB), approximately 10.6k tokens, and a symbol index with 18 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.