mirror of
https://github.com/finos/SymphonyElectron.git
synced 2025-02-25 18:55:29 -06:00
SDA-3770 Add support for optional symphony-c9-shell (#1446)
* Add support for optional symphony-c9-shell
This commit is contained in:
parent
5977ed8642
commit
8a7d5c0fcf
@ -13,7 +13,7 @@ namespace Symphony
|
||||
void dialog_Load(object sender, System.EventArgs e)
|
||||
{
|
||||
// Detect if Symphony is running
|
||||
bool isRunning = System.Diagnostics.Process.GetProcessesByName("Symphony").Length > 1;
|
||||
bool isRunning = (System.Diagnostics.Process.GetProcessesByName("Symphony").Length > 1 || System.Diagnostics.Process.GetProcessesByName("C9Shell").Length >= 1);
|
||||
if (isRunning)
|
||||
{
|
||||
// If it is running, disable the "next" button
|
||||
|
@ -12,7 +12,7 @@ namespace Symphony
|
||||
private void MaintenanceDialog_Shown(object sender, System.EventArgs e)
|
||||
{
|
||||
// Detect if Symphony is running
|
||||
bool isRunning = System.Diagnostics.Process.GetProcessesByName("Symphony").Length > 1;
|
||||
bool isRunning = (System.Diagnostics.Process.GetProcessesByName("Symphony").Length > 1 || System.Diagnostics.Process.GetProcessesByName("C9Shell").Length >= 1);
|
||||
if (isRunning)
|
||||
{
|
||||
// If it is running, continue to the "Close Symphony" screen
|
||||
|
@ -53,8 +53,8 @@ class Script
|
||||
// StartOn = SvcEvent.Install,
|
||||
// StopOn = SvcEvent.InstallUninstall_Wait,
|
||||
// RemoveOn = SvcEvent.Uninstall_Wait,
|
||||
// };
|
||||
|
||||
// };
|
||||
|
||||
// Create a wixsharp project instance and assign the project name to it, and a hierarchy of all files to include
|
||||
// Files are taken from multiple locations, and not all files in each location should be included, which is why
|
||||
// the file list is rather long and explicit. At some point we might make the `dist` folder match exactly the
|
||||
@ -116,7 +116,9 @@ class Script
|
||||
new Files(@"..\..\..\dist\win-unpacked\resources\app.asar.unpacked\node_modules\*.*")
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
new Dir(@"cloud9",
|
||||
new Files(@"..\..\..\dist\win-unpacked\cloud9\*.*")
|
||||
),
|
||||
|
||||
// Add a launch condition to require Windows Server 2008 or later
|
||||
@ -124,7 +126,7 @@ class Script
|
||||
// https://docs.microsoft.com/en-us/windows/win32/msi/operating-system-property-values
|
||||
new LaunchCondition("VersionNT>=600 AND WindowsBuild>=6001", "OS not supported"),
|
||||
|
||||
// Add registry entry used by protocol handler to launch symphony when opening symphony:// URIs
|
||||
// Add registry entry used by protocol handler to launch symphony when opening symphony:// URIs
|
||||
new RegValue(WixSharp.RegistryHive.ClassesRoot, productName, "", "URL:symphony"),
|
||||
new RegValue(WixSharp.RegistryHive.ClassesRoot, productName, "URL Protocol", ""),
|
||||
new RegValue(WixSharp.RegistryHive.ClassesRoot, productName + @"\shell\open\command", "", "\"[INSTALLDIR]Symphony.exe\" " + userDataPathArgument + " \"%1\""),
|
||||
@ -133,7 +135,8 @@ class Script
|
||||
// will not work for us, as we have a "minimize on close" option, which stops the app from terminating on WM_CLOSE. So we
|
||||
// instruct the installer to not send a Close message, but instead send the EndSession message, and we have a custom event
|
||||
// handler in the SDA code which listens for this message, and ensures app termination when it is received.
|
||||
new CloseApplication("Symphony.exe", false) { EndSessionMessage = true }
|
||||
new CloseApplication("Symphony.exe", false) { EndSessionMessage = true },
|
||||
new CloseApplication("C9Shell.exe", false) { EndSessionMessage = true }
|
||||
);
|
||||
|
||||
// The build script which calls the wix# builder, will be run from a command environment which has %SYMVER% set.
|
||||
@ -312,6 +315,19 @@ class Script
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// The embedded C9 Shell should terminate when the parent window is closed, but it doesn't always work. So we'll just force it to exit
|
||||
System.Diagnostics.Process.GetProcessesByName("C9Shell").ForEach(p =>
|
||||
{
|
||||
if (System.IO.Path.GetFileName(p.MainModule.FileName) == "C9Shell.exe")
|
||||
{
|
||||
if (!p.HasExited)
|
||||
{
|
||||
p.Kill();
|
||||
p.WaitForExit();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (System.ComponentModel.Win32Exception ex)
|
||||
|
@ -41,7 +41,7 @@ namespace Symphony
|
||||
}
|
||||
|
||||
// Detect if Symphony is running
|
||||
bool isRunning = System.Diagnostics.Process.GetProcessesByName("Symphony").Length > 1;
|
||||
bool isRunning = (System.Diagnostics.Process.GetProcessesByName("Symphony").Length > 1 || System.Diagnostics.Process.GetProcessesByName("C9Shell").Length >= 1);
|
||||
if (!isRunning)
|
||||
{
|
||||
// If it is not running, change the label of the "Next" button to "Install" as the CloseDialog will be skipped
|
||||
@ -81,7 +81,7 @@ namespace Symphony
|
||||
}
|
||||
|
||||
// Detect if Symphony is running
|
||||
bool isRunning = System.Diagnostics.Process.GetProcessesByName("Symphony").Length > 1;
|
||||
bool isRunning = (System.Diagnostics.Process.GetProcessesByName("Symphony").Length > 1 || System.Diagnostics.Process.GetProcessesByName("C9Shell").Length >= 1);
|
||||
if (isRunning)
|
||||
{
|
||||
// If it is running, continue to the "Close Symphony" screen
|
||||
|
17
package-lock.json
generated
17
package-lock.json
generated
@ -70,6 +70,7 @@
|
||||
"typescript": "3.9.7"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@symphony/symphony-c9-shell": "3.14.99-37",
|
||||
"auto-update": "file:auto_update",
|
||||
"screen-share-indicator-frame": "git+https://github.com/symphonyoss/ScreenShareIndicatorFrame.git#v1.4.13",
|
||||
"screen-snippet": "git+https://github.com/symphonyoss/ScreenSnippet2.git#9.2.2",
|
||||
@ -2288,6 +2289,16 @@
|
||||
"dev": true,
|
||||
"license": "(Unlicense OR Apache-2.0)"
|
||||
},
|
||||
"node_modules/@symphony/symphony-c9-shell": {
|
||||
"version": "3.14.99-37",
|
||||
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/@symphony/symphony-c9-shell/-/@symphony/symphony-c9-shell-3.14.99-37.tgz",
|
||||
"integrity": "sha1-Xl2kS4dN4jlSd6oBdzBLfUd/Mnc=",
|
||||
"license": "UNLICENSED",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@szmarczak/http-timer": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/@szmarczak/http-timer/-/http-timer-1.1.2.tgz",
|
||||
@ -26817,6 +26828,12 @@
|
||||
"integrity": "sha1-jaXGUwkVZT86Hzj9XxAdjD+AecU=",
|
||||
"dev": true
|
||||
},
|
||||
"@symphony/symphony-c9-shell": {
|
||||
"version": "3.14.99-37",
|
||||
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/@symphony/symphony-c9-shell/-/@symphony/symphony-c9-shell-3.14.99-37.tgz",
|
||||
"integrity": "sha1-Xl2kS4dN4jlSd6oBdzBLfUd/Mnc=",
|
||||
"optional": true
|
||||
},
|
||||
"@szmarczak/http-timer": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/@szmarczak/http-timer/-/http-timer-1.1.2.tgz",
|
||||
|
@ -170,6 +170,7 @@
|
||||
"shell-path": "^3.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@symphony/symphony-c9-shell": "3.14.99-37",
|
||||
"auto-update": "file:auto_update",
|
||||
"screen-share-indicator-frame": "git+https://github.com/symphonyoss/ScreenShareIndicatorFrame.git#v1.4.13",
|
||||
"screen-snippet": "git+https://github.com/symphonyoss/ScreenSnippet2.git#9.2.2",
|
||||
|
@ -91,6 +91,10 @@ set installerDir="%CD%\installer\win"
|
||||
set distDir="%CD%\dist"
|
||||
set rootDir="%CD%"
|
||||
|
||||
echo "Move optional symphony-c9-shell files into place: " "%distDir%\win-unpacked\cloud9"
|
||||
mkdir "%distDir%\win-unpacked\cloud9"
|
||||
move /y "%rootDir%\node_modules\@symphony\symphony-c9-shell\shell" "%distDir%\win-unpacked\cloud9"
|
||||
|
||||
if NOT EXIST "%PFX_DIR%\%PFX_FILE%" (
|
||||
echo "can not find .pfx file" "%pfxDir%\%pfxFile%"
|
||||
exit /b -1
|
||||
|
44
spec/c9PipeHandler.spec.ts
Normal file
44
spec/c9PipeHandler.spec.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { connectC9Pipe } from '../src/app/c9-pipe-handler';
|
||||
import { createConnection } from 'net';
|
||||
|
||||
jest.mock('net');
|
||||
|
||||
describe('C9 pipe handler', () => {
|
||||
const webContentsMocked = { send: jest.fn() };
|
||||
const mockConnectionEvents = new Map<String, any>();
|
||||
const mockCreateConnection = (createConnection as unknown) as jest.MockInstance<
|
||||
typeof createConnection
|
||||
>;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks().resetModules();
|
||||
mockCreateConnection.mockImplementation((_path, onConnect: () => void) => {
|
||||
onConnect();
|
||||
return {
|
||||
on: (event, callback) => {
|
||||
mockConnectionEvents.set(event, callback);
|
||||
},
|
||||
destroy: jest.fn(),
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
describe('connect', () => {
|
||||
it('success', () => {
|
||||
connectC9Pipe(webContentsMocked as any, 'symphony-c9-test');
|
||||
expect(webContentsMocked.send).toHaveBeenCalledWith(
|
||||
'c9-pipe-event',
|
||||
expect.objectContaining({ event: 'connected' }),
|
||||
);
|
||||
});
|
||||
|
||||
it('data', () => {
|
||||
connectC9Pipe(webContentsMocked as any, 'symphony-c9-test');
|
||||
mockConnectionEvents.get('data')('the data');
|
||||
expect(webContentsMocked.send).toHaveBeenCalledWith(
|
||||
'c9-pipe-event',
|
||||
expect.objectContaining({ event: 'data', arg: 'the data' }),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
@ -7,6 +7,7 @@ import * as windowActions from '../src/app/window-actions';
|
||||
import { windowHandler } from '../src/app/window-handler';
|
||||
import * as utils from '../src/app/window-utils';
|
||||
import { apiCmds, apiName } from '../src/common/api-interface';
|
||||
import * as c9PipeHandler from '../src/app/c9-pipe-handler';
|
||||
import { logger } from '../src/common/logger';
|
||||
import { BrowserWindow, ipcMain } from './__mocks__/electron';
|
||||
|
||||
@ -503,5 +504,16 @@ describe('main api handler', () => {
|
||||
expect(windows['popout1'].getNativeWindowHandle).toBeCalledTimes(1);
|
||||
expect(windows['popout2'].getNativeWindowHandle).toBeCalledTimes(0);
|
||||
});
|
||||
|
||||
it('should call `connectC9Pipe` correctly', () => {
|
||||
const spy = jest.spyOn(c9PipeHandler, 'connectC9Pipe');
|
||||
const value = {
|
||||
cmd: apiCmds.connectCloud9Pipe,
|
||||
pipe: 'pipe-name',
|
||||
};
|
||||
const expectedValue = [{ send: expect.any(Function) }, 'pipe-name'];
|
||||
ipcMain.send(apiName.symphonyApi, value);
|
||||
expect(spy).toBeCalledWith(...expectedValue);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
115
src/app/c9-pipe-handler.ts
Normal file
115
src/app/c9-pipe-handler.ts
Normal file
@ -0,0 +1,115 @@
|
||||
import { WebContents } from 'electron';
|
||||
import { createConnection, Socket } from 'net';
|
||||
import { logger } from '../common/logger';
|
||||
|
||||
class C9PipeHandler {
|
||||
private _socket: Socket | undefined;
|
||||
|
||||
/**
|
||||
* Connects to the C9 pipe server. Errors will be reported as events.
|
||||
* @param sender Where to send incoming events
|
||||
* @param pipe pipe identifier
|
||||
*/
|
||||
public connect(sender: WebContents, pipe: string) {
|
||||
let connectionSuccess = false;
|
||||
if (!pipe.startsWith('symphony-c9-')) {
|
||||
logger.info('c9-pipe: Invalid pipe name specified: ' + pipe);
|
||||
sender.send('c9-pipe-event', {
|
||||
event: 'connection-failed',
|
||||
arg: 'invalid pipe',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const path = '\\\\?\\pipe\\' + pipe;
|
||||
logger.info('c9-pipe: Connecting to ' + path);
|
||||
const client = createConnection(path, () => {
|
||||
connectionSuccess = true;
|
||||
logger.info('c9-pipe: Connected to ' + path);
|
||||
sender.send('c9-pipe-event', { event: 'connected' });
|
||||
});
|
||||
this._socket = client;
|
||||
|
||||
client.on('data', (data) => {
|
||||
sender.send('c9-pipe-event', { event: 'data', arg: data });
|
||||
});
|
||||
client.on('close', () => {
|
||||
// If the socket is successfully connected, the close is coming from the server side, or
|
||||
// is otherwise unexpected. In this case, send close event to the extension so it can go
|
||||
// into a reconnect loop.
|
||||
if (connectionSuccess) {
|
||||
logger.info('c9-pipe: Server closed ' + path);
|
||||
sender.send('c9-pipe-event', { event: 'close' });
|
||||
this._socket?.destroy();
|
||||
}
|
||||
});
|
||||
client.on('error', (err: Error) => {
|
||||
// If the connection is already established, any error will also result in a 'close' event
|
||||
// and will be handled above.
|
||||
logger.info('c9-pipe: Error from ' + path, err);
|
||||
if (!connectionSuccess) {
|
||||
sender.send('c9-pipe-event', {
|
||||
event: 'connection-failed',
|
||||
arg: err.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes data to the pipe
|
||||
* @param data the data to be written
|
||||
*/
|
||||
public write(data: Uint8Array) {
|
||||
this._socket?.write(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes an open pipe
|
||||
*/
|
||||
public close() {
|
||||
logger.info('c9-pipe: Closing pipe');
|
||||
this._socket?.destroy();
|
||||
this._socket = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the pipe is open
|
||||
*/
|
||||
public isConnected() {
|
||||
return this._socket !== undefined;
|
||||
}
|
||||
}
|
||||
|
||||
let c9PipeHandler: C9PipeHandler | undefined;
|
||||
|
||||
/**
|
||||
* Connects to the C9 pipe server. Errors will be reported as callbacks.
|
||||
* @param sender Where to send incoming events
|
||||
* @param pipe pipe identifier
|
||||
*/
|
||||
export const connectC9Pipe = (sender: WebContents, pipe: string) => {
|
||||
if (!c9PipeHandler) {
|
||||
c9PipeHandler = new C9PipeHandler();
|
||||
} else {
|
||||
if (c9PipeHandler.isConnected()) {
|
||||
c9PipeHandler.close();
|
||||
}
|
||||
}
|
||||
c9PipeHandler.connect(sender, pipe);
|
||||
};
|
||||
|
||||
/**
|
||||
* Writes data to the pipe
|
||||
* @param data the data to be written
|
||||
*/
|
||||
export const writeC9Pipe = (data: Uint8Array) => {
|
||||
c9PipeHandler?.write(data);
|
||||
};
|
||||
|
||||
/**
|
||||
* Closes an open pipe
|
||||
*/
|
||||
export const closeC9Pipe = () => {
|
||||
c9PipeHandler?.close();
|
||||
};
|
161
src/app/c9-shell-handler.ts
Normal file
161
src/app/c9-shell-handler.ts
Normal file
@ -0,0 +1,161 @@
|
||||
import { app, WebContents } from 'electron';
|
||||
import { isDevEnv, isWindowsOS } from '../common/env';
|
||||
import { logger } from '../common/logger';
|
||||
|
||||
import { ChildProcess, spawn } from 'child_process';
|
||||
import * as path from 'path';
|
||||
import { getCommandLineArgs, getGuid } from '../common/utils';
|
||||
|
||||
/**
|
||||
* Current state of the C9 shell process.
|
||||
*/
|
||||
export interface IShellStatus {
|
||||
status: 'inactive' | 'starting' | 'active';
|
||||
pipeName?: string;
|
||||
}
|
||||
|
||||
type StatusCallback = (status: IShellStatus) => void;
|
||||
|
||||
class C9ShellHandler {
|
||||
private _c9shell: ChildProcess | undefined;
|
||||
private _curStatus: IShellStatus | undefined;
|
||||
private _statusCallback: StatusCallback | undefined;
|
||||
|
||||
/**
|
||||
* Starts the c9shell process
|
||||
*/
|
||||
public startShell() {
|
||||
if (this._attachExistingC9Shell()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._c9shell) {
|
||||
this._c9shell = this._launchC9Shell();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows the C9 extension to subscribe to shell status updates. Immediately sends current status.
|
||||
*/
|
||||
public setStatusCallback(callback: StatusCallback) {
|
||||
this._statusCallback = callback;
|
||||
if (!this._statusCallback) {
|
||||
return;
|
||||
}
|
||||
if (this._curStatus) {
|
||||
this._statusCallback(this._curStatus);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the current shell status and notify the callback if set.
|
||||
*/
|
||||
private _updateStatus(status: IShellStatus) {
|
||||
this._curStatus = status;
|
||||
if (this._statusCallback) {
|
||||
this._statusCallback(status);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the user wants to control the C9 shell process explicitly, returns true if so
|
||||
* @returns
|
||||
*/
|
||||
private _attachExistingC9Shell(): boolean {
|
||||
const customC9ShellPipe = getCommandLineArgs(
|
||||
process.argv,
|
||||
'--c9pipe=',
|
||||
false,
|
||||
);
|
||||
if (customC9ShellPipe) {
|
||||
logger.info(`c9-shell: Using custom pipe: ${customC9ShellPipe}`);
|
||||
this._updateStatus({
|
||||
status: 'active',
|
||||
pipeName: 'symphony-c9-' + customC9ShellPipe.substring(9),
|
||||
});
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Launches the correct c9shell process
|
||||
*/
|
||||
private _launchC9Shell(): ChildProcess | undefined {
|
||||
this._curStatus = undefined;
|
||||
const uniquePipeName = getGuid();
|
||||
|
||||
const c9ShellPath = isDevEnv
|
||||
? path.join(
|
||||
__dirname,
|
||||
'../../../node_modules/@symphony/symphony-c9-shell/shell/c9shell.exe',
|
||||
)
|
||||
: path.join(path.dirname(app.getPath('exe')), 'cloud9/shell/c9shell.exe');
|
||||
|
||||
const customC9ShellArgs = getCommandLineArgs(
|
||||
process.argv,
|
||||
'--c9args=',
|
||||
false,
|
||||
);
|
||||
const customC9ShellArgList = customC9ShellArgs
|
||||
? customC9ShellArgs.substring(9).split(' ')
|
||||
: [];
|
||||
|
||||
logger.info('c9-shell: launching shell', c9ShellPath, customC9ShellArgList);
|
||||
this._updateStatus({ status: 'starting' });
|
||||
|
||||
const c9Shell = spawn(
|
||||
c9ShellPath,
|
||||
[
|
||||
'--allowmultiproc',
|
||||
'--symphonyHost',
|
||||
uniquePipeName,
|
||||
...customC9ShellArgList,
|
||||
],
|
||||
{
|
||||
stdio: 'pipe',
|
||||
},
|
||||
);
|
||||
c9Shell.on('close', (code) => {
|
||||
logger.info('c9-shell: closed with code', code);
|
||||
this._c9shell = undefined;
|
||||
this._updateStatus({ status: 'inactive' });
|
||||
});
|
||||
c9Shell.on('spawn', () => {
|
||||
logger.info('c9-shell: shell process successfully spawned');
|
||||
this._updateStatus({
|
||||
status: 'active',
|
||||
pipeName: 'symphony-c9-' + uniquePipeName,
|
||||
});
|
||||
});
|
||||
c9Shell.stdout.on('data', (data) => {
|
||||
logger.info(`c9-shell: ${data.toString().trim()}`);
|
||||
});
|
||||
c9Shell.stderr.on('data', (data) => {
|
||||
logger.error(`c9-shell: ${data.toString().trim()}`);
|
||||
});
|
||||
|
||||
return c9Shell;
|
||||
}
|
||||
}
|
||||
|
||||
let c9ShellHandler: C9ShellHandler | undefined;
|
||||
|
||||
/**
|
||||
* Starts the C9 shell process asynchronously, if not already started.
|
||||
*/
|
||||
export const loadC9Shell = (sender: WebContents) => {
|
||||
if (!isWindowsOS) {
|
||||
logger.error("c9-shell: can't load shell on non-Windows OS");
|
||||
return;
|
||||
}
|
||||
if (!c9ShellHandler) {
|
||||
c9ShellHandler = new C9ShellHandler();
|
||||
}
|
||||
c9ShellHandler.setStatusCallback((status: IShellStatus) => {
|
||||
logger.info('c9-shell: sending status', status);
|
||||
sender.send('c9-status-event', { status });
|
||||
});
|
||||
c9ShellHandler.startShell();
|
||||
};
|
@ -18,6 +18,8 @@ import { logger } from '../common/logger';
|
||||
import { activityDetection } from './activity-detection';
|
||||
import { analytics } from './analytics-handler';
|
||||
import appStateHandler from './app-state-handler';
|
||||
import { closeC9Pipe, connectC9Pipe, writeC9Pipe } from './c9-pipe-handler';
|
||||
import { loadC9Shell } from './c9-shell-handler';
|
||||
import { getCitrixMediaRedirectionStatus } from './citrix-handler';
|
||||
import { CloudConfigDataTypes, config, ICloudConfig } from './config-handler';
|
||||
import { downloadHandler } from './download-handler';
|
||||
@ -356,6 +358,18 @@ ipcMain.on(
|
||||
swiftSearchInstance.handleMessageEvents(arg.swiftSearchData);
|
||||
}
|
||||
break;
|
||||
case apiCmds.connectCloud9Pipe:
|
||||
connectC9Pipe(event.sender, arg.pipe);
|
||||
break;
|
||||
case apiCmds.writeCloud9Pipe:
|
||||
writeC9Pipe(arg.data);
|
||||
break;
|
||||
case apiCmds.closeCloud9Pipe:
|
||||
closeC9Pipe();
|
||||
break;
|
||||
case apiCmds.launchCloud9:
|
||||
loadC9Shell(event.sender);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ import {
|
||||
import { notification } from '../renderer/notification';
|
||||
import { cleanAppCacheOnCrash } from './app-cache-handler';
|
||||
import { AppMenu } from './app-menu';
|
||||
import { closeC9Pipe } from './c9-pipe-handler';
|
||||
import { handleChildWindow } from './child-window-handler';
|
||||
import {
|
||||
CloudConfigDataTypes,
|
||||
@ -468,6 +469,8 @@ export class WindowHandler {
|
||||
// reset to false when the client reloads
|
||||
this.isMana = false;
|
||||
logger.info(`window-handler: main window web contents finished loading!`);
|
||||
// Make sure there is no lingering C9 pipe connection
|
||||
closeC9Pipe();
|
||||
// early exit if the window has already been destroyed
|
||||
if (!this.mainWebContents || this.mainWebContents.isDestroyed()) {
|
||||
logger.info(
|
||||
|
@ -65,6 +65,10 @@ export enum apiCmds {
|
||||
getNativeWindowHandle = 'get-native-window-handle',
|
||||
getCitrixMediaRedirectionStatus = 'get-citrix-media-redirection-status',
|
||||
getSources = 'getSources',
|
||||
launchCloud9 = 'launch-cloud9',
|
||||
connectCloud9Pipe = 'connect-cloud9-pipe',
|
||||
writeCloud9Pipe = 'write-cloud9-pipe',
|
||||
closeCloud9Pipe = 'close-cloud9-pipe',
|
||||
}
|
||||
|
||||
export enum apiName {
|
||||
@ -115,6 +119,8 @@ export interface IApiArgs {
|
||||
swiftSearchData: any;
|
||||
types: string[];
|
||||
thumbnailSize: Size;
|
||||
pipe: string;
|
||||
data: Uint8Array;
|
||||
}
|
||||
|
||||
export type Themes = 'light' | 'dark';
|
||||
@ -269,3 +275,8 @@ export type NotificationActionCallback = (
|
||||
) => void;
|
||||
|
||||
export type ConfigUpdateType = 'restart' | 'reload';
|
||||
|
||||
export interface ICloud9Pipe {
|
||||
write(data: Uint8Array): void;
|
||||
close(): void;
|
||||
}
|
||||
|
@ -92,6 +92,8 @@ if (ssfWindow.ssf) {
|
||||
getCitrixMediaRedirectionStatus:
|
||||
ssfWindow.ssf.getCitrixMediaRedirectionStatus,
|
||||
registerClientBanner: ssfWindow.ssf.registerClientBanner,
|
||||
launchCloud9: ssfWindow.ssf.launchCloud9,
|
||||
connectCloud9Pipe: ssfWindow.ssf.connectCloud9Pipe,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@ import {
|
||||
searchAPIVersion,
|
||||
version,
|
||||
} from '../../package.json';
|
||||
import { IShellStatus } from '../app/c9-shell-handler';
|
||||
import { RedirectionStatus } from '../app/citrix-handler';
|
||||
import { IDownloadItem } from '../app/download-handler';
|
||||
import {
|
||||
@ -13,6 +14,7 @@ import {
|
||||
ConfigUpdateType,
|
||||
IBadgeCount,
|
||||
IBoundsChange,
|
||||
ICloud9Pipe,
|
||||
ICPUUsage,
|
||||
ILogMsg,
|
||||
IMediaPermission,
|
||||
@ -69,6 +71,8 @@ export interface ILocalObject {
|
||||
showClientBannerCallback?: Array<
|
||||
(reason: string, action: ConfigUpdateType) => void
|
||||
>;
|
||||
c9PipeEventCallback?: (event: string, arg?: any) => void;
|
||||
c9MessageCallback?: (status: IShellStatus) => void;
|
||||
}
|
||||
|
||||
const local: ILocalObject = {
|
||||
@ -776,6 +780,80 @@ export class SSFApi {
|
||||
local.showClientBannerCallback.push(callback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects to a Cloud9 pipe
|
||||
*
|
||||
* @param pipe pipe name
|
||||
* @param onData callback that is invoked when data is received over the connection
|
||||
* @param onClose callback that is invoked when the connection is closed by the remote side
|
||||
* @returns Cloud9 pipe instance promise
|
||||
*/
|
||||
public connectCloud9Pipe(
|
||||
pipe: string,
|
||||
onData: (data: Uint8Array) => void,
|
||||
onClose: () => void,
|
||||
): Promise<ICloud9Pipe> {
|
||||
if (
|
||||
typeof pipe === 'string' &&
|
||||
typeof onData === 'function' &&
|
||||
typeof onClose === 'function'
|
||||
) {
|
||||
if (local.c9PipeEventCallback) {
|
||||
return Promise.reject("Can't connect to pipe, already connected");
|
||||
}
|
||||
|
||||
return new Promise<ICloud9Pipe>((resolve, reject) => {
|
||||
local.c9PipeEventCallback = (event: string, arg?: any) => {
|
||||
switch (event) {
|
||||
case 'connected':
|
||||
const ret = {
|
||||
write: (data: Uint8Array) => {
|
||||
ipcRenderer.send(apiName.symphonyApi, {
|
||||
cmd: apiCmds.writeCloud9Pipe,
|
||||
data,
|
||||
});
|
||||
},
|
||||
close: () => {
|
||||
ipcRenderer.send(apiName.symphonyApi, {
|
||||
cmd: apiCmds.closeCloud9Pipe,
|
||||
});
|
||||
},
|
||||
};
|
||||
resolve(ret);
|
||||
break;
|
||||
case 'connection-failed':
|
||||
local.c9PipeEventCallback = undefined;
|
||||
reject(arg);
|
||||
break;
|
||||
case 'data':
|
||||
onData(arg);
|
||||
break;
|
||||
case 'close':
|
||||
local.c9PipeEventCallback = undefined;
|
||||
onClose();
|
||||
break;
|
||||
}
|
||||
};
|
||||
ipcRenderer.send(apiName.symphonyApi, {
|
||||
cmd: apiCmds.connectCloud9Pipe,
|
||||
pipe,
|
||||
});
|
||||
});
|
||||
} else {
|
||||
return Promise.reject('Invalid arguments');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Launches the Cloud9 client.
|
||||
*/
|
||||
public launchCloud9(callback: (status: IShellStatus) => void): void {
|
||||
local.c9MessageCallback = callback;
|
||||
ipcRenderer.send(apiName.symphonyApi, {
|
||||
cmd: apiCmds.launchCloud9,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1018,6 +1096,20 @@ local.ipcRenderer.on('display-client-banner', (_event, args) => {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* An event triggered by the main process when a cloud9 pipe event occurs
|
||||
*/
|
||||
local.ipcRenderer.on('c9-pipe-event', (_event, args) => {
|
||||
local.c9PipeEventCallback?.call(null, args.event, args?.arg);
|
||||
});
|
||||
|
||||
/**
|
||||
* An event triggered by the main process when the status of the cloud9 client changes
|
||||
*/
|
||||
local.ipcRenderer.on('c9-status-event', (_event, args) => {
|
||||
local.c9MessageCallback?.call(null, args?.status);
|
||||
});
|
||||
|
||||
// Invoked whenever the app is reloaded/navigated
|
||||
const sanitize = (): void => {
|
||||
if (window.name === apiName.mainWindowName) {
|
||||
|
Loading…
Reference in New Issue
Block a user