parent
8287f68209
commit
ee4b672de3
@ -0,0 +1,16 @@ |
|||||||
|
{ |
||||||
|
"env": { |
||||||
|
"browser": true, |
||||||
|
"es6": true, |
||||||
|
"node": true |
||||||
|
}, |
||||||
|
"extends": [ |
||||||
|
"eslint:recommended", |
||||||
|
"plugin:@typescript-eslint/eslint-recommended", |
||||||
|
"plugin:@typescript-eslint/recommended", |
||||||
|
"plugin:import/recommended", |
||||||
|
"plugin:import/electron", |
||||||
|
"plugin:import/typescript" |
||||||
|
], |
||||||
|
"parser": "@typescript-eslint/parser" |
||||||
|
} |
@ -0,0 +1,92 @@ |
|||||||
|
# Logs |
||||||
|
logs |
||||||
|
*.log |
||||||
|
npm-debug.log* |
||||||
|
yarn-debug.log* |
||||||
|
yarn-error.log* |
||||||
|
lerna-debug.log* |
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html) |
||||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json |
||||||
|
|
||||||
|
# Runtime data |
||||||
|
pids |
||||||
|
*.pid |
||||||
|
*.seed |
||||||
|
*.pid.lock |
||||||
|
.DS_Store |
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover |
||||||
|
lib-cov |
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul |
||||||
|
coverage |
||||||
|
*.lcov |
||||||
|
|
||||||
|
# nyc test coverage |
||||||
|
.nyc_output |
||||||
|
|
||||||
|
# node-waf configuration |
||||||
|
.lock-wscript |
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html) |
||||||
|
build/Release |
||||||
|
|
||||||
|
# Dependency directories |
||||||
|
node_modules/ |
||||||
|
jspm_packages/ |
||||||
|
|
||||||
|
# TypeScript v1 declaration files |
||||||
|
typings/ |
||||||
|
|
||||||
|
# TypeScript cache |
||||||
|
*.tsbuildinfo |
||||||
|
|
||||||
|
# Optional npm cache directory |
||||||
|
.npm |
||||||
|
|
||||||
|
# Optional eslint cache |
||||||
|
.eslintcache |
||||||
|
|
||||||
|
# Optional REPL history |
||||||
|
.node_repl_history |
||||||
|
|
||||||
|
# Output of 'npm pack' |
||||||
|
*.tgz |
||||||
|
|
||||||
|
# Yarn Integrity file |
||||||
|
.yarn-integrity |
||||||
|
|
||||||
|
# dotenv environment variables file |
||||||
|
.env |
||||||
|
.env.test |
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/) |
||||||
|
.cache |
||||||
|
|
||||||
|
# next.js build output |
||||||
|
.next |
||||||
|
|
||||||
|
# nuxt.js build output |
||||||
|
.nuxt |
||||||
|
|
||||||
|
# vuepress build output |
||||||
|
.vuepress/dist |
||||||
|
|
||||||
|
# Serverless directories |
||||||
|
.serverless/ |
||||||
|
|
||||||
|
# FuseBox cache |
||||||
|
.fusebox/ |
||||||
|
|
||||||
|
# DynamoDB Local files |
||||||
|
.dynamodb/ |
||||||
|
|
||||||
|
# Webpack |
||||||
|
.webpack/ |
||||||
|
|
||||||
|
# Vite |
||||||
|
.vite/ |
||||||
|
|
||||||
|
# Electron-Forge |
||||||
|
out/ |
@ -0,0 +1,35 @@ |
|||||||
|
import type { ForgeConfig } from '@electron-forge/shared-types'; |
||||||
|
import { MakerSquirrel } from '@electron-forge/maker-squirrel'; |
||||||
|
import { MakerZIP } from '@electron-forge/maker-zip'; |
||||||
|
import { MakerDeb } from '@electron-forge/maker-deb'; |
||||||
|
import { MakerRpm } from '@electron-forge/maker-rpm'; |
||||||
|
import { WebpackPlugin } from '@electron-forge/plugin-webpack'; |
||||||
|
|
||||||
|
import { mainConfig } from './webpack.main.config'; |
||||||
|
import { rendererConfig } from './webpack.renderer.config'; |
||||||
|
|
||||||
|
const config: ForgeConfig = { |
||||||
|
packagerConfig: {}, |
||||||
|
rebuildConfig: {}, |
||||||
|
makers: [new MakerSquirrel({}), new MakerZIP({}, ['darwin']), new MakerRpm({}), new MakerDeb({})], |
||||||
|
plugins: [ |
||||||
|
new WebpackPlugin({ |
||||||
|
mainConfig, |
||||||
|
renderer: { |
||||||
|
config: rendererConfig, |
||||||
|
entryPoints: [ |
||||||
|
{ |
||||||
|
html: './src/index.html', |
||||||
|
js: './src/renderer.ts', |
||||||
|
name: 'main_window', |
||||||
|
preload: { |
||||||
|
js: './src/preload.ts', |
||||||
|
}, |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
}), |
||||||
|
], |
||||||
|
}; |
||||||
|
|
||||||
|
export default config; |
@ -0,0 +1,44 @@ |
|||||||
|
{ |
||||||
|
"name": "1test", |
||||||
|
"productName": "1test", |
||||||
|
"version": "1.0.0", |
||||||
|
"description": "My Electron application description", |
||||||
|
"main": ".webpack/main", |
||||||
|
"scripts": { |
||||||
|
"start": "electron-forge start", |
||||||
|
"package": "electron-forge package", |
||||||
|
"make": "electron-forge make", |
||||||
|
"publish": "electron-forge publish", |
||||||
|
"lint": "eslint --ext .ts,.tsx ." |
||||||
|
}, |
||||||
|
"keywords": [], |
||||||
|
"author": { |
||||||
|
"name": "filip mertens", |
||||||
|
"email": "filip.mertens@ethereum.org" |
||||||
|
}, |
||||||
|
"license": "MIT", |
||||||
|
"devDependencies": { |
||||||
|
"@electron-forge/cli": "^6.1.1", |
||||||
|
"@electron-forge/maker-deb": "^6.1.1", |
||||||
|
"@electron-forge/maker-rpm": "^6.1.1", |
||||||
|
"@electron-forge/maker-squirrel": "^6.1.1", |
||||||
|
"@electron-forge/maker-zip": "^6.1.1", |
||||||
|
"@electron-forge/plugin-webpack": "^6.1.1", |
||||||
|
"@typescript-eslint/eslint-plugin": "^5.0.0", |
||||||
|
"@typescript-eslint/parser": "^5.0.0", |
||||||
|
"@vercel/webpack-asset-relocator-loader": "1.7.3", |
||||||
|
"css-loader": "^6.0.0", |
||||||
|
"electron": "25.0.0", |
||||||
|
"eslint": "^8.0.1", |
||||||
|
"eslint-plugin-import": "^2.25.0", |
||||||
|
"fork-ts-checker-webpack-plugin": "^7.2.13", |
||||||
|
"node-loader": "^2.0.0", |
||||||
|
"style-loader": "^3.0.0", |
||||||
|
"ts-loader": "^9.2.2", |
||||||
|
"ts-node": "^10.0.0", |
||||||
|
"typescript": "~4.5.4" |
||||||
|
}, |
||||||
|
"dependencies": { |
||||||
|
"electron-squirrel-startup": "^1.0.0" |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,38 @@ |
|||||||
|
import { ClientConnector, connectClient, applyApi, Client, PluginClient } from '@remixproject/plugin' |
||||||
|
import type { Message, Api, ApiMap } from '@remixproject/plugin-utils' |
||||||
|
import { IRemixApi } from '@remixproject/plugin-api' |
||||||
|
import { ipcMain } from 'electron' |
||||||
|
import { mainWindow } from '..' |
||||||
|
|
||||||
|
export class ElectronPluginClientConnector implements ClientConnector { |
||||||
|
|
||||||
|
constructor(public IPCName: string) {
|
||||||
|
console.log('ElectronPluginClientConnector constructor', IPCName) |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** Send a message to the engine */ |
||||||
|
send(message: Partial<Message>) { |
||||||
|
console.log('ElectronPluginConnector send', message) |
||||||
|
mainWindow.webContents.send(this.IPCName + ':send', message) |
||||||
|
} |
||||||
|
|
||||||
|
/** Listen to message from the engine */ |
||||||
|
on(cb: (message: Partial<Message>) => void) { |
||||||
|
console.log('ElectronPluginConnector on', cb) |
||||||
|
ipcMain.on(this.IPCName + ':on', (event, message) => { |
||||||
|
console.log('ElectronPluginConnector on message received', message) |
||||||
|
cb(message) |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export const createClient = < |
||||||
|
P extends Api, |
||||||
|
App extends ApiMap = Readonly<IRemixApi> |
||||||
|
>(client: PluginClient<P, App> = new PluginClient(), IPCName: string): Client<P, App> => { |
||||||
|
const c = client as any |
||||||
|
connectClient(new ElectronPluginClientConnector(IPCName), c) |
||||||
|
applyApi(c) |
||||||
|
return c |
||||||
|
} |
@ -0,0 +1,18 @@ |
|||||||
|
import { Engine, PluginManager, Plugin } from '@remixproject/engine'; |
||||||
|
import { ipcMain, ipcRenderer } from 'electron'; |
||||||
|
import { FSPlugin } from './fsPlugin'; |
||||||
|
import { GitPlugin } from './gitPlugin'; |
||||||
|
|
||||||
|
const engine = new Engine() |
||||||
|
const appManager = new PluginManager() |
||||||
|
const fsPlugin = new FSPlugin() |
||||||
|
const gitPlugin = new GitPlugin() |
||||||
|
engine.register(appManager) |
||||||
|
engine.register(fsPlugin) |
||||||
|
engine.register(gitPlugin) |
||||||
|
//appManager.activatePlugin('fs')
|
||||||
|
|
||||||
|
ipcMain.on('engine:activatePlugin', (event, arg) => { |
||||||
|
console.log('engine:activatePlugin', arg) |
||||||
|
appManager.activatePlugin(arg) |
||||||
|
}) |
@ -0,0 +1,72 @@ |
|||||||
|
import { PluginClient } from "@remixproject/plugin"; |
||||||
|
import { createClient } from "./electronPluginClient" |
||||||
|
import { Engine, PluginManager, Plugin } from '@remixproject/engine'; |
||||||
|
import fs from 'fs' |
||||||
|
import { existsSync } from "fs-extra"; |
||||||
|
|
||||||
|
const profile = { |
||||||
|
displayName: 'fs', |
||||||
|
name: 'fs', |
||||||
|
description: 'fs', |
||||||
|
} |
||||||
|
|
||||||
|
export class FSPlugin extends Plugin { |
||||||
|
client: PluginClient |
||||||
|
constructor(){ |
||||||
|
super(profile) |
||||||
|
} |
||||||
|
|
||||||
|
onActivation(): void { |
||||||
|
console.log('fsPlugin onActivation') |
||||||
|
this.client = new FSPluginClient() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class FSPluginClient extends PluginClient { |
||||||
|
constructor(){ |
||||||
|
super() |
||||||
|
this.methods = ['readdir', 'readFile', 'writeFile', 'mkdir', 'rmdir', 'unlink', 'rename', 'stat', 'exists'] |
||||||
|
createClient(this, 'fs') |
||||||
|
this.onload(() => { |
||||||
|
console.log('fsPluginClient onload') |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
async readdir(path: string): Promise<string[]> { |
||||||
|
// call node fs.readdir
|
||||||
|
return fs.readdirSync(path) |
||||||
|
} |
||||||
|
|
||||||
|
async readFile(path: string): Promise<string> { |
||||||
|
return fs.readFileSync(path, 'utf8') |
||||||
|
} |
||||||
|
|
||||||
|
async writeFile(path: string, content: string): Promise<void> { |
||||||
|
return fs.writeFileSync(path, content, 'utf8') |
||||||
|
} |
||||||
|
|
||||||
|
async mkdir(path: string): Promise<void> { |
||||||
|
return fs.mkdirSync(path) |
||||||
|
} |
||||||
|
|
||||||
|
async rmdir(path: string): Promise<void> { |
||||||
|
return fs.rmdirSync(path) |
||||||
|
} |
||||||
|
|
||||||
|
async unlink(path: string): Promise<void> { |
||||||
|
return fs.unlinkSync(path) |
||||||
|
} |
||||||
|
|
||||||
|
async rename(oldPath: string, newPath: string): Promise<void> { |
||||||
|
return fs.renameSync(oldPath, newPath) |
||||||
|
} |
||||||
|
|
||||||
|
async stat(path: string): Promise<fs.Stats> { |
||||||
|
return fs.statSync(path) |
||||||
|
} |
||||||
|
|
||||||
|
async exists(path: string): Promise<boolean> { |
||||||
|
return existsSync(path) |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,46 @@ |
|||||||
|
import { Plugin } from "@remixproject/engine"; |
||||||
|
import { PluginClient } from "@remixproject/plugin"; |
||||||
|
import { Profile } from "@remixproject/plugin-utils"; |
||||||
|
import { spawn } from "child_process"; |
||||||
|
import { createClient } from "./electronPluginClient"; |
||||||
|
|
||||||
|
const profile: Profile = { |
||||||
|
name: 'git', |
||||||
|
displayName: 'Git', |
||||||
|
description: 'Git plugin', |
||||||
|
} |
||||||
|
|
||||||
|
export class GitPlugin extends Plugin { |
||||||
|
client: PluginClient |
||||||
|
constructor() { |
||||||
|
super(profile) |
||||||
|
} |
||||||
|
|
||||||
|
onActivation(): void { |
||||||
|
console.log('GitPlugin onActivation') |
||||||
|
this.client = new GitPluginClient() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class GitPluginClient extends PluginClient { |
||||||
|
constructor() { |
||||||
|
super() |
||||||
|
this.methods = ['log', 'status', 'add', 'commit', 'push', 'pull', 'clone', 'checkout', 'branch', 'merge', 'reset', 'revert', 'diff', 'stash', 'apply', 'cherryPick', 'rebase', 'tag', 'fetch', 'remote', 'config', 'show', 'init', 'help', 'version'] |
||||||
|
createClient(this, 'git') |
||||||
|
this.onload(() => { |
||||||
|
console.log('GitPluginClient onload') |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
async log(path: string): Promise<string> { |
||||||
|
const log = spawn('git', ['log'], { cwd: path }) |
||||||
|
|
||||||
|
return new Promise((resolve, reject) => { |
||||||
|
log.stdout.on('data', (data) => { |
||||||
|
resolve(data.toString()) |
||||||
|
}) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
@ -0,0 +1,13 @@ |
|||||||
|
export interface IElectronAPI { |
||||||
|
activatePlugin: (name: string) => void |
||||||
|
receiveFromFS: (cb: any) => void |
||||||
|
sendToFS: (message: Partial<Message>) => void |
||||||
|
receiveFromGit: (cb: any) => void |
||||||
|
sendToGit: (message: Partial<Message>) => void |
||||||
|
} |
||||||
|
|
||||||
|
declare global { |
||||||
|
interface Window { |
||||||
|
api: IElectronAPI |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,7 @@ |
|||||||
|
body { |
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, |
||||||
|
Arial, sans-serif; |
||||||
|
margin: auto; |
||||||
|
max-width: 38rem; |
||||||
|
padding: 2rem; |
||||||
|
} |
@ -0,0 +1,12 @@ |
|||||||
|
<!DOCTYPE html> |
||||||
|
<html> |
||||||
|
<head> |
||||||
|
<meta charset="UTF-8" /> |
||||||
|
<title>Hello World!</title> |
||||||
|
|
||||||
|
</head> |
||||||
|
<body> |
||||||
|
<h1>💖 Hello World!</h1> |
||||||
|
<p>Welcome to your Electron application.</p> |
||||||
|
</body> |
||||||
|
</html> |
@ -0,0 +1,58 @@ |
|||||||
|
import { app, BrowserWindow } from 'electron'; |
||||||
|
import path from 'path'; |
||||||
|
// This allows TypeScript to pick up the magic constants that's auto-generated by Forge's Webpack
|
||||||
|
// plugin that tells the Electron app where to look for the Webpack-bundled app code (depending on
|
||||||
|
// whether you're running in development or production).
|
||||||
|
declare const MAIN_WINDOW_WEBPACK_ENTRY: string; |
||||||
|
declare const MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY: string; |
||||||
|
|
||||||
|
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
|
||||||
|
if (require('electron-squirrel-startup')) { |
||||||
|
app.quit(); |
||||||
|
} |
||||||
|
export let mainWindow: BrowserWindow; |
||||||
|
const createWindow = (): void => { |
||||||
|
// Create the browser window.
|
||||||
|
mainWindow = new BrowserWindow({ |
||||||
|
height: 800, |
||||||
|
width: 600, |
||||||
|
webPreferences: { |
||||||
|
preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
// and load the index.html of the app.
|
||||||
|
mainWindow.loadURL(MAIN_WINDOW_WEBPACK_ENTRY); |
||||||
|
|
||||||
|
//mainWindow.maximize();
|
||||||
|
// Open the DevTools.
|
||||||
|
mainWindow.webContents.openDevTools(); |
||||||
|
require('./electron/engine') |
||||||
|
}; |
||||||
|
|
||||||
|
// This method will be called when Electron has finished
|
||||||
|
// initialization and is ready to create browser windows.
|
||||||
|
// Some APIs can only be used after this event occurs.
|
||||||
|
app.on('ready', 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', () => { |
||||||
|
if (process.platform !== 'darwin') { |
||||||
|
app.quit(); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
app.on('activate', () => { |
||||||
|
// On OS X 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(); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
// 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 import them here.
|
||||||
|
|
||||||
|
|
@ -0,0 +1,16 @@ |
|||||||
|
|
||||||
|
import { Message } from '@remixproject/plugin-utils' |
||||||
|
import { contextBridge, ipcRenderer } from 'electron' |
||||||
|
|
||||||
|
console.log('preload.ts') |
||||||
|
|
||||||
|
contextBridge.exposeInMainWorld('api', { |
||||||
|
activatePlugin: (name: string) => { |
||||||
|
console.log('activatePlugin', name) |
||||||
|
ipcRenderer.send('engine:activatePlugin', name) |
||||||
|
}, |
||||||
|
receiveFromFS: (cb:any) => ipcRenderer.on('fs:send', cb), |
||||||
|
sendToFS: (message: Partial<Message>) => ipcRenderer.send('fs:on', message), |
||||||
|
receiveFromGit: (cb:any) => ipcRenderer.on('git:send', cb), |
||||||
|
sendToGit: (message: Partial<Message>) => ipcRenderer.send('git:on', message) |
||||||
|
}) |
@ -0,0 +1,157 @@ |
|||||||
|
import type { ExternalProfile, Profile, Message, PluginOptions } from '@remixproject/plugin-utils' |
||||||
|
import { Plugin } from '@remixproject/engine'; |
||||||
|
|
||||||
|
|
||||||
|
export interface PluginConnectorOptions extends PluginOptions { |
||||||
|
engine?:string |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
export abstract class ElectronPluginConnector extends Plugin { |
||||||
|
protected loaded: boolean |
||||||
|
protected id = 0 |
||||||
|
protected pendingRequest: Record<number, (result: any, error: Error | string) => void> = {} |
||||||
|
protected options: PluginConnectorOptions |
||||||
|
profile: Profile |
||||||
|
constructor(profile: Profile) { |
||||||
|
super(profile) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Send a message to the external plugin |
||||||
|
* @param message the message passed to the plugin |
||||||
|
*/ |
||||||
|
protected abstract send(message: Partial<Message>): void |
||||||
|
/** |
||||||
|
* Open connection with the plugin |
||||||
|
* @param url The transformed url the plugin should connect to |
||||||
|
*/ |
||||||
|
protected abstract connect(name: string): any | Promise<any> |
||||||
|
/** Close connection with the plugin */ |
||||||
|
protected abstract disconnect(): any | Promise<any> |
||||||
|
|
||||||
|
async activate() { |
||||||
|
await this.connect(this.profile.name) |
||||||
|
return super.activate() |
||||||
|
} |
||||||
|
|
||||||
|
async deactivate() { |
||||||
|
this.loaded = false |
||||||
|
await this.disconnect() |
||||||
|
return super.deactivate() |
||||||
|
} |
||||||
|
|
||||||
|
/** Set options for an external plugin */ |
||||||
|
setOptions(options: Partial<PluginConnectorOptions> = {}) { |
||||||
|
super.setOptions(options) |
||||||
|
} |
||||||
|
|
||||||
|
/** Call a method from this plugin */ |
||||||
|
protected callPluginMethod(key: string, payload: any[] = []): Promise<any> { |
||||||
|
const action = 'request' |
||||||
|
const id = this.id++ |
||||||
|
const requestInfo = this.currentRequest |
||||||
|
const name = this.name |
||||||
|
const promise = new Promise((res, rej) => { |
||||||
|
this.pendingRequest[id] = (result: any[], error: Error | string) => error ? rej (error) : res(result) |
||||||
|
}) |
||||||
|
this.send({ id, action, key, payload, requestInfo, name }) |
||||||
|
return promise |
||||||
|
} |
||||||
|
|
||||||
|
/** Perform handshake with the client if not loaded yet */ |
||||||
|
protected async handshake() { |
||||||
|
console.log('ElectronPluginConnector handshake', this.loaded) |
||||||
|
if (!this.loaded) { |
||||||
|
this.loaded = true |
||||||
|
let methods: string[]; |
||||||
|
try { |
||||||
|
console.log('ElectronPluginConnector handshake calling plugin method') |
||||||
|
methods = await this.callPluginMethod('handshake', [this.profile.name, this.options?.engine]) |
||||||
|
console.log('ElectronPluginConnector handshake methods', methods) |
||||||
|
} catch (err) { |
||||||
|
console.error('ElectronPluginConnector handshake error', err) |
||||||
|
this.loaded = false |
||||||
|
throw err; |
||||||
|
} |
||||||
|
if (methods) { |
||||||
|
this.profile.methods = methods |
||||||
|
this.call('manager', 'updateProfile', this.profile) |
||||||
|
} |
||||||
|
} else { |
||||||
|
// If there is a broken connection we want send back the handshake to the plugin client
|
||||||
|
console.log('ElectronPluginConnector handshake already loaded') |
||||||
|
return this.callPluginMethod('handshake', [this.profile.name, this.options?.engine]) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* React when a message comes from client |
||||||
|
* @param message The message sent by the client |
||||||
|
*/ |
||||||
|
protected async getMessage(message: Message) { |
||||||
|
console.log('ElectronPluginConnector getMessage', message) |
||||||
|
// Check for handshake request from the client
|
||||||
|
if (message.action === 'request' && message.key === 'handshake') { |
||||||
|
return this.handshake() |
||||||
|
} |
||||||
|
|
||||||
|
switch (message.action) { |
||||||
|
// Start listening on an event
|
||||||
|
case 'on': |
||||||
|
case 'listen': { |
||||||
|
const { name, key } = message |
||||||
|
const action = 'notification' |
||||||
|
this.on(name, key, (...payload: any[]) => this.send({ action, name, key, payload })) |
||||||
|
break |
||||||
|
} |
||||||
|
case 'off': { |
||||||
|
const { name, key } = message |
||||||
|
this.off(name, key) |
||||||
|
break |
||||||
|
} |
||||||
|
case 'once': { |
||||||
|
const { name, key } = message |
||||||
|
const action = 'notification' |
||||||
|
this.once(name, key, (...payload: any) => this.send({ action, name, key, payload })) |
||||||
|
break |
||||||
|
} |
||||||
|
// Emit an event
|
||||||
|
case 'emit': |
||||||
|
case 'notification': { |
||||||
|
if (!message.payload) break |
||||||
|
this.emit(message.key, ...message.payload) |
||||||
|
break |
||||||
|
} |
||||||
|
// Call a method
|
||||||
|
case 'call': |
||||||
|
case 'request': { |
||||||
|
const action = 'response' |
||||||
|
try { |
||||||
|
const payload = await this.call(message.name, message.key, ...message.payload) |
||||||
|
const error: any = undefined |
||||||
|
this.send({ ...message, action, payload, error }) |
||||||
|
} catch (err) { |
||||||
|
const payload: any = undefined |
||||||
|
const error = err.message || err |
||||||
|
this.send({ ...message, action, payload, error }) |
||||||
|
} |
||||||
|
break |
||||||
|
} |
||||||
|
case 'cancel': { |
||||||
|
const payload = this.cancel(message.name, message.key) |
||||||
|
break; |
||||||
|
} |
||||||
|
// Return result from exposed method
|
||||||
|
case 'response': { |
||||||
|
const { id, payload, error } = message |
||||||
|
this.pendingRequest[id](payload, error) |
||||||
|
delete this.pendingRequest[id] |
||||||
|
break |
||||||
|
} |
||||||
|
default: { |
||||||
|
throw new Error('Message should be a notification, request or response') |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,36 @@ |
|||||||
|
import { Engine, PluginManager, Plugin, PluginConnector } from '@remixproject/engine'; |
||||||
|
import { Message, Profile } from '@remixproject/plugin-utils'; |
||||||
|
import { ElectronPluginConnector } from './electronPluginConnector'; |
||||||
|
|
||||||
|
export class fsPlugin extends ElectronPluginConnector { |
||||||
|
constructor(){ |
||||||
|
super({ |
||||||
|
displayName: 'fs', |
||||||
|
name: 'fs', |
||||||
|
description: 'fs', |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
onActivation(): void { |
||||||
|
console.log('fsPlugin onActivation') |
||||||
|
//window.api.activatePlugin('fs')
|
||||||
|
} |
||||||
|
|
||||||
|
protected connect(name: string) { |
||||||
|
console.log('fsPlugin connect', name) |
||||||
|
window.api.activatePlugin(name) |
||||||
|
window.api.receiveFromFS((event: any, message: any) => { |
||||||
|
console.log('fsPlugin fsClientSend message received', message) |
||||||
|
this.getMessage(message) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
protected send(message: Partial<Message>): void { |
||||||
|
console.log('fsPlugin send', message) |
||||||
|
window.api.sendToFS(message) |
||||||
|
} |
||||||
|
|
||||||
|
protected disconnect() { |
||||||
|
console.log('fsPlugin disconnect') |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,36 @@ |
|||||||
|
import { Engine, PluginManager, Plugin, PluginConnector } from '@remixproject/engine'; |
||||||
|
import { Message, Profile } from '@remixproject/plugin-utils'; |
||||||
|
import { ElectronPluginConnector } from './electronPluginConnector'; |
||||||
|
|
||||||
|
export class gitPlugin extends ElectronPluginConnector { |
||||||
|
constructor(){ |
||||||
|
super({ |
||||||
|
displayName: 'git', |
||||||
|
name: 'git', |
||||||
|
description: 'git', |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
onActivation(): void { |
||||||
|
console.log('git onActivation') |
||||||
|
//window.api.activatePlugin('fs')
|
||||||
|
} |
||||||
|
|
||||||
|
protected connect(name: string) { |
||||||
|
console.log('git connect', name) |
||||||
|
window.api.activatePlugin(name) |
||||||
|
window.api.receiveFromGit((event: any, message: any) => { |
||||||
|
console.log('git fsClientSend message received', message) |
||||||
|
this.getMessage(message) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
protected send(message: Partial<Message>): void { |
||||||
|
console.log('git send', message) |
||||||
|
window.api.sendToGit(message) |
||||||
|
} |
||||||
|
|
||||||
|
protected disconnect() { |
||||||
|
console.log('git disconnect') |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,25 @@ |
|||||||
|
|
||||||
|
import { Engine, PluginManager, Plugin, PluginConnector } from '@remixproject/engine'; |
||||||
|
import { Profile } from '@remixproject/plugin-utils'; |
||||||
|
import { fsPlugin } from './remix/lib/fsPlugin'; |
||||||
|
import { gitPlugin } from './remix/lib/gitPlugin'; |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const engine = new Engine() |
||||||
|
const appManager = new PluginManager() |
||||||
|
const fs = new fsPlugin() |
||||||
|
const git = new gitPlugin() |
||||||
|
engine.register(appManager) |
||||||
|
engine.register(fs) |
||||||
|
engine.register(git) |
||||||
|
//appManager.activatePlugin('fs')
|
||||||
|
appManager.activatePlugin('git') |
||||||
|
|
||||||
|
setTimeout(async () => { |
||||||
|
//const files = await appManager.call('fs', 'readdir', './')
|
||||||
|
//console.log('files', files)
|
||||||
|
const log = await appManager.call('git', 'log', './') |
||||||
|
console.log('log', log) |
||||||
|
}, 5000) |
||||||
|
|
@ -0,0 +1,19 @@ |
|||||||
|
{ |
||||||
|
"compilerOptions": { |
||||||
|
"target": "ES6", |
||||||
|
"allowJs": true, |
||||||
|
"module": "commonjs", |
||||||
|
"skipLibCheck": true, |
||||||
|
"esModuleInterop": true, |
||||||
|
"noImplicitAny": true, |
||||||
|
"sourceMap": true, |
||||||
|
"baseUrl": ".", |
||||||
|
"outDir": "dist", |
||||||
|
"moduleResolution": "node", |
||||||
|
"resolveJsonModule": true, |
||||||
|
"paths": { |
||||||
|
"*": ["node_modules/*"] |
||||||
|
} |
||||||
|
}, |
||||||
|
"include": ["src/**/*"] |
||||||
|
} |
@ -0,0 +1,18 @@ |
|||||||
|
import type { Configuration } from 'webpack'; |
||||||
|
|
||||||
|
import { rules } from './webpack.rules'; |
||||||
|
|
||||||
|
export const mainConfig: Configuration = { |
||||||
|
/** |
||||||
|
* This is the main entry point for your application, it's the first file |
||||||
|
* that runs in the main process. |
||||||
|
*/ |
||||||
|
entry: './src/index.ts', |
||||||
|
// Put your normal webpack config below here
|
||||||
|
module: { |
||||||
|
rules, |
||||||
|
}, |
||||||
|
resolve: { |
||||||
|
extensions: ['.js', '.ts', '.jsx', '.tsx', '.css', '.json'], |
||||||
|
}, |
||||||
|
}; |
@ -0,0 +1,10 @@ |
|||||||
|
import type IForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin'; |
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
const ForkTsCheckerWebpackPlugin: typeof IForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); |
||||||
|
|
||||||
|
export const plugins = [ |
||||||
|
new ForkTsCheckerWebpackPlugin({ |
||||||
|
logger: 'webpack-infrastructure', |
||||||
|
}), |
||||||
|
]; |
@ -0,0 +1,19 @@ |
|||||||
|
import type { Configuration } from 'webpack'; |
||||||
|
|
||||||
|
import { rules } from './webpack.rules'; |
||||||
|
import { plugins } from './webpack.plugins'; |
||||||
|
|
||||||
|
rules.push({ |
||||||
|
test: /\.css$/, |
||||||
|
use: [{ loader: 'style-loader' }, { loader: 'css-loader' }], |
||||||
|
}); |
||||||
|
|
||||||
|
export const rendererConfig: Configuration = { |
||||||
|
module: { |
||||||
|
rules, |
||||||
|
}, |
||||||
|
plugins, |
||||||
|
resolve: { |
||||||
|
extensions: ['.js', '.ts', '.jsx', '.tsx', '.css'], |
||||||
|
}, |
||||||
|
}; |
@ -0,0 +1,31 @@ |
|||||||
|
import type { ModuleOptions } from 'webpack'; |
||||||
|
|
||||||
|
export const rules: Required<ModuleOptions>['rules'] = [ |
||||||
|
// Add support for native node modules
|
||||||
|
{ |
||||||
|
// We're specifying native_modules in the test because the asset relocator loader generates a
|
||||||
|
// "fake" .node file which is really a cjs file.
|
||||||
|
test: /native_modules[/\\].+\.node$/, |
||||||
|
use: 'node-loader', |
||||||
|
}, |
||||||
|
{ |
||||||
|
test: /[/\\]node_modules[/\\].+\.(m?js|node)$/, |
||||||
|
parser: { amd: false }, |
||||||
|
use: { |
||||||
|
loader: '@vercel/webpack-asset-relocator-loader', |
||||||
|
options: { |
||||||
|
outputAssetBase: 'native_modules', |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
test: /\.tsx?$/, |
||||||
|
exclude: /(node_modules|\.webpack)/, |
||||||
|
use: { |
||||||
|
loader: 'ts-loader', |
||||||
|
options: { |
||||||
|
transpileOnly: true, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
]; |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,14 @@ |
|||||||
|
import { Engine, PluginManager, Plugin } from '@remixproject/engine'; |
||||||
|
import { ipcMain, ipcRenderer } from 'electron'; |
||||||
|
import { fsPlugin } from './fsPlugin'; |
||||||
|
|
||||||
|
const engine = new Engine() |
||||||
|
const appManager = new PluginManager() |
||||||
|
const plugin = new fsPlugin() |
||||||
|
engine.register(appManager) |
||||||
|
engine.register(plugin) |
||||||
|
//appManager.activatePlugin('fs')
|
||||||
|
|
||||||
|
ipcMain.on('engine:activatePlugin', (event, arg) => { |
||||||
|
appManager.activatePlugin(arg) |
||||||
|
}) |
@ -0,0 +1,70 @@ |
|||||||
|
import { PluginClient } from "@remixproject/plugin"; |
||||||
|
import { createClient } from "./electronPluginClient" |
||||||
|
import { Engine, PluginManager, Plugin } from '@remixproject/engine'; |
||||||
|
import fs from 'fs' |
||||||
|
import { existsSync } from "fs-extra"; |
||||||
|
|
||||||
|
const profile = { |
||||||
|
displayName: 'fs', |
||||||
|
name: 'fs', |
||||||
|
description: 'fs', |
||||||
|
} |
||||||
|
|
||||||
|
export class fsPlugin extends Plugin { |
||||||
|
client: PluginClient |
||||||
|
constructor(){ |
||||||
|
super(profile) |
||||||
|
} |
||||||
|
|
||||||
|
onActivation(): void { |
||||||
|
console.log('fsPlugin onActivation') |
||||||
|
this.client = new fsPluginClient() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class fsPluginClient extends PluginClient { |
||||||
|
constructor(){ |
||||||
|
super() |
||||||
|
this.methods = ['readdir', 'readFile', 'writeFile', 'mkdir', 'rmdir', 'unlink', 'rename', 'stat', 'exists'] |
||||||
|
createClient(this) |
||||||
|
this.onload() |
||||||
|
} |
||||||
|
|
||||||
|
async readdir(path: string): Promise<string[]> { |
||||||
|
// call node fs.readdir
|
||||||
|
return fs.readdirSync(path) |
||||||
|
} |
||||||
|
|
||||||
|
async readFile(path: string): Promise<string> { |
||||||
|
return fs.readFileSync(path, 'utf8') |
||||||
|
} |
||||||
|
|
||||||
|
async writeFile(path: string, content: string): Promise<void> { |
||||||
|
return fs.writeFileSync(path, content, 'utf8') |
||||||
|
} |
||||||
|
|
||||||
|
async mkdir(path: string): Promise<void> { |
||||||
|
return fs.mkdirSync(path) |
||||||
|
} |
||||||
|
|
||||||
|
async rmdir(path: string): Promise<void> { |
||||||
|
return fs.rmdirSync(path) |
||||||
|
} |
||||||
|
|
||||||
|
async unlink(path: string): Promise<void> { |
||||||
|
return fs.unlinkSync(path) |
||||||
|
} |
||||||
|
|
||||||
|
async rename(oldPath: string, newPath: string): Promise<void> { |
||||||
|
return fs.renameSync(oldPath, newPath) |
||||||
|
} |
||||||
|
|
||||||
|
async stat(path: string): Promise<fs.Stats> { |
||||||
|
return fs.statSync(path) |
||||||
|
} |
||||||
|
|
||||||
|
async exists(path: string): Promise<boolean> { |
||||||
|
return existsSync(path) |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,12 @@ |
|||||||
|
|
||||||
|
import { Message } from '@remixproject/plugin-utils' |
||||||
|
import { contextBridge, ipcRenderer } from 'electron' |
||||||
|
|
||||||
|
console.log('preload.ts') |
||||||
|
|
||||||
|
contextBridge.exposeInMainWorld('api', { |
||||||
|
activatePlugin: (name: string) => { |
||||||
|
console.log('activatePlugin', name) |
||||||
|
ipcRenderer.send('engine:activatePlugin', name) |
||||||
|
} |
||||||
|
}) |
Loading…
Reference in new issue