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 ` /main.js ` ## 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' } }