mirror of
https://github.com/finos/SymphonyElectron.git
synced 2024-12-26 08:51:22 -06:00
SDA-4178 Add support for SMS protocol
This commit is contained in:
parent
028292b6d4
commit
3d5519aea0
@ -484,6 +484,7 @@ public class CustomActions
|
||||
{
|
||||
key.DeleteSubKeyTree("symphony", false);
|
||||
key.DeleteSubKeyTree("Symphony.tel", false);
|
||||
key.DeleteSubKeyTree("Symphony.sms", false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -57,7 +57,7 @@ const verifyProtocolForNewUrl = (url: string): boolean => {
|
||||
return false;
|
||||
}
|
||||
|
||||
const allowedProtocols = ['http:', 'https:', 'mailto:', 'symphony:'];
|
||||
const allowedProtocols = ['http:', 'https:', 'mailto:', 'symphony:', 'sms:'];
|
||||
// url parse returns protocol with :
|
||||
if (allowedProtocols.includes(parsedUrl.protocol)) {
|
||||
logger.info(
|
||||
@ -207,7 +207,8 @@ export const handleChildWindow = (webContents: WebContents): void => {
|
||||
webContents.on(
|
||||
'did-create-window',
|
||||
(browserWindow: BrowserWindow, details: DidCreateWindowDetails) => {
|
||||
const newWinOptions = details.options as ICustomBrowserWindowConstructorOpts;
|
||||
const newWinOptions =
|
||||
details.options as ICustomBrowserWindowConstructorOpts;
|
||||
const width = newWinOptions.width || DEFAULT_POP_OUT_WIDTH;
|
||||
const height = newWinOptions.height || DEFAULT_POP_OUT_HEIGHT;
|
||||
const newWinKey = getGuid();
|
||||
|
@ -496,8 +496,13 @@ ipcMain.on(
|
||||
autoUpdate.checkUpdates();
|
||||
}
|
||||
break;
|
||||
case apiCmds.registerVoiceServices:
|
||||
voiceHandler.registerSymphonyAsDefaultCallApp();
|
||||
case apiCmds.registerPhoneNumberServices:
|
||||
voiceHandler.registerSymphonyAsDefaultApp(arg.protocols);
|
||||
break;
|
||||
case apiCmds.unregisterPhoneNumberServices:
|
||||
voiceHandler.unregisterSymphonyAsDefaultApp(arg.protocols);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
@ -11,6 +11,7 @@ import { windowHandler } from './window-handler';
|
||||
enum protocol {
|
||||
SymphonyProtocol = 'symphony://',
|
||||
TelProtocol = 'tel:',
|
||||
SmsProtocol = 'sms:',
|
||||
}
|
||||
|
||||
class ProtocolHandler {
|
||||
@ -87,11 +88,8 @@ class ProtocolHandler {
|
||||
`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],
|
||||
);
|
||||
} else if (url?.includes('tel:') || url?.includes('sms:')) {
|
||||
this.preloadWebContents.send('phone-number-received', url);
|
||||
}
|
||||
}
|
||||
|
||||
@ -112,6 +110,11 @@ class ProtocolHandler {
|
||||
protocol.TelProtocol,
|
||||
false,
|
||||
);
|
||||
const smsArgFromArgv = getCommandLineArgs(
|
||||
argv || process.argv,
|
||||
protocol.SmsProtocol,
|
||||
false,
|
||||
);
|
||||
if (protocolUriFromArgv) {
|
||||
logger.info(
|
||||
`protocol-handler: we have a protocol request for the url ${protocolUriFromArgv}!`,
|
||||
@ -122,6 +125,11 @@ class ProtocolHandler {
|
||||
`protocol-handler: we have a tel request for ${telArgFromArgv}!`,
|
||||
);
|
||||
this.sendProtocol(telArgFromArgv, isAppAlreadyOpen);
|
||||
} else if (smsArgFromArgv) {
|
||||
logger.info(
|
||||
`protocol-handler: we have an sms request for ${smsArgFromArgv}!`,
|
||||
);
|
||||
this.sendProtocol(smsArgFromArgv, isAppAlreadyOpen);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,43 +2,49 @@ import { exec } from 'child_process';
|
||||
import { app } from 'electron';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { PhoneNumberProtocol } from '../common/api-interface';
|
||||
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 {
|
||||
Classes = '\\Software\\Classes',
|
||||
Capabilities = '\\Software\\Symphony\\Capabilities',
|
||||
UrlRegistration = '\\Software\\Symphony\\Capabilities\\URLAssociations',
|
||||
SymTelCmd = '\\Software\\Classes\\Symphony.tel\\shell\\open\\command',
|
||||
SymSmsCmd = '\\Software\\Classes\\Symphony.sms\\shell\\open\\command',
|
||||
SymTelDefaultIcon = '\\Software\\Classes\\Symphony.tel\\DefaultIcon',
|
||||
SymSmsDefaultIcon = '\\Software\\Classes\\Symphony.sms\\DefaultIcon',
|
||||
RegisteredApps = '\\Software\\RegisteredApplications',
|
||||
}
|
||||
|
||||
class VoiceHandler {
|
||||
/**
|
||||
* Registers Symphony as phone calls app
|
||||
* Registers Symphony as phone calls/SMS app
|
||||
*/
|
||||
public registerSymphonyAsDefaultCallApp() {
|
||||
public registerSymphonyAsDefaultApp(protocols: PhoneNumberProtocol[]) {
|
||||
if (isWindowsOS) {
|
||||
this.registerAppOnWindows();
|
||||
this.registerAppOnWindows(protocols);
|
||||
} else if (isMac) {
|
||||
this.registerAppOnMacOS();
|
||||
this.registerAppOnMacOS(protocols);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Unregisters Symphony as phone calls/SMS app
|
||||
*/
|
||||
public unregisterSymphonyAsDefaultApp(protocols: PhoneNumberProtocol[]) {
|
||||
if (isWindowsOS) {
|
||||
this.unregisterAppOnWindows(protocols);
|
||||
} else if (isMac) {
|
||||
this.unregisterAppOnMacOS(protocols);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers app on Windows
|
||||
*/
|
||||
private async registerAppOnWindows() {
|
||||
private async registerAppOnWindows(protocols: PhoneNumberProtocol[]) {
|
||||
const Registry = require('winreg');
|
||||
const appPath = isDevEnv
|
||||
? path.join(path.dirname(app.getPath('exe')), 'Electron.exe')
|
||||
@ -51,79 +57,166 @@ class VoiceHandler {
|
||||
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,
|
||||
});
|
||||
for (const protocol of protocols) {
|
||||
const keys = this.getProtocolSpecificKeys(protocol);
|
||||
|
||||
const symCommandRegKey = new Registry({
|
||||
hive: Registry.HKCU,
|
||||
key: keys.symCommandKey,
|
||||
});
|
||||
const symDefaultIconRegKey = new Registry({
|
||||
hive: Registry.HKCU,
|
||||
key: keys.symDefaultIconKey,
|
||||
});
|
||||
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(
|
||||
protocol,
|
||||
Registry.REG_SZ,
|
||||
`Symphony.${protocol}`,
|
||||
errorCallback,
|
||||
);
|
||||
await symDefaultIconRegKey.set(
|
||||
'',
|
||||
Registry.REG_SZ,
|
||||
appPath,
|
||||
errorCallback,
|
||||
);
|
||||
await symCommandRegKey.set(
|
||||
'',
|
||||
Registry.REG_SZ,
|
||||
`"${appPath}" "%1"`,
|
||||
errorCallback,
|
||||
);
|
||||
await symAppRegistrationRegKey.set(
|
||||
'Symphony',
|
||||
Registry.REG_SZ,
|
||||
'Software\\Symphony\\Capabilities',
|
||||
errorCallback,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Windows call/sms specific registry keys calls/SMS
|
||||
*/
|
||||
private getProtocolSpecificKeys(protocol) {
|
||||
const keys = {
|
||||
symCommandKey: '',
|
||||
symDefaultIconKey: '',
|
||||
};
|
||||
switch (protocol) {
|
||||
case PhoneNumberProtocol.Sms:
|
||||
keys.symCommandKey = REGISTRY_PATHS.SymSmsCmd;
|
||||
keys.symDefaultIconKey = REGISTRY_PATHS.SymSmsDefaultIcon;
|
||||
break;
|
||||
case PhoneNumberProtocol.Tel:
|
||||
keys.symCommandKey = REGISTRY_PATHS.SymTelCmd;
|
||||
keys.symDefaultIconKey = REGISTRY_PATHS.SymTelDefaultIcon;
|
||||
break;
|
||||
default:
|
||||
logger.info('voice-handler: unsupported protocol');
|
||||
break;
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters tel / sms protocols on Windows
|
||||
*/
|
||||
private async unregisterAppOnWindows(protocols: PhoneNumberProtocol[]) {
|
||||
const Registry = require('winreg');
|
||||
const symURLAssociationRegKey = new Registry({
|
||||
hive: Registry.HKCU,
|
||||
key: REGISTRY_PATHS.UrlRegistration,
|
||||
});
|
||||
|
||||
const errorCallback = (error) => {
|
||||
if (error) {
|
||||
logger.error(
|
||||
'voice-handler: error while creating voice registry keys: ',
|
||||
'voice-handler: error while removing 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,
|
||||
);
|
||||
const DestroyErrorCallback = (error) => {
|
||||
if (error) {
|
||||
logger.error(
|
||||
'voice-handler: error while destroying voice registry keys: ',
|
||||
error,
|
||||
);
|
||||
}
|
||||
};
|
||||
for (const protocol of protocols) {
|
||||
await symURLAssociationRegKey.remove(protocol, errorCallback);
|
||||
const symprotocolClassRegKey = new Registry({
|
||||
hive: Registry.HKCU,
|
||||
key: `${REGISTRY_PATHS.Classes}\\Symphony.${protocol}`,
|
||||
});
|
||||
await symprotocolClassRegKey.destroy(DestroyErrorCallback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
private registerAppOnMacOS(protocols: PhoneNumberProtocol[]) {
|
||||
this.readLaunchServicesPlist((plist) => {
|
||||
for (const protocol of protocols) {
|
||||
const itemIdx = plist.LSHandlers.findIndex(
|
||||
(lsHandler) => lsHandler.LSHandlerURLScheme === protocol,
|
||||
);
|
||||
// macOS allows only one app being declared as able to make calls
|
||||
if (itemIdx !== -1) {
|
||||
plist.splice(itemIdx, 1);
|
||||
}
|
||||
const plistEntry = {
|
||||
LSHandlerURLScheme: protocol,
|
||||
LSHandlerRoleAll: 'com.symphony.electron-desktop',
|
||||
LSHandlerPreferredVersions: {
|
||||
LSHandlerRoleAll: '-',
|
||||
},
|
||||
};
|
||||
plist.LSHandlers.push(plistEntry);
|
||||
}
|
||||
this.updateLaunchServicesPlist(plist);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters app for tel/sms on macOS
|
||||
*/
|
||||
private unregisterAppOnMacOS(protocols: PhoneNumberProtocol[]) {
|
||||
this.readLaunchServicesPlist((plist) => {
|
||||
if (plist) {
|
||||
const filteredList = plist.LSHandlers.filter(
|
||||
(lsHandler) => protocols.indexOf(lsHandler.LSHandlerURLScheme) === -1,
|
||||
);
|
||||
plist.LSHandlers = filteredList;
|
||||
this.updateLaunchServicesPlist(plist);
|
||||
}
|
||||
data.push(SYM_TEL_PROTOCOL_PLIST_ENTRY);
|
||||
this.updateLaunchServicesPlist(data);
|
||||
});
|
||||
}
|
||||
|
||||
@ -136,22 +229,28 @@ class VoiceHandler {
|
||||
const tmpPath = `${plistPath}.${Math.random()}`;
|
||||
exec(`plutil -convert json "${plistPath}" -o "${tmpPath}"`, (err) => {
|
||||
if (err) {
|
||||
callback(err);
|
||||
logger.error(
|
||||
'voice-handler: error while converting binary file: ',
|
||||
err,
|
||||
);
|
||||
return;
|
||||
}
|
||||
fs.readFile(tmpPath, (readErr, data) => {
|
||||
if (readErr) {
|
||||
callback(readErr);
|
||||
logger.error('voice-handler: error while reading tmp file:');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const json = JSON.parse(data.toString());
|
||||
callback(json.LSHandlers, json);
|
||||
const plistContent = JSON.parse(data.toString());
|
||||
callback(plistContent);
|
||||
fs.unlink(tmpPath, (err) => {
|
||||
logger.error('Error: ', err);
|
||||
if (err) {
|
||||
logger.error('voice-handler: error clearing tmp file ', err);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
callback(e);
|
||||
logger.error('voice-handler: unexpected error occured ', err);
|
||||
return;
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -164,38 +263,30 @@ class VoiceHandler {
|
||||
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) => {
|
||||
try {
|
||||
fs.writeFileSync(tmpPath, JSON.stringify(defaults));
|
||||
} catch (e) {
|
||||
logger.error('voice-handler: error while creating tmp plist ', e);
|
||||
return;
|
||||
}
|
||||
exec(`plutil -convert binary1 "${tmpPath}" -o "${plistPath}"`, () => {
|
||||
fs.unlink(tmpPath, (err) => {
|
||||
if (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,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
// 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,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -76,7 +76,8 @@ export enum apiCmds {
|
||||
updateMyPresence = 'update-my-presence',
|
||||
getMyPresence = 'get-my-presence',
|
||||
updateSymphonyTray = 'update-system-tray',
|
||||
registerVoiceServices = 'register-voice-services',
|
||||
registerPhoneNumberServices = 'register-phone-numbers-services',
|
||||
unregisterPhoneNumberServices = 'unregister-phone-numbers-services',
|
||||
}
|
||||
|
||||
export enum apiName {
|
||||
@ -137,6 +138,7 @@ export interface IApiArgs {
|
||||
autoUpdateTrigger: AutoUpdateTrigger;
|
||||
hideOnCapture: boolean;
|
||||
status: IPresenceStatus;
|
||||
protocols: PhoneNumberProtocol[];
|
||||
}
|
||||
|
||||
export type Themes = 'light' | 'dark';
|
||||
@ -376,3 +378,8 @@ export interface IAuthResponse {
|
||||
ssoDisabledForMobile: boolean;
|
||||
keymanagerUrl: string;
|
||||
}
|
||||
|
||||
export enum PhoneNumberProtocol {
|
||||
Tel = 'tel',
|
||||
Sms = 'sms',
|
||||
}
|
||||
|
@ -99,7 +99,8 @@ if (ssfWindow.ssf) {
|
||||
checkForUpdates: ssfWindow.ssf.checkForUpdates,
|
||||
updateMyPresence: ssfWindow.ssf.updateMyPresence,
|
||||
getMyPresence: ssfWindow.ssf.getMyPresence,
|
||||
registerVoiceServices: ssfWindow.ssf.registerVoiceServices,
|
||||
registerPhoneNumberServices: ssfWindow.ssf.registerPhoneNumberServices,
|
||||
unregisterPhoneNumberServices: ssfWindow.ssf.unregisterPhoneNumberServices,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -30,6 +30,7 @@ import {
|
||||
KeyCodes,
|
||||
LogLevel,
|
||||
NotificationActionCallback,
|
||||
PhoneNumberProtocol,
|
||||
} from '../common/api-interface';
|
||||
import { i18n, LocaleType } from '../common/i18n-preload';
|
||||
import { throttle } from '../common/utils';
|
||||
@ -879,17 +880,30 @@ export class SSFApi {
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows JS to register SDA for calls
|
||||
* @param {Function} phoneNumberCallback callback function invoked when receiving a phone number
|
||||
* Allows JS to register SDA for phone numbers clicks
|
||||
* @param {Function} phoneNumberCallback callback function invoked when receiving a phone number for calls/sms
|
||||
*/
|
||||
public registerVoiceServices(
|
||||
public registerPhoneNumberServices(
|
||||
protocols: PhoneNumberProtocol[],
|
||||
phoneNumberCallback: (arg: string) => void,
|
||||
): void {
|
||||
if (typeof phoneNumberCallback === 'function') {
|
||||
local.phoneNumberCallback = phoneNumberCallback;
|
||||
}
|
||||
ipcRenderer.send(apiName.symphonyApi, {
|
||||
cmd: apiCmds.registerVoiceServices,
|
||||
cmd: apiCmds.registerPhoneNumberServices,
|
||||
protocols,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows JS to unregister app to sms/call protocols
|
||||
* @param protocol protocol to be unregistered.
|
||||
*/
|
||||
public unregisterPhoneNumberServices(protocols: PhoneNumberProtocol[]) {
|
||||
ipcRenderer.send(apiName.symphonyApi, {
|
||||
cmd: apiCmds.unregisterPhoneNumberServices,
|
||||
protocols,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user