-
-
Notifications
You must be signed in to change notification settings - Fork 81
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
c2d58c8
commit 2000d57
Showing
13 changed files
with
439 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
3 changes: 3 additions & 0 deletions
3
packages/electron-chrome-extensions/script/native-messaging-host/.gitignore
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
crxtesthost | ||
crxtesthost.blob | ||
sea-config.json |
82 changes: 82 additions & 0 deletions
82
packages/electron-chrome-extensions/script/native-messaging-host/build.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
#!/usr/bin/env node | ||
|
||
const { promises: fs } = require('node:fs') | ||
const path = require('node:path') | ||
const os = require('node:os') | ||
const util = require('node:util') | ||
const cp = require('node:child_process') | ||
const exec = util.promisify(cp.exec) | ||
|
||
const basePath = 'script/native-messaging-host/' | ||
const outDir = path.join(__dirname, '.') | ||
const exeName = `crxtesthost${process.platform === 'win32' ? '.exe' : ''}` | ||
const seaBlobName = 'crxtesthost.blob' | ||
|
||
async function createSEA() { | ||
await fs.rm(path.join(outDir, seaBlobName), { force: true }) | ||
await fs.rm(path.join(outDir, exeName), { force: true }) | ||
|
||
await exec('node --experimental-sea-config sea-config.json', { cwd: outDir }) | ||
await fs.cp(process.execPath, path.join(outDir, exeName)) | ||
|
||
if (process.platform === 'darwin') { | ||
await exec(`codesign --remove-signature ${exeName}`, { cwd: outDir }) | ||
} | ||
|
||
console.info(`Building ${exeName}…`) | ||
await exec( | ||
`npx postject ${basePath}${exeName} NODE_SEA_BLOB ${basePath}${seaBlobName} --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 --macho-segment-name NODE_SEA`, | ||
{ cwd: outDir }, | ||
) | ||
|
||
if (process.platform === 'darwin') { | ||
await exec(`codesign --sign - ${exeName}`, { cwd: outDir }) | ||
} | ||
} | ||
|
||
async function installConfig(extensionIds) { | ||
console.info(`Installing config…`) | ||
|
||
const name = 'com.crx.test' | ||
const config = { | ||
name, | ||
description: 'electron-chrome-extensions test', | ||
path: path.join(outDir, exeName), | ||
type: 'stdio', | ||
allowed_origins: extensionIds.map((id) => `chrome-extension://${id}/`), | ||
} | ||
|
||
switch (process.platform) { | ||
case 'darwin': { | ||
const configPath = path.join( | ||
os.homedir(), | ||
'Library', | ||
'Application Support', | ||
'Electron', | ||
'NativeMessagingHosts', | ||
) | ||
await fs.mkdir(configPath, { recursive: true }) | ||
const filePath = path.join(configPath, `${name}.json`) | ||
const data = Buffer.from(JSON.stringify(config, null, 2)) | ||
await fs.writeFile(filePath, data) | ||
break | ||
} | ||
default: | ||
return | ||
} | ||
} | ||
|
||
async function main() { | ||
const extensionIdsArg = process.argv[2] | ||
if (!extensionIdsArg) { | ||
console.error('Must pass in csv of allowed extension IDs') | ||
process.exit(1) | ||
} | ||
|
||
const extensionIds = extensionIdsArg.split(',') | ||
console.log(extensionIds) | ||
await createSEA() | ||
await installConfig(extensionIds) | ||
} | ||
|
||
main() |
26 changes: 26 additions & 0 deletions
26
packages/electron-chrome-extensions/script/native-messaging-host/main.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
const fs = require('node:fs') | ||
|
||
function readMessage() { | ||
let buffer = Buffer.alloc(4) | ||
if (fs.readSync(0, buffer, 0, 4, null) !== 4) { | ||
process.exit(1) | ||
} | ||
|
||
let messageLength = buffer.readUInt32LE(0) | ||
let messageBuffer = Buffer.alloc(messageLength) | ||
fs.readSync(0, messageBuffer, 0, messageLength, null) | ||
|
||
return JSON.parse(messageBuffer.toString()) | ||
} | ||
|
||
function sendMessage(message) { | ||
let json = JSON.stringify(message) | ||
let buffer = Buffer.alloc(4 + json.length) | ||
buffer.writeUInt32LE(json.length, 0) | ||
buffer.write(json, 4) | ||
|
||
fs.writeSync(1, buffer) | ||
} | ||
|
||
const message = readMessage() | ||
sendMessage(message) |
30 changes: 30 additions & 0 deletions
30
packages/electron-chrome-extensions/spec/chrome-nativeMessaging-spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { promisify } from 'node:util' | ||
import * as cp from 'node:child_process' | ||
import * as path from 'node:path' | ||
const exec = promisify(cp.exec) | ||
|
||
import { useExtensionBrowser, useServer } from './hooks' | ||
import { getExtensionId } from './crx-helpers' | ||
|
||
// TODO: | ||
describe.skip('nativeMessaging', () => { | ||
const server = useServer() | ||
const browser = useExtensionBrowser({ | ||
url: server.getUrl, | ||
extensionName: 'rpc', | ||
}) | ||
const hostApplication = 'com.crx.test' | ||
|
||
before(async () => { | ||
const extensionId = await getExtensionId('rpc') | ||
const scriptPath = path.join(__dirname, '..', 'script', 'native-messaging-host', 'build.js') | ||
await exec(`${scriptPath} ${extensionId}`) | ||
}) | ||
|
||
describe('connectNative()', () => { | ||
it('returns tab details', async () => { | ||
const result = await browser.crx.exec('runtime.connectNative', hostApplication) | ||
console.log({ result }) | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
125 changes: 125 additions & 0 deletions
125
packages/electron-chrome-extensions/src/browser/api/lib/native-messaging-host.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
import { spawn } from 'node:child_process' | ||
import { promises as fs } from 'node:fs' | ||
import * as path from 'node:path' | ||
import { app } from 'electron' | ||
import { ExtensionSender } from '../../router' | ||
|
||
const d = require('debug')('electron-chrome-extensions:nativeMessaging') | ||
|
||
interface NativeConfig { | ||
name: string | ||
description: string | ||
path: string | ||
type: 'stdio' | ||
allowed_origins: string[] | ||
} | ||
|
||
async function readNativeMessagingHostConfig( | ||
application: string, | ||
): Promise<NativeConfig | undefined> { | ||
let searchPaths = [path.join(app.getPath('userData'), 'NativeMessagingHosts')] | ||
switch (process.platform) { | ||
case 'darwin': | ||
searchPaths.push('/Library/Google/Chrome/NativeMessagingHosts') | ||
break | ||
default: | ||
throw new Error('Unsupported platform') | ||
} | ||
|
||
for (const basePath of searchPaths) { | ||
const filePath = path.join(basePath, `${application}.json`) | ||
try { | ||
const data = await fs.readFile(filePath) | ||
return JSON.parse(data.toString()) | ||
} catch { | ||
continue | ||
} | ||
} | ||
} | ||
export class NativeMessagingHost { | ||
private process?: ReturnType<typeof spawn> | ||
private sender: ExtensionSender | ||
private connectionId: string | ||
private connected: boolean = false | ||
private pending?: any[] | ||
|
||
constructor( | ||
extensionId: string, | ||
sender: ExtensionSender, | ||
connectionId: string, | ||
application: string, | ||
) { | ||
this.sender = sender | ||
this.sender.ipc.on(`crx-native-msg-${connectionId}`, this.receiveExtensionMessage) | ||
this.connectionId = connectionId | ||
this.launch(application, extensionId) | ||
} | ||
|
||
destroy() { | ||
this.connected = false | ||
if (this.process) { | ||
this.process.disconnect() | ||
this.process = undefined | ||
} | ||
this.sender.ipc.off(`crx-native-msg-${this.connectionId}`, this.receiveExtensionMessage) | ||
// TODO: send disconnect | ||
} | ||
|
||
private async launch(application: string, extensionId: string) { | ||
const config = await readNativeMessagingHostConfig(application) | ||
if (!config) { | ||
d('launch: unable to find %s for %s', application, extensionId) | ||
this.destroy() | ||
return | ||
} | ||
|
||
d('launch: spawning %s for %s', config.path, extensionId) | ||
// TODO: must be a binary executable | ||
this.process = spawn(config.path, [`chrome-extension://${extensionId}/`], { | ||
shell: false, | ||
}) | ||
|
||
this.process.stdout!.on('data', this.receive) | ||
this.process.stderr!.on('data', (data) => { | ||
d('stderr: %s', data.toString()) | ||
}) | ||
this.process.on('error', (err) => d('error: %s', err)) | ||
this.process.on('exit', (code) => d('exited %d', code)) | ||
|
||
this.connected = true | ||
|
||
if (this.pending && this.pending.length > 0) { | ||
d('sending %d pending messages', this.pending.length) | ||
this.pending.forEach((msg) => this.send(msg)) | ||
this.pending = [] | ||
} | ||
} | ||
|
||
private receiveExtensionMessage = (_event: Electron.IpcMainEvent, message: any) => { | ||
this.send(message) | ||
} | ||
|
||
private send(json: any) { | ||
d('send', json) | ||
|
||
if (!this.connected) { | ||
const pending = this.pending || (this.pending = []) | ||
pending.push(json) | ||
d('send: pending') | ||
return | ||
} | ||
|
||
const message = JSON.stringify(json) | ||
const buffer = Buffer.alloc(4 + message.length) | ||
buffer.writeUInt32LE(message.length, 0) | ||
buffer.write(message, 4) | ||
this.process!.stdin!.write(buffer) | ||
} | ||
|
||
private receive = (data: Buffer) => { | ||
const length = data.readUInt32LE(0) | ||
const message = JSON.parse(data.subarray(4, 4 + length).toString()) | ||
d('receive: %s', message) | ||
this.sender.send(`crx-native-msg-${this.connectionId}`, message) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.