1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-17 08:43:33 +00:00

Initial PoC for browser <-> desktop communication

This commit is contained in:
Hinton
2020-10-05 15:11:37 +02:00
parent 38ecc3b74b
commit f09a788103
13 changed files with 960 additions and 4 deletions

22
proxy/app.ts Normal file
View File

@@ -0,0 +1,22 @@
import NativeMessage from './nativemessage';
import IPC from './ipc';
const args = process.argv.slice(2);
class Proxy {
ipc: IPC;
nativeMessage: NativeMessage;
constructor() {
this.ipc = new IPC();
this.nativeMessage = new NativeMessage(this.ipc);
}
run() {
this.ipc.connect();
this.nativeMessage.listen();
}
}
const proxy = new Proxy();
proxy.run();

4
proxy/global.d.ts vendored Normal file
View File

@@ -0,0 +1,4 @@
declare module 'node-ipc' {
const x: any;
export = x;
}

61
proxy/ipc.ts Normal file
View File

@@ -0,0 +1,61 @@
/* tslint:disable:no-console */
import * as ipc from 'node-ipc';
import { spawn } from 'child_process';
const StartDesktopCooldown = 60 * 1000; // 1 minute delay between attempts to start desktop
ipc.config.id = 'proxy';
ipc.config.retry = 1500;
ipc.config.logger = console.warn; // Stdout is used for native messaging
export default class IPC {
private connected = false;
private lastStartedDesktop = 0;
connect() {
ipc.connectTo('bitwarden', () => {
ipc.of.bitwarden.on('connect', () => {
this.connected = true;
console.error(
'## connected to bitwarden desktop ##',
ipc.config.delay
);
ipc.of.bitwarden.emit('message', 'hello');
});
ipc.of.bitwarden.on('disconnect', () => {
this.connected = false;
console.error('disconnected from world');
});
ipc.of.bitwarden.on('message', (data: any) => {
console.error('got a message from world : ', data);
});
ipc.of.bitwarden.on('error', (err: any) => {
if (err.syscall === 'connect' && this.lastStartedDesktop + StartDesktopCooldown < Date.now()) {
this.lastStartedDesktop = Date.now();
console.error('Attempting to start Bitwarden desktop application');
this.startDesktop();
}
console.error('error', err);
});
});
}
isConnected() {
return this.connected;
}
send(json: object) {
ipc.of.bitwarden.emit('message', json);
}
// TODO: Do we want to start the desktop application? How do we get the install path?
private startDesktop() {
spawn(
'C:\\Users\\Oscar\\Documents\\Projects\\Bitwarden\\desktop\\dist\\Bitwarden-Portable-1.22.2.exe',
{ detached: true }
);
}
}

85
proxy/nativemessage.ts Normal file
View File

@@ -0,0 +1,85 @@
/* tslint:disable:no-console */
import IPC from 'ipc';
// Mostly copied from the example on
// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Native_messaging
export default class NativeMessage {
ipc: IPC;
constructor(ipc: IPC) {
this.ipc = ipc;
}
send(message: object) {
const messageBuffer = Buffer.from(JSON.stringify(message));
const headerBuffer = Buffer.alloc(4);
headerBuffer.writeUInt32LE(messageBuffer.length, 0);
process.stdout.write(Buffer.concat([headerBuffer, messageBuffer]));
}
listen() {
let payloadSize: number = null;
// A queue to store the chunks as we read them from stdin.
// This queue can be flushed when `payloadSize` data has been read
const chunks: any = [];
// Only read the size once for each payload
const sizeHasBeenRead = () => Boolean(payloadSize);
// All the data has been read, reset everything for the next message
const flushChunksQueue = () => {
payloadSize = null;
chunks.splice(0);
};
const processData = () => {
// Create one big buffer with all all the chunks
const stringData = Buffer.concat(chunks);
console.error(stringData);
// The browser will emit the size as a header of the payload,
// if it hasn't been read yet, do it.
// The next time we'll need to read the payload size is when all of the data
// of the current payload has been read (ie. data.length >= payloadSize + 4)
if (!sizeHasBeenRead()) {
payloadSize = stringData.readUInt32LE(0);
}
// If the data we have read so far is >= to the size advertised in the header,
// it means we have all of the data sent.
// We add 4 here because that's the size of the bytes that old the payloadSize
if (stringData.length >= payloadSize + 4) {
// Remove the header
const contentWithoutSize = stringData
.slice(4, payloadSize + 4)
.toString();
// Reset the read size and the queued chunks
flushChunksQueue();
const json = JSON.parse(contentWithoutSize);
console.error(json);
// Forward to desktop application
this.ipc.send(json);
}
};
process.stdin.on('readable', () => {
// A temporary variable holding the nodejs.Buffer of each
// chunk of data read off stdin
let chunk = null;
// Read all of the available data
// tslint:disable-next-line:no-conditional-assignment
while ((chunk = process.stdin.read()) !== null) {
chunks.push(chunk);
}
processData();
});
}
}

56
proxy/tsconfig.json Normal file
View File

@@ -0,0 +1,56 @@
{
"compilerOptions": {
"moduleResolution": "node",
"noImplicitAny": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"module": "CommonJS",
"target": "ES2016",
"allowJs": true,
"sourceMap": true,
"types": ["sweetalert2", "node"],
"baseUrl": ".",
"outDir": "../build/proxy",
"paths": {
"tldjs": [
"jslib/src/misc/tldjs.noop"
],
"jslib/*": [
"jslib/src/*"
],
"@angular/*": [
"node_modules/@angular/*"
],
"electron": [
"node_modules/electron"
],
"electron-log": [
"node_modules/electron-log"
],
"electron-store": [
"node_modules/electron-store"
],
"node": [
"node_modules/@types/node"
],
"duo_web_sdk": [
"node_modules/duo_web_sdk"
]
}
},
"angularCompilerOptions": {
"preserveWhitespaces": true
},
"exclude": [
"node_modules",
"jslib/node_modules",
"dist",
"dist-safari",
"jslib/dist",
"build",
"jslib/spec",
"jslib/src/cli",
"jslib/src/services/nodeApi.service.ts",
"jslib/src/services/lowdbStorage.service.ts"
]
}