mirror of
https://github.com/finos/SymphonyElectron.git
synced 2024-12-25 08:21:21 -06:00
SDA-4177 Adding tel custom protocol to support calls through Symphony on windows and mac
This commit is contained in:
parent
1864a7b410
commit
36d6e51221
@ -75,7 +75,7 @@ npm run test:unit -- --match=spell*
|
||||
|
||||
```
|
||||
registry = https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/
|
||||
_auth = <auth value from #4>
|
||||
//repo.symphony.com/artifactory/api/npm/npm-virtual-dev/:_auth = <auth value from #4>
|
||||
always-auth = true
|
||||
```
|
||||
|
||||
|
20407
package-lock.json
generated
20407
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
18
package.json
18
package.json
@ -74,15 +74,19 @@
|
||||
"entitlements": "entitlements.mac.plist",
|
||||
"notarize": false,
|
||||
"entitlementsInherit": "entitlements.mac.plist",
|
||||
"extendInfo": {
|
||||
"CFBundleURLTypes": [
|
||||
{
|
||||
"CFBundleTypeRole": "Viewer",
|
||||
"CFBundleURLName": "SymTel",
|
||||
"CFBundleURLSchemes": [
|
||||
"tel"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"gatekeeperAssess": true,
|
||||
"hardenedRuntime": true,
|
||||
"binaries": [
|
||||
"library/lz4.exec",
|
||||
"library/indexvalidator.exec",
|
||||
"library/libsymphonysearch.dylib",
|
||||
"library/cryptoLib.dylib",
|
||||
"library/dictionary"
|
||||
],
|
||||
"target": [
|
||||
"dir",
|
||||
"zip"
|
||||
|
@ -67,18 +67,6 @@ fi
|
||||
NODE_VERSION=$(node --version)
|
||||
echo "Executing using Node Version: ${NODE_VERSION}"
|
||||
|
||||
# We need to include swift search libraries to build SDA
|
||||
if [ ! -d "$HOME/tronlibraries/library" ]; then
|
||||
echo 'Search libraries do not exist! Not building with swift search' >&2
|
||||
else
|
||||
cp -r "$HOME/tronlibraries/library" .
|
||||
echo 'Copied search libraries'
|
||||
ls -lrth $HOME/tronlibraries/library
|
||||
fi
|
||||
|
||||
codesign --force --options runtime -s "Developer ID Application: Symphony Communication Services LLC" library/lz4.exec
|
||||
codesign --force --options runtime -s "Developer ID Application: Symphony Communication Services LLC" library/indexvalidator.exec
|
||||
|
||||
PKG_VERSION=$(node -e "console.log(require('./package.json').version);")
|
||||
|
||||
# Install app dependencies
|
||||
|
@ -48,9 +48,6 @@ call snyk config set api=%SNYK_API_TOKEN%
|
||||
:: Below command replaces buildVersion with the appropriate build number from jenkins
|
||||
sed -i -e "s/\"buildNumber\"[[:space:]]*\:[[:space:]]*\".*\"/\"buildNumber\":\"%PARENT_BUILD_VERSION%\"/g" package.json
|
||||
|
||||
:: Copy search libraries onto the project root
|
||||
echo "Copying search libraries"
|
||||
echo D | xcopy /y "C:\jenkins\workspace\tronlibraries\library" "library"
|
||||
|
||||
echo "Installing dependencies..."
|
||||
call npm install
|
||||
|
@ -41,10 +41,6 @@ call snyk config set api=%SNYK_API_TOKEN%
|
||||
sed -i -e "s/\"buildNumber\"[[:space:]]*\:[[:space:]]*\".*\"/\"buildNumber\": \"%PARENT_BUILD_VERSION%\"/g" package.json
|
||||
sed -i -e "s/\"version\"[[:space:]]*\:[[:space:]]\"\(.*\)\"/\"version\": \"\1-%PARENT_BUILD_VERSION%\"/g" package.json
|
||||
|
||||
:: Copy search libraries onto the project root
|
||||
echo "Copying search libraries"
|
||||
echo D | xcopy /y "C:\jenkins\workspace\tronlibraries\library" "library"
|
||||
|
||||
echo "Installing dependencies..."
|
||||
call npm install
|
||||
|
||||
@ -138,23 +134,6 @@ IF %errorlevel% neq 0 (
|
||||
exit /b -1
|
||||
)
|
||||
|
||||
call %SIGNING_FILE_PATH% ..\..\library\indexvalidator-x64.exe
|
||||
IF %errorlevel% neq 0 (
|
||||
echo "Signing failed"
|
||||
exit /b -1
|
||||
)
|
||||
|
||||
call %SIGNING_FILE_PATH% ..\..\library\lz4-win-x64.exe
|
||||
IF %errorlevel% neq 0 (
|
||||
echo "Signing failed"
|
||||
exit /b -1
|
||||
)
|
||||
|
||||
call %SIGNING_FILE_PATH% ..\..\library\tar-win.exe
|
||||
IF %errorlevel% neq 0 (
|
||||
echo "Signing failed"
|
||||
exit /b -1
|
||||
)
|
||||
|
||||
node ..\..\scripts\windows_update_checksum.js "..\..\dist\Symphony-%SYMVER%-win.exe" "..\..\dist\latest.yml"
|
||||
|
||||
|
@ -53,6 +53,7 @@ import { getCommandLineArgs } from '../common/utils';
|
||||
import { autoUpdate, AutoUpdateTrigger } from './auto-update-handler';
|
||||
import { presenceStatus } from './presence-status-handler';
|
||||
import { presenceStatusStore } from './stores/index';
|
||||
import { voiceHandler } from './voice-handler';
|
||||
|
||||
// Swift search API
|
||||
let swiftSearchInstance;
|
||||
@ -492,7 +493,8 @@ ipcMain.on(
|
||||
autoUpdate.checkUpdates();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
case apiCmds.registerVoiceServices:
|
||||
voiceHandler.registerSymphonyAsDefaultCallApp();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
@ -83,7 +83,6 @@ const startApplication = async () => {
|
||||
await config.initializeUserConfig();
|
||||
await config.readUserConfig();
|
||||
await config.checkFirstTimeLaunch();
|
||||
|
||||
if (config.isFirstTimeLaunch()) {
|
||||
logger.info(
|
||||
`main: This is a first time launch! will update config and handle auto launch`,
|
||||
|
@ -10,6 +10,7 @@ import { windowHandler } from './window-handler';
|
||||
|
||||
enum protocol {
|
||||
SymphonyProtocol = 'symphony://',
|
||||
TelProtocol = 'tel:',
|
||||
}
|
||||
|
||||
class ProtocolHandler {
|
||||
@ -31,12 +32,12 @@ class ProtocolHandler {
|
||||
public setPreloadWebContents(webContents: WebContents): void {
|
||||
this.preloadWebContents = webContents;
|
||||
logger.info(
|
||||
`protocol handler: SFE is active and we have a valid protocol window with web contents!`,
|
||||
`protocol-handler: SFE is active and we have a valid protocol window with web contents!`,
|
||||
);
|
||||
if (this.protocolUri) {
|
||||
this.sendProtocol(this.protocolUri);
|
||||
logger.info(
|
||||
`protocol handler: we have a cached url ${this.protocolUri}, so, processed the request to SFE!`,
|
||||
`protocol-handler: we have a cached url ${this.protocolUri}, so, processed the request to SFE!`,
|
||||
);
|
||||
this.protocolUri = null;
|
||||
}
|
||||
@ -59,7 +60,7 @@ class ProtocolHandler {
|
||||
return;
|
||||
}
|
||||
logger.info(
|
||||
`protocol handler: processing protocol request for the url ${url}!`,
|
||||
`protocol-handler: processing protocol request for the url ${url}!`,
|
||||
);
|
||||
// Handle protocol for Seamless login
|
||||
if (url?.includes('skey') && url?.includes('anticsrf')) {
|
||||
@ -69,7 +70,7 @@ class ProtocolHandler {
|
||||
|
||||
if (!this.preloadWebContents || !isAppRunning) {
|
||||
logger.info(
|
||||
`protocol handler: app was started from the protocol request. Caching the URL ${url}!`,
|
||||
`protocol-handler: app was started from the protocol request. Caching the URL ${url}!`,
|
||||
);
|
||||
this.protocolUri = url;
|
||||
return;
|
||||
@ -83,9 +84,14 @@ class ProtocolHandler {
|
||||
|
||||
if (ProtocolHandler.isValidProtocolUri(url)) {
|
||||
logger.info(
|
||||
`protocol handler: our protocol request is a valid url ${url}! sending request to SFE for further action!`,
|
||||
`protocol-handler: our protocol request is a valid url ${url}! sending request to SFE for further action!`,
|
||||
);
|
||||
this.preloadWebContents.send('protocol-action', url);
|
||||
} else if (url?.includes('tel:')) {
|
||||
this.preloadWebContents.send(
|
||||
'phone-number-received',
|
||||
url.split('tel:')[1],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -95,17 +101,27 @@ class ProtocolHandler {
|
||||
* @param argv {String[]} - data received from process.argv
|
||||
*/
|
||||
public processArgv(argv?: string[], isAppAlreadyOpen: boolean = false): void {
|
||||
logger.info(`protocol handler: processing protocol args!`);
|
||||
logger.info(`protocol-handler: processing protocol args!`);
|
||||
const protocolUriFromArgv = getCommandLineArgs(
|
||||
argv || process.argv,
|
||||
protocol.SymphonyProtocol,
|
||||
false,
|
||||
);
|
||||
const telArgFromArgv = getCommandLineArgs(
|
||||
argv || process.argv,
|
||||
protocol.TelProtocol,
|
||||
false,
|
||||
);
|
||||
if (protocolUriFromArgv) {
|
||||
logger.info(
|
||||
`protocol handler: we have a protocol request for the url ${protocolUriFromArgv}!`,
|
||||
`protocol-handler: we have a protocol request for the url ${protocolUriFromArgv}!`,
|
||||
);
|
||||
this.sendProtocol(protocolUriFromArgv, isAppAlreadyOpen);
|
||||
} else if (telArgFromArgv) {
|
||||
logger.info(
|
||||
`protocol-handler: we have a tel request for ${telArgFromArgv}!`,
|
||||
);
|
||||
this.sendProtocol(telArgFromArgv, isAppAlreadyOpen);
|
||||
}
|
||||
}
|
||||
|
||||
|
222
src/app/voice-handler.ts
Normal file
222
src/app/voice-handler.ts
Normal file
@ -0,0 +1,222 @@
|
||||
import { exec } from 'child_process';
|
||||
import { app } from 'electron';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { isDevEnv, isMac, isWindowsOS } from '../common/env';
|
||||
import { logger } from '../common/logger';
|
||||
|
||||
const LS_REGISTER_PATH =
|
||||
'/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister';
|
||||
const SYM_TEL_PROTOCOL_PLIST_ENTRY = {
|
||||
LSHandlerURLScheme: 'tel',
|
||||
LSHandlerRoleAll: 'com.symphony.electron-desktop',
|
||||
LSHandlerPreferredVersions: {
|
||||
LSHandlerRoleAll: '-',
|
||||
},
|
||||
};
|
||||
const DEFAULT_TEL_PROTOCOL = 'tel';
|
||||
enum REGISTRY_PATHS {
|
||||
Capabilities = '\\Software\\Symphony\\Capabilities',
|
||||
UrlRegistration = '\\Software\\Symphony\\Capabilities\\URLAssociations',
|
||||
SymTelCmd = '\\Software\\Classes\\Symphony.tel\\shell\\open\\command',
|
||||
SymTelDefaultIcon = '\\Software\\Classes\\Symphony.tel\\DefaultIcon',
|
||||
RegisteredApps = '\\Software\\RegisteredApplications',
|
||||
}
|
||||
|
||||
class VoiceHandler {
|
||||
/**
|
||||
* Registers Symphony as phone calls app
|
||||
*/
|
||||
public registerSymphonyAsDefaultCallApp() {
|
||||
if (isWindowsOS) {
|
||||
this.registerAppOnWindows();
|
||||
} else if (isMac) {
|
||||
this.registerAppOnMacOS();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers app on Windows
|
||||
*/
|
||||
private async registerAppOnWindows() {
|
||||
const Registry = require('winreg');
|
||||
const appPath = isDevEnv
|
||||
? path.join(path.dirname(app.getPath('exe')), 'Electron.exe')
|
||||
: path.join(path.dirname(app.getPath('exe')), 'Symphony.exe');
|
||||
const applicationCapabilitiesRegKey = new Registry({
|
||||
hive: Registry.HKCU,
|
||||
key: REGISTRY_PATHS.Capabilities,
|
||||
});
|
||||
const symURLAssociationRegKey = new Registry({
|
||||
hive: Registry.HKCU,
|
||||
key: REGISTRY_PATHS.UrlRegistration,
|
||||
});
|
||||
const symTelCommandRegKey = new Registry({
|
||||
hive: Registry.HKCU,
|
||||
key: REGISTRY_PATHS.SymTelCmd,
|
||||
});
|
||||
const symTelDefaultIconRegKey = new Registry({
|
||||
hive: Registry.HKCU,
|
||||
key: REGISTRY_PATHS.SymTelDefaultIcon,
|
||||
});
|
||||
const symAppRegistrationRegKey = new Registry({
|
||||
hive: Registry.HKCU,
|
||||
key: REGISTRY_PATHS.RegisteredApps,
|
||||
});
|
||||
const errorCallback = (error) => {
|
||||
if (error) {
|
||||
logger.error(
|
||||
'voice-handler: error while creating voice registry keys: ',
|
||||
error,
|
||||
);
|
||||
}
|
||||
};
|
||||
await applicationCapabilitiesRegKey.set(
|
||||
'ApplicationName',
|
||||
Registry.REG_SZ,
|
||||
'Symphony',
|
||||
errorCallback,
|
||||
);
|
||||
await applicationCapabilitiesRegKey.set(
|
||||
'ApplicationDescription',
|
||||
Registry.REG_SZ,
|
||||
'Symphony',
|
||||
errorCallback,
|
||||
);
|
||||
await symURLAssociationRegKey.set(
|
||||
'tel',
|
||||
Registry.REG_SZ,
|
||||
'Symphony.tel',
|
||||
errorCallback,
|
||||
);
|
||||
await symTelDefaultIconRegKey.set(
|
||||
'',
|
||||
Registry.REG_SZ,
|
||||
appPath,
|
||||
errorCallback,
|
||||
);
|
||||
await symTelCommandRegKey.set(
|
||||
'',
|
||||
Registry.REG_SZ,
|
||||
`"${appPath}" "%1"`,
|
||||
errorCallback,
|
||||
);
|
||||
await symAppRegistrationRegKey.set(
|
||||
'Symphony',
|
||||
Registry.REG_SZ,
|
||||
'Software\\Symphony\\Capabilities',
|
||||
errorCallback,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers app on macOS
|
||||
*/
|
||||
private registerAppOnMacOS() {
|
||||
this.readLaunchServicesPlist((res, _err) => {
|
||||
const data = res;
|
||||
const itemIdx = data.findIndex(
|
||||
(item) => item.LSHandlerURLScheme === DEFAULT_TEL_PROTOCOL,
|
||||
);
|
||||
// macOS allows only one app being declared as able to make calls
|
||||
if (itemIdx !== -1) {
|
||||
data.splice(itemIdx, 1);
|
||||
}
|
||||
data.push(SYM_TEL_PROTOCOL_PLIST_ENTRY);
|
||||
this.updateLaunchServicesPlist(data);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads macOS launch services plist
|
||||
* @param callback
|
||||
*/
|
||||
private readLaunchServicesPlist(callback) {
|
||||
const plistPath = this.getLaunchServicesPlistPath();
|
||||
const tmpPath = `${plistPath}.${Math.random()}`;
|
||||
exec(`plutil -convert json "${plistPath}" -o "${tmpPath}"`, (err) => {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
fs.readFile(tmpPath, (readErr, data) => {
|
||||
if (readErr) {
|
||||
callback(readErr);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const json = JSON.parse(data.toString());
|
||||
callback(json.LSHandlers, json);
|
||||
fs.unlink(tmpPath, (err) => {
|
||||
logger.error('Error: ', err);
|
||||
});
|
||||
} catch (e) {
|
||||
callback(e);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates launch services plist file
|
||||
* @param defaults
|
||||
*/
|
||||
private updateLaunchServicesPlist(defaults) {
|
||||
const plistPath = this.getLaunchServicesPlistPath();
|
||||
const tmpPath = `${plistPath}.${Math.random()}`;
|
||||
exec(`plutil -convert json "${plistPath}" -o "${tmpPath}"`, (err) => {
|
||||
if (err) {
|
||||
logger.error('voice-handler: error while converting plist ', err);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
let data = fs.readFileSync(tmpPath).toString();
|
||||
data = JSON.parse(data);
|
||||
(data as any).LSHandlers = defaults;
|
||||
data = JSON.stringify(data);
|
||||
fs.writeFileSync(tmpPath, data);
|
||||
} catch (e) {
|
||||
logger.error('voice-handler: error while converting plist ', err);
|
||||
return;
|
||||
}
|
||||
exec(`plutil -convert binary1 "${tmpPath}" -o "${plistPath}"`, () => {
|
||||
fs.unlink(tmpPath, (err) => {
|
||||
logger.error(`voice-handler: error while clearing ${tmpPath}: `, err);
|
||||
});
|
||||
// Relaunch Launch Services so it take into consideration updated plist file
|
||||
exec(
|
||||
`${LS_REGISTER_PATH} -kill -r -domain local -domain system -domain user`,
|
||||
(registerErr) => {
|
||||
if (registerErr) {
|
||||
logger.error(
|
||||
'voice-handler: error relaunching Launch Services ',
|
||||
registerErr,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Launch services plist filepath
|
||||
* @param callback
|
||||
*/
|
||||
private getLaunchServicesPlistPath() {
|
||||
const secureLaunchServicesPlist = `${process.env.HOME}/Library/Preferences/com.apple.LaunchServices/com.apple.launchservices.secure.plist`;
|
||||
const insecureLaunchServicesPlist = `${process.env.HOME}/Library/Preferences/com.apple.LaunchServices.plist`;
|
||||
|
||||
const secureLaunchServicesPlistExists = fs.existsSync(
|
||||
secureLaunchServicesPlist,
|
||||
);
|
||||
if (secureLaunchServicesPlistExists) {
|
||||
return secureLaunchServicesPlist;
|
||||
}
|
||||
return insecureLaunchServicesPlist;
|
||||
}
|
||||
}
|
||||
|
||||
const voiceHandler = new VoiceHandler();
|
||||
|
||||
export { voiceHandler };
|
@ -547,6 +547,7 @@ export const handlePermissionRequests = (webContents: WebContents): void => {
|
||||
case Permissions.OPEN_EXTERNAL:
|
||||
if (
|
||||
details?.externalURL?.startsWith('symphony:') ||
|
||||
details?.externalURL?.startsWith('tel:') ||
|
||||
details?.externalURL?.startsWith('mailto:')
|
||||
) {
|
||||
return callback(true);
|
||||
|
@ -76,6 +76,7 @@ export enum apiCmds {
|
||||
updateMyPresence = 'update-my-presence',
|
||||
getMyPresence = 'get-my-presence',
|
||||
updateSymphonyTray = 'update-system-tray',
|
||||
registerVoiceServices = 'register-voice-services',
|
||||
}
|
||||
|
||||
export enum apiName {
|
||||
|
@ -99,6 +99,7 @@ if (ssfWindow.ssf) {
|
||||
checkForUpdates: ssfWindow.ssf.checkForUpdates,
|
||||
updateMyPresence: ssfWindow.ssf.updateMyPresence,
|
||||
getMyPresence: ssfWindow.ssf.getMyPresence,
|
||||
registerVoiceServices: ssfWindow.ssf.registerVoiceServices,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -61,6 +61,7 @@ export interface ILocalObject {
|
||||
c9PipeEventCallback?: (event: string, arg?: any) => void;
|
||||
c9MessageCallback?: (status: IShellStatus) => void;
|
||||
updateMyPresenceCallback?: (presence: EPresenceStatusCategory) => void;
|
||||
phoneNumberCallback?: (arg: string) => void;
|
||||
}
|
||||
|
||||
const local: ILocalObject = {
|
||||
@ -876,6 +877,21 @@ export class SSFApi {
|
||||
autoUpdateTrigger,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows JS to register SDA for calls
|
||||
* @param {Function} phoneNumberCallback callback function invoked when receiving a phone number
|
||||
*/
|
||||
public registerVoiceServices(
|
||||
phoneNumberCallback: (arg: string) => void,
|
||||
): void {
|
||||
if (typeof phoneNumberCallback === 'function') {
|
||||
local.phoneNumberCallback = phoneNumberCallback;
|
||||
}
|
||||
ipcRenderer.send(apiName.symphonyApi, {
|
||||
cmd: apiCmds.registerVoiceServices,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1145,6 +1161,24 @@ local.ipcRenderer.on('c9-status-event', (_event, args) => {
|
||||
local.c9MessageCallback?.call(null, args?.status);
|
||||
});
|
||||
|
||||
/**
|
||||
* An event triggered by the main process
|
||||
* to forward clicked phone number
|
||||
*
|
||||
* @param {string} phoneNumber - phone number received by SDA
|
||||
*/
|
||||
local.ipcRenderer.on(
|
||||
'phone-number-received',
|
||||
(_event: Event, phoneNumber: string) => {
|
||||
if (
|
||||
typeof phoneNumber === 'string' &&
|
||||
typeof local.phoneNumberCallback === 'function'
|
||||
) {
|
||||
local.phoneNumberCallback(phoneNumber);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Invoked whenever the app is reloaded/navigated
|
||||
const sanitize = (): void => {
|
||||
if (window.name === apiName.mainWindowName) {
|
||||
|
Loading…
Reference in New Issue
Block a user