SDA-2547 (Upgrade electron version to 14.0.1) (#1267)

* SDA-2547 - Upgrade electron version to 14.0.1

* SDA-2547 - refactor and fix unit tests

* SDA-2555 - Move custom title bar away from remote module

* SDA-2555 - Update API new-window to setWindowOpenHandler and fix issues

* SDA-2555 - Arrange imports

* SDA-2555 - Fix unit tests

* SDA-3387 - Fixed reload, native notification issues & finally removed the SFE CSS injection 🎉

* SDA-2547 - Fix fullscreen state on Windows

* SDA-2552 - Update version info

* SDA-2548 - Fix media permission

* SDA-2547 - Get app name from package.json
This commit is contained in:
Kiran Niranjan 2021-10-20 13:10:58 +05:30 committed by GitHub
parent 962eb0886c
commit e7f4470d9c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 1487 additions and 761 deletions

View File

@ -1,7 +1,7 @@
## Prerequisites
### Windows
- NodeJS version >= 12.x.y (corresponds to electron 9.x.y)
- NodeJS version >= 14.x.y (corresponds to electron 14.x.y)
- Microsoft Visual Studio 2017 Community or Paid (C++ and .NET/C# development tools)
- Python >= 2.7.1
- Dot Net 3.5 SP1 or later
@ -13,7 +13,7 @@
### Mac
- Xcode command line tools. Or better, Xcode latest version
- NodeJS version >= 12.x.y (corresponds to electron 9.x.y)
- NodeJS version >= 14.x.y (corresponds to electron 14.x.y)
- [Sudre Packages](http://s.sudre.free.fr/Software/Packages/about.html)
#### Notes
@ -35,7 +35,7 @@ npm install
npm run prebuild
# Run against a POD
cross-env ELECTRON_DEV=true electron . --url=https://corporate.symphony.com
npm run dev -- --url=https://corporate.symphony.com
# Run the demo app
npm run demo

476
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "symphony",
"version": "9.3.0",
"version": "14.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -1163,9 +1163,9 @@
}
},
"@electron/get": {
"version": "1.12.4",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/@electron/get/-/get-1.12.4.tgz",
"integrity": "sha1-pZcRE/wb+PoSqHidwgFSpzWfBqs=",
"version": "1.13.0",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/@electron/get/-/get-1.13.0.tgz",
"integrity": "sha1-lca8r/T5pQXqRnkkJPRR7+qJIow=",
"dev": true,
"requires": {
"debug": "^4.1.1",
@ -1179,6 +1179,12 @@
"sumchecker": "^3.0.1"
}
},
"@electron/remote": {
"version": "1.2.2",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/@electron/remote/-/remote-1.2.2.tgz",
"integrity": "sha1-TDkKLmad9Hr5c8Ce7BBhYqKWwyM=",
"dev": true
},
"@felixrieseberg/spellchecker": {
"version": "4.0.12",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/@felixrieseberg/spellchecker/-/spellchecker-4.0.12.tgz",
@ -1673,9 +1679,9 @@
}
},
"@types/cacheable-request": {
"version": "6.0.1",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/@types/cacheable-request/-/cacheable-request-6.0.1.tgz",
"integrity": "sha1-XSLz3e0f06hMC761A5p0GcLJGXY=",
"version": "6.0.2",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/@types/cacheable-request/-/cacheable-request-6.0.2.tgz",
"integrity": "sha1-wyTaAZfeCpiiMSFWU2riYkKf9rk=",
"dev": true,
"requires": {
"@types/http-cache-semantics": "*",
@ -1755,9 +1761,9 @@
}
},
"@types/http-cache-semantics": {
"version": "4.0.0",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz",
"integrity": "sha1-kUB3lzaqJlVjXudW4kZ9eHz+iio=",
"version": "4.0.1",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz",
"integrity": "sha1-Dqe2FJaQK5WJDcTDoRa2DLja6BI=",
"dev": true
},
"@types/istanbul-lib-coverage": {
@ -1792,9 +1798,9 @@
"dev": true
},
"@types/keyv": {
"version": "3.1.1",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/@types/keyv/-/keyv-3.1.1.tgz",
"integrity": "sha1-5FpFMk/KnatxarEjDuJJyftSz6c=",
"version": "3.1.3",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/@types/keyv/-/keyv-3.1.3.tgz",
"integrity": "sha1-HJquMocuwfINza7omp87qI9GXkE=",
"dev": true,
"requires": {
"@types/node": "*"
@ -1851,9 +1857,9 @@
"dev": true
},
"@types/puppeteer": {
"version": "5.4.3",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/@types/puppeteer/-/puppeteer-5.4.3.tgz",
"integrity": "sha1-zcqEqndR13RI2KR32/oK8fEUhfI=",
"version": "5.4.4",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/@types/puppeteer/-/puppeteer-5.4.4.tgz",
"integrity": "sha1-6Sq+zMT0YgfD4bOJNKEka+CAzNA=",
"dev": true,
"requires": {
"@types/node": "*"
@ -1959,9 +1965,9 @@
"dev": true
},
"@types/yauzl": {
"version": "2.9.1",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/@types/yauzl/-/yauzl-2.9.1.tgz",
"integrity": "sha1-0Q9p+fUi7vPPmOMK+2hKHh7JI68=",
"version": "2.9.2",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/@types/yauzl/-/yauzl-2.9.2.tgz",
"integrity": "sha1-xI5dVq/xREQJ45+hZLC01FUqe3o=",
"dev": true,
"optional": true,
"requires": {
@ -1992,15 +1998,15 @@
},
"dependencies": {
"ansi-regex": {
"version": "5.0.0",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/ansi-regex/-/ansi-regex-5.0.0.tgz",
"integrity": "sha1-OIU59VF5vzkznIGvMKZU1p+Hy3U=",
"version": "5.0.1",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha1-CCyyyJyf6GWaMRpTvWpNxTAdswQ=",
"dev": true
},
"chalk": {
"version": "4.1.0",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/chalk/-/chalk-4.1.0.tgz",
"integrity": "sha1-ThSHCmGNni7dl92DRf2dncMVZGo=",
"version": "4.1.2",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha1-qsTit3NKdAhnrrFr8CqtVWoeegE=",
"dev": true,
"requires": {
"ansi-styles": "^4.1.0",
@ -2008,12 +2014,12 @@
}
},
"strip-ansi": {
"version": "6.0.0",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/strip-ansi/-/strip-ansi-6.0.0.tgz",
"integrity": "sha1-CxVx3XZpzNTz4G4U7x7tJiJa5TI=",
"version": "6.0.1",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha1-nibGPTD1NEPpSJSVshBdN7Z6hdk=",
"dev": true,
"requires": {
"ansi-regex": "^5.0.0"
"ansi-regex": "^5.0.1"
}
}
}
@ -3525,9 +3531,9 @@
"dev": true
},
"boolean": {
"version": "3.0.2",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/boolean/-/boolean-3.0.2.tgz",
"integrity": "sha1-3xuqGLaisOcIQEdeHZPsj+dbJXA=",
"version": "3.1.4",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/boolean/-/boolean-3.1.4.tgz",
"integrity": "sha1-9RovtYOKmeBvm27B7bZ03mcCZDU=",
"dev": true,
"optional": true
},
@ -4121,9 +4127,9 @@
}
},
"caniuse-lite": {
"version": "1.0.30001203",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/caniuse-lite/-/caniuse-lite-1.0.30001203.tgz",
"integrity": "sha1-p6NN8ho4fZ3v/NVsAAuM9atUBYA=",
"version": "1.0.30001265",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/caniuse-lite/-/caniuse-lite-1.0.30001265.tgz",
"integrity": "sha1-BhPJ5ski5CJ5Lm/O/fmjr+7k+MM=",
"dev": true
},
"capture-exit": {
@ -4732,9 +4738,9 @@
}
},
"config-chain": {
"version": "1.1.12",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/config-chain/-/config-chain-1.1.12.tgz",
"integrity": "sha1-D96NCRIA616AjK8l/mGMAvSOTvo=",
"version": "1.1.13",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/config-chain/-/config-chain-1.1.13.tgz",
"integrity": "sha1-+tB5Wqamza/57Rto6d/5Q3LCMvQ=",
"dev": true,
"optional": true,
"requires": {
@ -5566,9 +5572,9 @@
"dev": true
},
"detect-node": {
"version": "2.0.5",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/detect-node/-/detect-node-2.0.5.tgz",
"integrity": "sha1-nScKp+qlrwtyxMnZuBTn9M5zi3k=",
"version": "2.1.0",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/detect-node/-/detect-node-2.1.0.tgz",
"integrity": "sha1-yccHdaScPQO8LAbZpzvlUPl4+LE=",
"dev": true,
"optional": true
},
@ -5930,14 +5936,22 @@
}
},
"electron": {
"version": "9.4.1",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/electron/-/electron-9.4.1.tgz",
"integrity": "sha1-YqKq5M2T8bVteUpHVBUFpxZUF3o=",
"version": "14.1.1",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/electron/-/electron-14.1.1.tgz",
"integrity": "sha1-ZDcm/h/UrXf7s6dbIRAF7QE1dIU=",
"dev": true,
"requires": {
"@electron/get": "^1.0.1",
"@types/node": "^12.0.12",
"@types/node": "^14.6.2",
"extract-zip": "^1.0.3"
},
"dependencies": {
"@types/node": {
"version": "14.17.19",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/@types/node/-/node-14.17.19.tgz",
"integrity": "sha1-c0HprBtddI16PdwEM27VNqb5HDE=",
"dev": true
}
}
},
"electron-builder": {
@ -6235,12 +6249,12 @@
}
},
"electron-chromedriver": {
"version": "9.0.0",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/electron-chromedriver/-/electron-chromedriver-9.0.0.tgz",
"integrity": "sha1-x2Kf5rlyEUDzo4AUT5mWDCvDtcE=",
"version": "13.0.0",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/electron-chromedriver/-/electron-chromedriver-13.0.0.tgz",
"integrity": "sha1-pVOvd0MhWsRj4eQODbFNSlQu92I=",
"dev": true,
"requires": {
"@electron/get": "^1.12.2",
"@electron/get": "^1.12.4",
"extract-zip": "^2.0.0"
},
"dependencies": {
@ -6480,6 +6494,15 @@
"path-exists": "^3.0.0"
}
},
"node-abi": {
"version": "2.30.1",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/node-abi/-/node-abi-2.30.1.tgz",
"integrity": "sha1-xDfUsf4OKFqvKQ1FtF1Nev7axM8=",
"dev": true,
"requires": {
"semver": "^5.4.1"
}
},
"p-locate": {
"version": "3.0.0",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/p-locate/-/p-locate-3.0.0.tgz",
@ -6504,6 +6527,12 @@
"tslib": "^1.9.0"
}
},
"semver": {
"version": "5.7.1",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/semver/-/semver-5.7.1.tgz",
"integrity": "sha1-qVT5Ma66UI0we78Gnv8MAclhFvc=",
"dev": true
},
"spawn-rx": {
"version": "3.0.0",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/spawn-rx/-/spawn-rx-3.0.0.tgz",
@ -6589,8 +6618,8 @@
}
},
"electron-spellchecker": {
"version": "git+https://github.com/symphonyoss/electron-spellchecker.git#8628a0e62660bc23da969fc19c9e9b39eb54be5a",
"from": "git+https://github.com/symphonyoss/electron-spellchecker.git#v2.3.2",
"version": "git+ssh://git@github.com/symphonyoss/electron-spellchecker.git#8628a0e62660bc23da969fc19c9e9b39eb54be5a",
"from": "electron-spellchecker@git+https://github.com/symphonyoss/electron-spellchecker.git#v2.3.2",
"requires": {
"@aabuhijleh/electron-remote": "^1.4.0",
"@felixrieseberg/spellchecker": "^4.0.12",
@ -8415,9 +8444,9 @@
}
},
"global-agent": {
"version": "2.1.12",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/global-agent/-/global-agent-2.1.12.tgz",
"integrity": "sha1-5K44Ercxqegcv4Jfk3fvRQqOQZU=",
"version": "2.2.0",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/global-agent/-/global-agent-2.2.0.tgz",
"integrity": "sha1-VmMxsGRua/eUKaFod2hcSh+/dtw=",
"dev": true,
"optional": true,
"requires": {
@ -8431,9 +8460,9 @@
},
"dependencies": {
"core-js": {
"version": "3.9.1",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/core-js/-/core-js-3.9.1.tgz",
"integrity": "sha1-zsjeWT246yqF/7Db3rMSy25UYK4=",
"version": "3.18.1",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/core-js/-/core-js-3.18.1.tgz",
"integrity": "sha1-KJ1L4s4AhdQPwSRMCxpUwARUYi8=",
"dev": true,
"optional": true
},
@ -8465,9 +8494,9 @@
}
},
"semver": {
"version": "7.3.4",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/semver/-/semver-7.3.4.tgz",
"integrity": "sha1-J6qn0uTKdkUvmNOt0JOnLJQ+3Jc=",
"version": "7.3.5",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/semver/-/semver-7.3.5.tgz",
"integrity": "sha1-C2Ich5NI2JmOSw5L6Us/EuYBjvc=",
"dev": true,
"optional": true,
"requires": {
@ -11414,13 +11443,13 @@
}
},
"lighthouse-logger": {
"version": "1.2.0",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/lighthouse-logger/-/lighthouse-logger-1.2.0.tgz",
"integrity": "sha1-t21Wk16cE36GoEdB9rubJ3bohso=",
"version": "1.3.0",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/lighthouse-logger/-/lighthouse-logger-1.3.0.tgz",
"integrity": "sha1-umMD5zkwfE7uGPCCSVJOfa/VENs=",
"dev": true,
"requires": {
"debug": "^2.6.8",
"marky": "^1.2.0"
"debug": "^2.6.9",
"marky": "^1.2.2"
},
"dependencies": {
"debug": {
@ -11755,9 +11784,9 @@
}
},
"marky": {
"version": "1.2.1",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/marky/-/marky-1.2.1.tgz",
"integrity": "sha1-o/z4L/01d1a4uK/+yf2/OjDcGwI=",
"version": "1.2.2",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/marky/-/marky-1.2.2.tgz",
"integrity": "sha1-RFZ2W03jB6E9JjppsMeb8ibmgyM=",
"dev": true
},
"matchdep": {
@ -12348,18 +12377,36 @@
}
},
"node-abi": {
"version": "2.21.0",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/node-abi/-/node-abi-2.21.0.tgz",
"integrity": "sha1-wtyeutb09T1uqbUx57j6rYEEHUg=",
"version": "3.2.0",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/node-abi/-/node-abi-3.2.0.tgz",
"integrity": "sha1-yOxodPgItNpfvVbpUGOQzmWxUqI=",
"dev": true,
"requires": {
"semver": "^5.4.1"
"semver": "^7.3.5"
},
"dependencies": {
"lru-cache": {
"version": "6.0.0",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha1-bW/mVw69lqr5D8rR2vo7JWbbOpQ=",
"dev": true,
"requires": {
"yallist": "^4.0.0"
}
},
"semver": {
"version": "5.7.1",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/semver/-/semver-5.7.1.tgz",
"integrity": "sha1-qVT5Ma66UI0we78Gnv8MAclhFvc=",
"version": "7.3.5",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/semver/-/semver-7.3.5.tgz",
"integrity": "sha1-C2Ich5NI2JmOSw5L6Us/EuYBjvc=",
"dev": true,
"requires": {
"lru-cache": "^6.0.0"
}
},
"yallist": {
"version": "4.0.0",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha1-m7knkNnA7/7GO+c1GeEaNQGaOnI=",
"dev": true
}
}
@ -12370,10 +12417,37 @@
"integrity": "sha1-gTJeCiEXeJwBKNq2Xn448HzroWE="
},
"node-fetch": {
"version": "2.6.1",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/node-fetch/-/node-fetch-2.6.1.tgz",
"integrity": "sha1-BFvTI2Mfdu0uK1VXM5RBa2OaAFI=",
"dev": true
"version": "2.6.5",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/node-fetch/-/node-fetch-2.6.5.tgz",
"integrity": "sha1-QnNVN9fwgKfl94tsVJtxRr4XQv0=",
"dev": true,
"requires": {
"whatwg-url": "^5.0.0"
},
"dependencies": {
"tr46": {
"version": "0.0.3",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=",
"dev": true
},
"webidl-conversions": {
"version": "3.0.1",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=",
"dev": true
},
"whatwg-url": {
"version": "5.0.0",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
"dev": true,
"requires": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
}
}
},
"node-gyp": {
"version": "6.1.0",
@ -13584,6 +13658,23 @@
"tar-fs": "^2.0.0",
"tunnel-agent": "^0.6.0",
"which-pm-runs": "^1.0.0"
},
"dependencies": {
"node-abi": {
"version": "2.30.1",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/node-abi/-/node-abi-2.30.1.tgz",
"integrity": "sha1-xDfUsf4OKFqvKQ1FtF1Nev7axM8=",
"dev": true,
"requires": {
"semver": "^5.4.1"
}
},
"semver": {
"version": "5.7.1",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/semver/-/semver-5.7.1.tgz",
"integrity": "sha1-qVT5Ma66UI0we78Gnv8MAclhFvc=",
"dev": true
}
}
},
"prelude-ls": {
@ -14593,9 +14684,9 @@
}
},
"resolve-alpn": {
"version": "1.0.0",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/resolve-alpn/-/resolve-alpn-1.0.0.tgz",
"integrity": "sha1-dFrWCz1q/0tKSOAbjAvccJWeDow=",
"version": "1.2.1",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/resolve-alpn/-/resolve-alpn-1.2.1.tgz",
"integrity": "sha1-t629rDVGqq7CC0Xn2CZZJwcnJvk=",
"dev": true
},
"resolve-cwd": {
@ -14648,9 +14739,9 @@
}
},
"resq": {
"version": "1.10.0",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/resq/-/resq-1.10.0.tgz",
"integrity": "sha1-QLXjUV/5hGaOa2t8JAHygrCAQuo=",
"version": "1.10.1",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/resq/-/resq-1.10.1.tgz",
"integrity": "sha1-wF0bOAgBbM7sTUhc6zday0lWX1M=",
"dev": true,
"requires": {
"fast-deep-equal": "^2.0.1"
@ -14752,6 +14843,23 @@
"nan": "^2.14.0",
"node-abi": "^2.13.0",
"prebuild-install": "^5.3.3"
},
"dependencies": {
"node-abi": {
"version": "2.30.1",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/node-abi/-/node-abi-2.30.1.tgz",
"integrity": "sha1-xDfUsf4OKFqvKQ1FtF1Nev7axM8=",
"dev": true,
"requires": {
"semver": "^5.4.1"
}
},
"semver": {
"version": "5.7.1",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/semver/-/semver-5.7.1.tgz",
"integrity": "sha1-qVT5Ma66UI0we78Gnv8MAclhFvc=",
"dev": true
}
}
},
"rst-selector-parser": {
@ -15042,16 +15150,16 @@
}
},
"screen-share-indicator-frame": {
"version": "git+https://github.com/symphonyoss/ScreenShareIndicatorFrame.git#e943ec141899d8cf7301f4bd3f91a1434e1ceb10",
"from": "screen-share-indicator-frame@git+https://github.com/symphonyoss/ScreenShareIndicatorFrame.git#e943ec141899d8cf7301f4bd3f91a1434e1ceb10",
"version": "git+ssh://git@github.com/symphonyoss/ScreenShareIndicatorFrame.git#e943ec141899d8cf7301f4bd3f91a1434e1ceb10",
"from": "screen-share-indicator-frame@git+https://github.com/symphonyoss/ScreenShareIndicatorFrame.git#v1.4.10",
"optional": true,
"requires": {
"run-script-os": "1.0.7"
}
},
"screen-snippet": {
"version": "git+https://github.com/symphonyoss/ScreenSnippet2.git#889aedbd3ecf16320a387967aaee0e7ca992d717",
"from": "screen-snippet@git+https://github.com/symphonyoss/ScreenSnippet2.git#889aedbd3ecf16320a387967aaee0e7ca992d717",
"version": "git+ssh://git@github.com/symphonyoss/ScreenSnippet2.git#889aedbd3ecf16320a387967aaee0e7ca992d717",
"from": "screen-snippet@git+https://github.com/symphonyoss/ScreenSnippet2.git#v2.4.0",
"optional": true
},
"semver": {
@ -15527,16 +15635,140 @@
"dev": true
},
"spectron": {
"version": "11.1.0",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/spectron/-/spectron-11.1.0.tgz",
"integrity": "sha1-7k8RyQV/bXkJTy1ETrpVXMvmOWU=",
"version": "15.0.0",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/spectron/-/spectron-15.0.0.tgz",
"integrity": "sha1-nA4lSyvj8HJagbg0MJK50BCEOcc=",
"dev": true,
"requires": {
"@electron/remote": "^1.1.0",
"dev-null": "^0.1.1",
"electron-chromedriver": "^9.0.0",
"request": "^2.87.0",
"split": "^1.0.0",
"webdriverio": "^6.1.20"
"electron-chromedriver": "^13.0.0",
"got": "^11.8.0",
"split": "^1.0.1",
"webdriverio": "^6.9.1"
},
"dependencies": {
"@sindresorhus/is": {
"version": "4.2.0",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/@sindresorhus/is/-/is-4.2.0.tgz",
"integrity": "sha1-Znv8YYaufJ4LRaCJYMVRQ3F24co=",
"dev": true
},
"@szmarczak/http-timer": {
"version": "4.0.6",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/@szmarczak/http-timer/-/http-timer-4.0.6.tgz",
"integrity": "sha1-tKkUu2LnwnLU5Zif5EQPgSqx2Ac=",
"dev": true,
"requires": {
"defer-to-connect": "^2.0.0"
}
},
"cacheable-request": {
"version": "7.0.2",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/cacheable-request/-/cacheable-request-7.0.2.tgz",
"integrity": "sha1-6g0LiJNkolhUdXMByhKy2nf5HSc=",
"dev": true,
"requires": {
"clone-response": "^1.0.2",
"get-stream": "^5.1.0",
"http-cache-semantics": "^4.0.0",
"keyv": "^4.0.0",
"lowercase-keys": "^2.0.0",
"normalize-url": "^6.0.1",
"responselike": "^2.0.0"
}
},
"decompress-response": {
"version": "6.0.0",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/decompress-response/-/decompress-response-6.0.0.tgz",
"integrity": "sha1-yjh2Et234QS9FthaqwDV7PCcZvw=",
"dev": true,
"requires": {
"mimic-response": "^3.1.0"
}
},
"defer-to-connect": {
"version": "2.0.1",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/defer-to-connect/-/defer-to-connect-2.0.1.tgz",
"integrity": "sha1-gBa9tBQ+RjK3ejRJxiNid95SBYc=",
"dev": true
},
"get-stream": {
"version": "5.2.0",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/get-stream/-/get-stream-5.2.0.tgz",
"integrity": "sha1-SWaheV7lrOZecGxLe+txJX1uItM=",
"dev": true,
"requires": {
"pump": "^3.0.0"
}
},
"got": {
"version": "11.8.2",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/got/-/got-11.8.2.tgz",
"integrity": "sha1-ers5Weoowx81dvFXbB7/ziPzNZk=",
"dev": true,
"requires": {
"@sindresorhus/is": "^4.0.0",
"@szmarczak/http-timer": "^4.0.5",
"@types/cacheable-request": "^6.0.1",
"@types/responselike": "^1.0.0",
"cacheable-lookup": "^5.0.3",
"cacheable-request": "^7.0.1",
"decompress-response": "^6.0.0",
"http2-wrapper": "^1.0.0-beta.5.2",
"lowercase-keys": "^2.0.0",
"p-cancelable": "^2.0.0",
"responselike": "^2.0.0"
}
},
"json-buffer": {
"version": "3.0.1",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/json-buffer/-/json-buffer-3.0.1.tgz",
"integrity": "sha1-kziAKjDTtmBfvgYT4JQAjKjAWhM=",
"dev": true
},
"keyv": {
"version": "4.0.3",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/keyv/-/keyv-4.0.3.tgz",
"integrity": "sha1-TzqpjeJUgDyvzSiWc0EI2qNeQlQ=",
"dev": true,
"requires": {
"json-buffer": "3.0.1"
}
},
"lowercase-keys": {
"version": "2.0.0",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
"integrity": "sha1-JgPni3tLAAbLyi+8yKMgJVislHk=",
"dev": true
},
"mimic-response": {
"version": "3.1.0",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/mimic-response/-/mimic-response-3.1.0.tgz",
"integrity": "sha1-LR1Zr5wbEpgVrMwsRqAipc4fo8k=",
"dev": true
},
"normalize-url": {
"version": "6.1.0",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/normalize-url/-/normalize-url-6.1.0.tgz",
"integrity": "sha1-QNCIW1Nd7/4/MUe+yHfQX+TFZoo=",
"dev": true
},
"p-cancelable": {
"version": "2.1.1",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/p-cancelable/-/p-cancelable-2.1.1.tgz",
"integrity": "sha1-qrf71BZYL6MqPbSYWcEiSHxe0s8=",
"dev": true
},
"responselike": {
"version": "2.0.0",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/responselike/-/responselike-2.0.0.tgz",
"integrity": "sha1-JjkbzDF091D5p56sxAoSpcQtdyM=",
"dev": true,
"requires": {
"lowercase-keys": "^2.0.0"
}
}
}
},
"split": {
@ -16853,9 +17085,9 @@
"dev": true
},
"ua-parser-js": {
"version": "0.7.24",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/ua-parser-js/-/ua-parser-js-0.7.24.tgz",
"integrity": "sha1-jT7OpG7U8fHWPsJfF9hWgQXcAnw=",
"version": "0.7.28",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/ua-parser-js/-/ua-parser-js-0.7.28.tgz",
"integrity": "sha1-i6BOZT81ziECOcZGYWhb+RId7DE=",
"dev": true
},
"uglify-js": {
@ -17447,24 +17679,24 @@
},
"dependencies": {
"@sindresorhus/is": {
"version": "4.0.0",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/@sindresorhus/is/-/is-4.0.0.tgz",
"integrity": "sha1-L/Z06WEbRbUoiW2CDT16gS3i8OQ=",
"version": "4.2.0",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/@sindresorhus/is/-/is-4.2.0.tgz",
"integrity": "sha1-Znv8YYaufJ4LRaCJYMVRQ3F24co=",
"dev": true
},
"@szmarczak/http-timer": {
"version": "4.0.5",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/@szmarczak/http-timer/-/http-timer-4.0.5.tgz",
"integrity": "sha1-v71QIR6d+lG6B9pYoUzf0zMgUVI=",
"version": "4.0.6",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/@szmarczak/http-timer/-/http-timer-4.0.6.tgz",
"integrity": "sha1-tKkUu2LnwnLU5Zif5EQPgSqx2Ac=",
"dev": true,
"requires": {
"defer-to-connect": "^2.0.0"
}
},
"cacheable-request": {
"version": "7.0.1",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/cacheable-request/-/cacheable-request-7.0.1.tgz",
"integrity": "sha1-BiAxwoViMngu1pSiV/o12pOUKlg=",
"version": "7.0.2",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/cacheable-request/-/cacheable-request-7.0.2.tgz",
"integrity": "sha1-6g0LiJNkolhUdXMByhKy2nf5HSc=",
"dev": true,
"requires": {
"clone-response": "^1.0.2",
@ -17472,7 +17704,7 @@
"http-cache-semantics": "^4.0.0",
"keyv": "^4.0.0",
"lowercase-keys": "^2.0.0",
"normalize-url": "^4.1.0",
"normalize-url": "^6.0.1",
"responselike": "^2.0.0"
}
},
@ -17546,10 +17778,16 @@
"integrity": "sha1-LR1Zr5wbEpgVrMwsRqAipc4fo8k=",
"dev": true
},
"normalize-url": {
"version": "6.1.0",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/normalize-url/-/normalize-url-6.1.0.tgz",
"integrity": "sha1-QNCIW1Nd7/4/MUe+yHfQX+TFZoo=",
"dev": true
},
"p-cancelable": {
"version": "2.1.0",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/p-cancelable/-/p-cancelable-2.1.0.tgz",
"integrity": "sha1-TVHDuR9IPQKg0wB2UyH8o5PXWN0=",
"version": "2.1.1",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/p-cancelable/-/p-cancelable-2.1.1.tgz",
"integrity": "sha1-qrf71BZYL6MqPbSYWcEiSHxe0s8=",
"dev": true
},
"responselike": {
@ -17610,19 +17848,19 @@
}
},
"async": {
"version": "3.2.0",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/async/-/async-3.2.0.tgz",
"integrity": "sha1-s6JoXF67ZB094C0WEALGD8n4VyA=",
"version": "3.2.1",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/async/-/async-3.2.1.tgz",
"integrity": "sha1-0ydOxm0QekdHakxJE2qs2wBmX8g=",
"dev": true
},
"compress-commons": {
"version": "4.1.0",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/compress-commons/-/compress-commons-4.1.0.tgz",
"integrity": "sha1-Jex6RSiFLM0dRBp9Q1PNDs4RNxs=",
"version": "4.1.1",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/compress-commons/-/compress-commons-4.1.1.tgz",
"integrity": "sha1-3yoJp+0XRHZCutEKhcyaGeXEKn0=",
"dev": true,
"requires": {
"buffer-crc32": "^0.2.13",
"crc32-stream": "^4.0.1",
"crc32-stream": "^4.0.2",
"normalize-path": "^3.0.0",
"readable-stream": "^3.6.0"
}

View File

@ -126,7 +126,7 @@
"browserify": "16.5.1",
"cross-env": "5.2.0",
"del": "3.0.0",
"electron": "9.4.1",
"electron": "14.1.1",
"electron-builder": "22.7.0",
"electron-builder-squirrel-windows": "20.38.3",
"electron-icon-maker": "0.0.4",
@ -144,13 +144,13 @@
"jest-html-reporter": "3.0.0",
"less": "3.8.1",
"ncp": "2.0.0",
"node-abi": "^2.17.0",
"node-abi": "^3.2.0",
"npm-run-all": "4.1.5",
"prettier": "2.2.1",
"pretty-quick": "^3.1.0",
"robotjs": "0.6.0",
"run-script-os": "1.0.7",
"spectron": "^11.0.0",
"spectron": "^15.0.0",
"ts-jest": "25.3.0",
"tslint": "5.11.0",
"tslint-config-prettier": "^1.18.0",

View File

@ -31,6 +31,7 @@ interface ILoginItemSettings {
}
interface IIpcMain {
on(event: any, cb: any): void;
handle(event: any, cb: any): Promise<void>;
send(event: any, cb: any): void;
}
interface IIpcRenderer {
@ -40,6 +41,14 @@ interface IIpcRenderer {
removeListener(eventName: any, cb: any): void;
once(eventName: any, cb: any): void;
}
interface IWebContents {
setWindowOpenHandler(details: any): any;
sendSync(event: any, cb: any): any;
on(eventName: any, cb: any): void;
send(event: any, ...cb: any[]): void;
removeListener(eventName: any, cb: any): void;
once(eventName: any, cb: any): void;
}
interface IPowerMonitor {
getSystemIdleTime(): void;
}
@ -92,6 +101,10 @@ export const ipcMain: IIpcMain = {
on: (event, cb) => {
ipcEmitter.on(event, cb);
},
handle: (event, cb) => {
ipcEmitter.on(event, cb);
return Promise.resolve();
},
send: (event, args) => {
const senderEvent = {
sender: {
@ -141,6 +154,42 @@ export const ipcRenderer: IIpcRenderer = {
},
};
export const webContents: IWebContents = {
setWindowOpenHandler: (_details: {}) => {
return { action: 'allow' };
},
sendSync: (event, args) => {
const listeners = ipcEmitter.listeners(event);
if (listeners.length > 0) {
const listener = listeners[0];
const eventArg = {};
listener(eventArg, args);
return eventArg;
}
return null;
},
send: (event, ...args) => {
const senderEvent = {
sender: {
send: (eventSend, ...arg) => {
ipcEmitter.emit(eventSend, ...arg);
},
},
preventDefault: jest.fn(),
};
ipcEmitter.emit(event, senderEvent, ...args);
},
on: (eventName, cb) => {
ipcEmitter.on(eventName, cb);
},
removeListener: (eventName, cb) => {
ipcEmitter.removeListener(eventName, cb);
},
once: (eventName, cb) => {
ipcEmitter.on(eventName, cb);
},
};
export const shell = {
openExternal: jest.fn(),
};

View File

@ -119,7 +119,7 @@ exports[`windows title bar should render correctly 1`] = `
onClick={[Function]}
onContextMenu={[Function]}
onMouseDown={[Function]}
title="Maximize"
title="Restore"
>
<svg
viewBox="0 0 14 10.2"
@ -127,7 +127,7 @@ exports[`windows title bar should render correctly 1`] = `
y="0px"
>
<path
d="M0,0v10.1h10.2V0H0z M9.2,9.2H1.1V1h8.1V9.2z"
d="M2.1,0v2H0v8.1h8.2v-2h2V0H2.1z M7.2,9.2H1.1V3h6.1V9.2z M9.2,7.1h-1V2H3.1V1h6.1V7.1z"
fill="rgba(255, 255, 255, 0.9)"
/>
</svg>

View File

@ -1,7 +1,8 @@
import { shallow } from 'enzyme';
import * as React from 'react';
import { apiCmds } from '../src/common/api-interface';
import AboutApp from '../src/renderer/components/about-app';
import { ipcRenderer, remote } from './__mocks__/electron';
import { ipcRenderer } from './__mocks__/electron';
describe('about app', () => {
const aboutAppDataLabel = 'about-app-data';
@ -32,6 +33,7 @@ describe('about app', () => {
swiftSearchSupportedVersion: 'N/A',
};
const onLabelEvent = 'on';
const ipcSendEvent = 'send';
const removeListenerLabelEvent = 'removeListener';
it('should render correctly', () => {
@ -62,12 +64,16 @@ describe('about app', () => {
});
it('should copy the correct data on to clipboard', () => {
const spyMount = jest.spyOn(remote.clipboard, 'write');
const spyIpc = jest.spyOn(ipcRenderer, ipcSendEvent);
const wrapper = shallow(React.createElement(AboutApp));
ipcRenderer.send('about-app-data', aboutDataMock);
const copyButtonSelector = `button.AboutApp-copy-button[title="Copy all the version information in this page"]`;
wrapper.find(copyButtonSelector).simulate('click');
const expectedData = { text: JSON.stringify(aboutDataMock, null, 4) };
expect(spyMount).toBeCalledWith(expectedData, 'clipboard');
const expectedData = {
cmd: apiCmds.aboutAppClipBoardData,
clipboard: aboutDataMock,
clipboardType: 'clipboard',
};
expect(spyIpc).toBeCalledWith('symphony-api', expectedData);
});
});

View File

@ -1,8 +1,6 @@
import { handleChildWindow } from '../src/app/child-window-handler';
import { config } from '../src/app/config-handler';
import { windowHandler } from '../src/app/window-handler';
import { injectStyles } from '../src/app/window-utils';
import { ipcRenderer } from './__mocks__/electron';
import { webContents } from './__mocks__/electron';
import anything = jasmine.anything;
const getMainWindow = {
isDestroyed: jest.fn(() => false),
@ -69,52 +67,16 @@ jest.mock('../src/common/logger', () => {
});
describe('child window handle', () => {
const frameName = {};
const disposition = 'new-window';
const newWinOptions = {
webPreferences: jest.fn(),
webContents: { ...ipcRenderer, ...getMainWindow, webContents: ipcRenderer },
};
it('should set open window handler', () => {
const spy = jest.spyOn(webContents, 'setWindowOpenHandler');
it('should call `did-start-loading` correctly on WindowOS', () => {
const newWinUrl = 'about:blank';
const args = [newWinUrl, frameName, disposition, newWinOptions];
const spy = jest.spyOn(getMainWindow, 'setMenuBarVisibility');
handleChildWindow(ipcRenderer as any);
ipcRenderer.send('new-window', ...args);
ipcRenderer.send('did-start-loading');
expect(spy).toBeCalledWith(false);
handleChildWindow(webContents as any);
expect(spy).toBeCalledWith(expect.any(Function));
});
it('should call `did-finish-load` correctly on WindowOS', () => {
config.getGlobalConfigFields = jest.fn(() => {
return {
url: 'https://foundation-dev.symphony.com',
};
});
const newWinUrl = 'about:blank';
const args = [newWinUrl, frameName, disposition, newWinOptions];
const spy = jest.spyOn(newWinOptions.webContents.webContents, 'send');
handleChildWindow(ipcRenderer as any);
ipcRenderer.send('new-window', ...args);
ipcRenderer.send('did-finish-load');
expect(spy).lastCalledWith('page-load', {
enableCustomTitleBar: false,
isMainWindow: false,
isWindowsOS: true,
locale: 'en-US',
origin: 'https://foundation-dev.symphony.com',
resources: {},
});
expect(injectStyles).toBeCalled();
});
it('should call `windowHandler.openUrlInDefaultBrowser` when url in invalid', () => {
const newWinUrl = 'invalid';
const args = [newWinUrl, frameName, disposition, newWinOptions];
const spy = jest.spyOn(windowHandler, 'openUrlInDefaultBrowser');
handleChildWindow(ipcRenderer as any);
ipcRenderer.send('new-window', ...args);
expect(spy).not.toBeCalledWith('invalid');
it('should trigger did-create-window', () => {
const spy = jest.spyOn(webContents, 'on');
handleChildWindow(webContents as any);
expect(spy).toBeCalledWith('did-create-window', anything());
});
});

View File

@ -1,7 +1,8 @@
import { shallow } from 'enzyme';
import * as React from 'react';
import { apiCmds } from '../src/common/api-interface';
import WindowsTitleBar from '../src/renderer/components/windows-title-bar';
import { ipcRenderer, remote } from './__mocks__/electron';
import { ipcRenderer } from './__mocks__/electron';
// @ts-ignore
global.MutationObserver = jest.fn().mockImplementation(() => ({
@ -9,33 +10,11 @@ global.MutationObserver = jest.fn().mockImplementation(() => ({
disconnect: jest.fn(),
}));
// TODO: Fix tests
describe('windows title bar', () => {
beforeEach(() => {
// state initial
jest.spyOn(remote, 'getCurrentWindow').mockImplementation(() => {
return {
isFullScreen: jest.fn(() => {
return false;
}),
isMaximized: jest.fn(() => {
return false;
}),
on: jest.fn(),
removeListener: jest.fn(),
isDestroyed: jest.fn(() => {
return false;
}),
close: jest.fn(),
maximize: jest.fn(),
minimize: jest.fn(),
unmaximize: jest.fn(),
setFullScreen: jest.fn(),
};
});
});
const getCurrentWindowFnLabel = 'getCurrentWindow';
const onEventLabel = 'on';
const sendEventLabel = 'send';
const apiName = 'symphony-api';
const maximizeEventLabel = 'maximize';
const unmaximizeEventLabel = 'unmaximize';
const enterFullScreenEventLabel = 'enter-full-screen';
@ -47,28 +26,19 @@ describe('windows title bar', () => {
});
it('should mount correctly', () => {
const spy = jest.spyOn(ipcRenderer, onEventLabel);
const wrapper = shallow(React.createElement(WindowsTitleBar));
const instance: any = wrapper.instance();
const window = instance.window;
const spy = jest.spyOn(remote, getCurrentWindowFnLabel);
const spyWindow = jest.spyOn(window, onEventLabel);
instance.updateState({ isMaximized: false });
expect(spy).toBeCalled();
expect(spyWindow).nthCalledWith(
1,
maximizeEventLabel,
expect.any(Function),
);
expect(spyWindow).nthCalledWith(
2,
unmaximizeEventLabel,
expect.any(Function),
);
expect(spyWindow).nthCalledWith(
expect(spy).nthCalledWith(1, maximizeEventLabel, expect.any(Function));
expect(spy).nthCalledWith(2, unmaximizeEventLabel, expect.any(Function));
expect(spy).nthCalledWith(
3,
enterFullScreenEventLabel,
expect.any(Function),
);
expect(spyWindow).nthCalledWith(
expect(spy).nthCalledWith(
4,
leaveFullScreenEventLabel,
expect.any(Function),
@ -76,27 +46,27 @@ describe('windows title bar', () => {
});
it('should call `close` correctly', () => {
const fnLabel = 'close';
const titleLabel = 'Close';
const wrapper = shallow(React.createElement(WindowsTitleBar));
const customSelector = `button.title-bar-button[title="${titleLabel}"]`;
const instance: any = wrapper.instance();
const window = instance.window;
const spy = jest.spyOn(window, fnLabel);
const cmd = {
cmd: apiCmds.closeMainWindow,
};
const spy = jest.spyOn(ipcRenderer, sendEventLabel);
wrapper.find(customSelector).simulate('click');
expect(spy).toBeCalled();
expect(spy).toBeCalledWith(apiName, cmd);
});
it('should call `minimize` correctly', () => {
const fnLabel = 'minimize';
const titleLabel = 'Minimize';
const wrapper = shallow(React.createElement(WindowsTitleBar));
const customSelector = `button.title-bar-button[title="${titleLabel}"]`;
const instance: any = wrapper.instance();
const window = instance.window;
const spy = jest.spyOn(window, fnLabel);
const spy = jest.spyOn(ipcRenderer, sendEventLabel);
const cmd = {
cmd: apiCmds.minimizeMainWindow,
};
wrapper.find(customSelector).simulate('click');
expect(spy).toBeCalled();
expect(spy).toBeCalledWith(apiName, cmd);
});
it('should call `showMenu` correctly', () => {
@ -132,82 +102,47 @@ describe('windows title bar', () => {
expect(spy).lastCalledWith(expect.any(Function));
});
describe('componentDidMount event', () => {
beforeEach(() => {
document.body.innerHTML = `<div id="content-wrapper"></div>`;
});
it('should call `componentDidMount` when isFullScreen', () => {
const spy = jest.spyOn(document.body.style, 'removeProperty');
const expectedValue = 'margin-top';
// changing state before componentDidMount
jest.spyOn(remote, 'getCurrentWindow').mockImplementation(() => {
return {
isFullScreen: jest.fn(() => {
return true;
}),
isMaximized: jest.fn(() => {
return false;
}),
on: jest.fn(),
removeListener: jest.fn(),
isDestroyed: jest.fn(() => {
return false;
}),
close: jest.fn(),
maximize: jest.fn(),
minimize: jest.fn(),
unmaximize: jest.fn(),
setFullScreen: jest.fn(),
};
});
shallow(React.createElement(WindowsTitleBar));
expect(spy).toBeCalledWith(expectedValue);
});
});
describe('maximize functions', () => {
it('should call `unmaximize` correctly when is not full screen', () => {
const titleLabel = 'Restore';
const unmaximizeFn = 'unmaximize';
const cmd = {
cmd: apiCmds.unmaximizeMainWindow,
};
const customSelector = `button.title-bar-button[title="${titleLabel}"]`;
const wrapper = shallow(React.createElement(WindowsTitleBar));
const instance: any = wrapper.instance();
const window = instance.window;
const spy = jest.spyOn(window, unmaximizeFn);
const spy = jest.spyOn(ipcRenderer, sendEventLabel);
wrapper.setState({ isMaximized: true });
wrapper.find(customSelector).simulate('click');
expect(spy).toBeCalled();
expect(spy).toBeCalledWith(apiName, cmd);
});
it('should call `unmaximize` correctly when is full screen', () => {
const windowSpyFn = 'setFullScreen';
const titleLabel = 'Restore';
const customSelector = `button.title-bar-button[title="${titleLabel}"]`;
const wrapper = shallow(React.createElement(WindowsTitleBar));
const instance: any = wrapper.instance();
const window = instance.window;
const spy = jest.spyOn(window, windowSpyFn);
window.isFullScreen = jest.fn(() => {
return true;
});
const cmd = {
cmd: apiCmds.unmaximizeMainWindow,
};
const spy = jest.spyOn(ipcRenderer, sendEventLabel);
wrapper.setState({ isMaximized: true });
wrapper.find(customSelector).simulate('click');
expect(spy).toBeCalledWith(false);
expect(spy).toBeCalledWith(apiName, cmd);
});
it('should call maximize correctly when it is not in full screen', () => {
const titleLabel = 'Maximize';
const maximizeFn = 'maximize';
const expectedState = { isMaximized: true };
const customSelector = `button.title-bar-button[title="${titleLabel}"]`;
const wrapper = shallow(React.createElement(WindowsTitleBar));
const instance: any = wrapper.instance();
const window = instance.window;
const spyWindow = jest.spyOn(window, maximizeFn);
wrapper.setState({ isMaximized: false });
const spy = jest.spyOn(ipcRenderer, sendEventLabel);
const spyState = jest.spyOn(wrapper, 'setState');
wrapper.find(customSelector).simulate('click');
expect(spyWindow).toBeCalled();
const cmd = {
cmd: apiCmds.maximizeMainWindow,
};
expect(spy).toBeCalled();
expect(spy).toBeCalledWith(apiName, cmd);
expect(spyState).lastCalledWith(expectedState);
});
});

View File

@ -1,4 +1,11 @@
import { BrowserWindow, crashReporter, WebContents } from 'electron';
import {
BrowserWindow,
BrowserWindowConstructorOptions,
crashReporter,
DidCreateWindowDetails,
HandlerDetails,
WebContents,
} from 'electron';
import { parse as parseQuerystring } from 'querystring';
import { format, parse, Url } from 'url';
@ -9,6 +16,7 @@ import { getGuid } from '../common/utils';
import { whitelistHandler } from '../common/whitelist-handler';
import { config } from './config-handler';
import crashHandler from './crash-handler';
import { mainEvents } from './main-event-handler';
import {
handlePermissionRequests,
monitorWindowActions,
@ -16,10 +24,13 @@ import {
removeWindowEventListener,
sendInitialBoundChanges,
} from './window-actions';
import { ICustomBrowserWindow, windowHandler } from './window-handler';
import {
ICustomBrowserWindow,
ICustomBrowserWindowConstructorOpts,
windowHandler,
} from './window-handler';
import {
getBounds,
// handleCertificateProxyVerification,
injectStyles,
preventWindowNavigation,
} from './window-utils';
@ -30,6 +41,8 @@ const DEFAULT_POP_OUT_HEIGHT = 600;
const MIN_WIDTH = 300;
const MIN_HEIGHT = 300;
const CHILD_WINDOW_EVENTS = ['enter-full-screen', 'leave-full-screen'];
/**
* Verifies protocol for a new url to check if it is http or https
* @param url URL to be verified
@ -82,38 +95,39 @@ const getParsedUrl = (url: string): Url => {
export const handleChildWindow = (webContents: WebContents): void => {
const childWindow = (
event,
newWinUrl,
frameName,
disposition,
newWinOptions,
): void => {
logger.info(`child-window-handler: trying to create new child window for url: ${newWinUrl},
frame name: ${frameName || undefined}, disposition: ${disposition}`);
details: HandlerDetails,
):
| { action: 'deny' }
| {
action: 'allow';
overrideBrowserWindowOptions?: BrowserWindowConstructorOptions;
} => {
logger.info(`child-window-handler: trying to create new child window for url: ${
details.url
},
frame name: ${details.frameName || undefined}, disposition: ${
details.disposition
}`);
const mainWindow = windowHandler.getMainWindow();
if (!mainWindow || mainWindow.isDestroyed()) {
logger.info(
`child-window-handler: main window is not available / destroyed, not creating child window!`,
);
return;
return {
action: 'deny',
};
}
if (!windowHandler.url) {
logger.info(
`child-window-handler: we don't have a valid url, not creating child window!`,
);
return;
return {
action: 'deny',
};
}
if (!newWinOptions.webPreferences) {
newWinOptions.webPreferences = {};
}
Object.assign(newWinOptions.webPreferences, webContents);
// need this to extract other parameters
const newWinParsedUrl = getParsedUrl(newWinUrl);
const newWinUrlData = whitelistHandler.parseDomain(newWinUrl);
const newWinOptions = windowHandler.getMainWindowOpts();
const newWinUrlData = whitelistHandler.parseDomain(details.url);
const mainWinUrlData = whitelistHandler.parseDomain(windowHandler.url);
const newWinDomainName = `${newWinUrlData.domain}${newWinUrlData.tld}`;
@ -130,24 +144,70 @@ export const handleChildWindow = (webContents: WebContents): void => {
// otherwise open in default browser.
if (
(newWinDomainName === mainWinDomainName ||
emptyUrlString.includes(newWinUrl)) &&
frameName !== '' &&
dispositionWhitelist.includes(disposition)
emptyUrlString.includes(details.url)) &&
details.frameName !== '' &&
dispositionWhitelist.includes(details.disposition)
) {
logger.info(
`child-window-handler: opening pop-out window for ${newWinUrl}`,
`child-window-handler: opening pop-out window for ${details.url}`,
);
const newWinKey = getGuid();
if (!frameName) {
if (!details.frameName) {
logger.info(
`child-window-handler: frame name missing! not opening the url ${newWinUrl}`,
`child-window-handler: frame name missing! not opening the url ${details.url}`,
);
return {
action: 'deny',
};
}
return {
action: 'allow',
// override child window options
overrideBrowserWindowOptions: { ...newWinOptions, ...{ frame: true } },
};
} else {
if (details.url && details.url.length > 2083) {
logger.info(
`child-window-handler: new window url length is greater than 2083, not performing any action!`,
);
return {
action: 'deny',
};
}
if (!verifyProtocolForNewUrl(details.url)) {
logger.info(
`child-window-handler: new window url protocol is not valid, not performing any action!`,
);
return {
action: 'deny',
};
}
logger.info(`child-window-handler: new window url is ${details.url} which is not of the same host / protocol,
so opening it in the default app!`);
windowHandler.openUrlInDefaultBrowser(details.url);
return { action: 'deny' };
}
};
webContents.setWindowOpenHandler(childWindow);
webContents.on(
'did-create-window',
(browserWindow: BrowserWindow, details: DidCreateWindowDetails) => {
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();
const mainWindow = windowHandler.getMainWindow();
if (!mainWindow || mainWindow.isDestroyed()) {
logger.info(
'child-window-handler: main window is not available / destroyed, not creating child window!',
);
return;
}
const width = newWinOptions.width || DEFAULT_POP_OUT_WIDTH;
const height = newWinOptions.height || DEFAULT_POP_OUT_HEIGHT;
// need this to extract other parameters
const newWinParsedUrl = getParsedUrl(details.url);
// try getting x and y position from query parameters
const query =
@ -184,8 +244,9 @@ export const handleChildWindow = (webContents: WebContents): void => {
newWinOptions.frame = true;
newWinOptions.winKey = newWinKey;
newWinOptions.fullscreen = false;
newWinOptions.fullscreenable = true;
const childWebContents: WebContents = newWinOptions.webContents;
const childWebContents: WebContents = browserWindow.webContents;
// Event needed to hide native menu bar
childWebContents.once('did-start-loading', () => {
const browserWin = BrowserWindow.fromWebContents(
@ -203,7 +264,7 @@ export const handleChildWindow = (webContents: WebContents): void => {
childWebContents.once('did-finish-load', async () => {
logger.info(
`child-window-handler: child window content loaded for url ${newWinUrl}!`,
`child-window-handler: child window content loaded for url ${details.url}!`,
);
const browserWin: ICustomBrowserWindow = BrowserWindow.fromWebContents(
childWebContents,
@ -221,12 +282,11 @@ export const handleChildWindow = (webContents: WebContents): void => {
locale: i18n.getLocale(),
resources: i18n.loadedResources,
origin: url,
enableCustomTitleBar: false,
isMainWindow: false,
});
// Inserts css on to the window
await injectStyles(browserWin, false);
browserWin.winName = frameName;
await injectStyles(browserWin.webContents, false);
browserWin.winName = details.frameName;
browserWin.setAlwaysOnTop(mainWindow.isAlwaysOnTop());
logger.info(
`child-window-handler: setting always on top for child window? ${mainWindow.isAlwaysOnTop()}!`,
@ -251,7 +311,12 @@ export const handleChildWindow = (webContents: WebContents): void => {
// Remove all attached event listeners
browserWin.on('close', () => {
logger.info(
`child-window-handler: close event occurred for window with url ${newWinUrl}!`,
`child-window-handler: close event occurred for window with url ${details.url}!`,
);
// Subscribe events for main view - snack bar
mainEvents.unsubscribeMultipleEvents(
CHILD_WINDOW_EVENTS,
browserWin.webContents,
);
removeWindowEventListener(browserWin);
});
@ -268,11 +333,6 @@ export const handleChildWindow = (webContents: WebContents): void => {
// validate link and create a child window or open in browser
handleChildWindow(browserWin.webContents);
// Certificate verification proxy
// if (!isDevEnv) {
// browserWin.webContents.session.setCertificateVerifyProc(handleCertificateProxyVerification);
// }
// Updates media permissions for preload context
const { permissions } = config.getConfigFields(['permissions']);
browserWin.webContents.send(
@ -280,25 +340,13 @@ export const handleChildWindow = (webContents: WebContents): void => {
permissions.media,
);
}
// Subscribe events for main view - snack bar
mainEvents.subscribeMultipleEvents(
CHILD_WINDOW_EVENTS,
browserWin.webContents,
);
});
} else {
event.preventDefault();
if (newWinUrl && newWinUrl.length > 2083) {
logger.info(
`child-window-handler: new window url length is greater than 2083, not performing any action!`,
);
return;
}
if (!verifyProtocolForNewUrl(newWinUrl)) {
logger.info(
`child-window-handler: new window url protocol is not valid, not performing any action!`,
);
return;
}
logger.info(`child-window-handler: new window url is ${newWinUrl} which is not of the same host / protocol,
so opening it in the default app!`);
windowHandler.openUrlInDefaultBrowser(newWinUrl);
}
};
webContents.on('new-window', childWindow);
},
);
};

View File

@ -1,4 +1,4 @@
import { app, crashReporter, Details, dialog } from 'electron';
import { app, crashReporter, dialog, RenderProcessGoneDetails } from 'electron';
import { i18n } from '../common/i18n';
import { logger } from '../common/logger';
import {
@ -89,7 +89,7 @@ class CrashHandler {
public handleRendererCrash(browserWindow: ICustomBrowserWindow) {
browserWindow.webContents.on(
'render-process-gone',
async (_event: Event, details: Details) => {
async (_event: Event, details: RenderProcessGoneDetails) => {
logger.info(`crash-handler: Renderer process for ${browserWindow.winName} crashed.
Reason is ${details.reason}`);
const eventData: ICrashData = {

View File

@ -1,11 +1,17 @@
import { BrowserWindow, ipcMain } from 'electron';
import {
BrowserWindow,
clipboard,
dialog,
ipcMain,
systemPreferences,
} from 'electron';
import {
apiCmds,
apiName,
IApiArgs,
INotificationData,
} from '../common/api-interface';
import { LocaleType } from '../common/i18n';
import { i18n, LocaleType } from '../common/i18n';
import { logger } from '../common/logger';
import { activityDetection } from './activity-detection';
import { analytics } from './analytics-handler';
@ -22,6 +28,7 @@ import { activate, handleKeyPress } from './window-actions';
import { ICustomBrowserWindow, windowHandler } from './window-handler';
import {
downloadManagerAction,
isValidView,
isValidWindow,
sanitize,
setDataUrl,
@ -40,7 +47,12 @@ import {
ipcMain.on(
apiName.symphonyApi,
async (event: Electron.IpcMainEvent, arg: IApiArgs) => {
if (!isValidWindow(BrowserWindow.fromWebContents(event.sender))) {
if (
!(
isValidWindow(BrowserWindow.fromWebContents(event.sender)) ||
isValidView(event.sender)
)
) {
logger.error(
`main-api-handler: invalid window try to perform action, ignoring action`,
arg.cmd,
@ -295,8 +307,89 @@ ipcMain.on(
case apiCmds.autoUpdate:
autoUpdate.update(arg.filename);
break;
case apiCmds.aboutAppClipBoardData:
if (arg.clipboard && arg.clipboardType) {
clipboard.write(
{ text: JSON.stringify(arg.clipboard, null, 4) },
arg.clipboardType,
);
}
break;
case apiCmds.closeMainWindow:
windowHandler.getMainWindow()?.close();
break;
case apiCmds.minimizeMainWindow:
windowHandler.getMainWindow()?.minimize();
break;
case apiCmds.maximizeMainWindow:
windowHandler.getMainWindow()?.maximize();
break;
case apiCmds.unmaximizeMainWindow:
const mainWindow = windowHandler.getMainWindow();
if (mainWindow && windowExists(mainWindow)) {
mainWindow.isFullScreen()
? mainWindow.setFullScreen(false)
: mainWindow.unmaximize();
}
break;
default:
break;
}
},
);
ipcMain.handle(
apiName.symphonyApi,
async (event: Electron.IpcMainInvokeEvent, arg: IApiArgs) => {
if (
!(
isValidWindow(BrowserWindow.fromWebContents(event.sender)) ||
isValidView(event.sender)
)
) {
logger.error(
`main-api-handler: invalid window try to perform action, ignoring action`,
arg.cmd,
);
return;
}
if (!arg) {
return;
}
switch (arg.cmd) {
case apiCmds.getCurrentOriginUrl:
return windowHandler.getMainWindow()?.origin;
case apiCmds.isAeroGlassEnabled:
return systemPreferences.isAeroGlassEnabled();
case apiCmds.showScreenSharePermissionDialog: {
const focusedWindow = BrowserWindow.getFocusedWindow();
if (focusedWindow && !focusedWindow.isDestroyed()) {
await dialog.showMessageBox(focusedWindow, {
message: `${i18n.t(
'Your administrator has disabled sharing your screen. Please contact your admin for help',
'Permissions',
)()}`,
title: `${i18n.t('Permission Denied')()}!`,
type: 'error',
});
return;
}
return;
}
case apiCmds.getMediaAccessStatus:
const camera = systemPreferences.getMediaAccessStatus('camera');
const microphone = systemPreferences.getMediaAccessStatus('microphone');
const screen = systemPreferences.getMediaAccessStatus('screen');
return {
camera,
microphone,
screen,
};
default:
break;
}
return;
},
);

View File

@ -0,0 +1,92 @@
import { WebContents } from 'electron';
export class MainProcessEvents {
private registeredWebContents: Map<string, Set<WebContents>> = new Map<
string,
Set<Electron.WebContents>
>();
/**
* Broadcasts events to all the registered webContents
* @param eventName
* @param args
*/
public publish(eventName: string, args?: any[]) {
const allWebContests = this.registeredWebContents.get(eventName);
if (!allWebContests) {
return;
}
allWebContests.forEach((w) => {
if (w && !w.isDestroyed()) {
w.send(eventName, args);
}
});
}
/**
* Subscribe multiple events for the webContents
* @param eventNames
* @param webContents
*/
public subscribeMultipleEvents(
eventNames: string[],
webContents: WebContents,
) {
eventNames.forEach((e) => this.subscribe(e, webContents));
}
/**
* Unsubscribe multiple events for the webContents
* @param eventNames
* @param webContents
*/
public unsubscribeMultipleEvents(
eventNames: string[],
webContents: WebContents,
) {
eventNames.forEach((e) => this.unsubscribe(e, webContents));
}
/**
* Subscribe event for the webContents
* @param eventName
* @param webContents
*/
public subscribe(eventName: string, webContents: WebContents) {
if (!webContents || webContents.isDestroyed()) {
return;
}
const registeredWebContents = this.registeredWebContents.get(eventName);
if (registeredWebContents) {
const isRegistered = registeredWebContents.has(webContents);
if (isRegistered) {
return;
}
registeredWebContents.add(webContents);
return;
}
this.registeredWebContents.set(eventName, new Set<WebContents>());
this.registeredWebContents.get(eventName)!.add(webContents);
}
/**
* Unsubscribe an event from a specific webContents
* @param eventName
* @param webContents
*/
public unsubscribe(eventName: string, webContents: WebContents) {
if (!webContents || webContents.isDestroyed()) {
return;
}
const registeredWebContents = this.registeredWebContents.get(eventName);
if (registeredWebContents) {
if (registeredWebContents.has(webContents)) {
registeredWebContents.delete(webContents);
}
}
}
}
const mainEvents = new MainProcessEvents();
export { mainEvents };

View File

@ -2,7 +2,7 @@ import { app, systemPreferences } from 'electron';
import * as electronDownloader from 'electron-dl';
import * as shellPath from 'shell-path';
import { isDevEnv, isElectronQA, isLinux, isMac } from '../common/env';
import { isDevEnv, isLinux, isMac } from '../common/env';
import { logger } from '../common/logger';
import { getCommandLineArgs } from '../common/utils';
import { cleanUpAppCache, createAppCacheFile } from './app-cache-handler';
@ -60,12 +60,6 @@ electronDownloader();
handlePerformanceSettings();
setChromeFlags();
// Need this to prevent blank pop-out from 8.x versions
// Refer - SDA-1877 - https://github.com/electron/electron/issues/18397
if (!isElectronQA) {
app.allowRendererProcessReuse = true;
}
// Electron sets the default protocol
if (!isDevEnv) {
const { userDataPath } = config.getConfigFields(['userDataPath']);

View File

@ -19,26 +19,11 @@ export class ElectronNotification extends Notification {
this.callback = callback;
this.options = options;
this.once('click', this.onClick);
this.once('reply', this.onReply);
}
/**
* Notification on click handler
* @param _event
* @private
*/
private onClick(_event: Event) {
this.callback(NotificationActions.notificationClicked, this.options);
}
/**
* Notification reply handler
* @param _event
* @param reply
* @private
*/
private onReply(_event: Event, reply: string) {
this.callback(NotificationActions.notificationReply, this.options, reply);
this.once('click', (_event) => {
this.callback(NotificationActions.notificationClicked, this.options);
});
this.once('reply', (_event, reply) => {
this.callback(NotificationActions.notificationReply, this.options, reply);
});
}
}

View File

@ -6,7 +6,6 @@ import {
import { isWindowsOS } from '../../common/env';
import { notification } from '../../renderer/notification';
import { windowHandler } from '../window-handler';
import { windowExists } from '../window-utils';
import { ElectronNotification } from './electron-notification';
class NotificationHelper {
@ -80,9 +79,9 @@ class NotificationHelper {
data: ElectronNotificationData,
notificationData: ElectronNotificationData,
) {
const mainWindow = windowHandler.getMainWindow();
if (mainWindow && windowExists(mainWindow) && mainWindow.webContents) {
mainWindow.webContents.send('notification-actions', {
const mainWebContents = windowHandler.getMainWebContents();
if (mainWebContents && !mainWebContents.isDestroyed()) {
mainWebContents.send('notification-actions', {
event,
data,
notificationData,

View File

@ -12,6 +12,7 @@ import { logger } from '../common/logger';
import { throttle } from '../common/utils';
import { notification } from '../renderer/notification';
import { CloudConfigDataTypes, config } from './config-handler';
import { mainEvents } from './main-event-handler';
import { ICustomBrowserWindow, windowHandler } from './window-handler';
import { showPopupMenu, windowExists } from './window-utils';
@ -97,10 +98,11 @@ const windowMaximized = async (): Promise<void> => {
}
};
const throttledWindowChanges = throttle(async () => {
const throttledWindowChanges = throttle(async (eventName) => {
await saveWindowSettings();
await windowMaximized();
notification.moveNotificationToTop();
mainEvents.publish(eventName);
}, 1000);
const throttledWindowRestore = throttle(async () => {
@ -303,14 +305,18 @@ export const monitorWindowActions = (window: BrowserWindow): void => {
eventNames.forEach((event: string) => {
if (window) {
// @ts-ignore
window.on(event, throttledWindowChanges);
window.on(event, () => throttledWindowChanges(event));
}
});
window.on('enter-full-screen', throttledWindowChanges);
window.on('maximize', throttledWindowChanges);
window.on('enter-full-screen', () =>
throttledWindowChanges('enter-full-screen'),
);
window.on('maximize', () => throttledWindowChanges('maximize'));
window.on('leave-full-screen', throttledWindowChanges);
window.on('unmaximize', throttledWindowChanges);
window.on('leave-full-screen', () =>
throttledWindowChanges('leave-full-screen'),
);
window.on('unmaximize', () => throttledWindowChanges('unmaximize'));
if ((window as ICustomBrowserWindow).winName === apiName.mainWindowName) {
window.on('restore', throttledWindowRestore);

View File

@ -8,8 +8,10 @@ import {
dialog,
globalShortcut,
ipcMain,
RenderProcessGoneDetails,
screen,
shell,
WebContents,
} from 'electron';
import * as fs from 'fs';
import * as path from 'path';
@ -41,6 +43,7 @@ import {
IGlobalConfig,
} from './config-handler';
import crashHandler from './crash-handler';
import { mainEvents } from './main-event-handler';
import { SpellChecker } from './spell-check-handler';
import { checkIfBuildExpired } from './ttl-handler';
import { versionHandler } from './version-handler';
@ -58,10 +61,12 @@ import {
handleDownloadManager,
injectStyles,
isSymphonyReachable,
loadBrowserViews,
monitorNetworkInterception,
preventWindowNavigation,
reloadWindow,
resetZoomLevel,
viewExists,
windowExists,
zoomIn,
zoomOut,
@ -79,7 +84,9 @@ enum ClientSwitchType {
CLIENT_2_0_DAILY = 'CLIENT_2_0_DAILY',
}
interface ICustomBrowserWindowConstructorOpts
const MAIN_WEB_CONTENTS_EVENTS = ['enter-full-screen', 'leave-full-screen'];
export interface ICustomBrowserWindowConstructorOpts
extends Electron.BrowserWindowConstructorOptions {
winKey: string;
}
@ -90,9 +97,15 @@ export interface ICustomBrowserWindow extends Electron.BrowserWindow {
origin?: string;
}
export interface ICustomBrowserView extends Electron.BrowserView {
winName: string;
notificationData?: object;
origin?: string;
}
// Default window width & height
let DEFAULT_WIDTH: number = 900;
let DEFAULT_HEIGHT: number = 900;
export let DEFAULT_WIDTH: number = 900;
export let DEFAULT_HEIGHT: number = 900;
// Timeout on restarting SDA in case it's stuck
const LISTEN_TIMEOUT: number = 25 * 1000;
@ -113,6 +126,9 @@ export class WindowHandler {
}
return format(parsedUrl);
}
public mainView: ICustomBrowserView | null;
public titleBarView: ICustomBrowserView | null;
public mainWebContents: WebContents | undefined;
public appMenu: AppMenu | null;
public isAutoReload: boolean;
public isOnline: boolean;
@ -233,6 +249,8 @@ export class WindowHandler {
}
this.appMenu = null;
this.mainView = null;
this.titleBarView = null;
const locale: LocaleType = (this.config.locale ||
app.getLocale()) as LocaleType;
i18n.setLocale(locale);
@ -387,8 +405,24 @@ export class WindowHandler {
cleanAppCacheOnCrash(this.mainWindow);
// loads the main window with url from config/cmd line
logger.info(`Loading main window with url ${this.url}`);
const userAgent = this.getUserAgent(this.mainWindow);
this.mainWindow.loadURL(this.url, { userAgent });
const userAgent = this.getUserAgent(this.mainWindow.webContents);
if (
this.config.isCustomTitleBar === CloudConfigDataTypes.ENABLED &&
isWindowsOS &&
this.mainWindow &&
windowExists(this.mainWindow)
) {
this.mainWebContents = await loadBrowserViews(
this.mainWindow,
this.url,
userAgent,
);
} else {
await this.mainWindow.loadURL(this.url, { userAgent });
this.mainWebContents = this.mainWindow.webContents;
}
// check for build expiry in case of test builds
this.checkExpiry(this.mainWindow);
// update version info from server
@ -397,20 +431,12 @@ export class WindowHandler {
this.mainWindow.origin = this.globalConfig.contextOriginUrl || this.url;
// Event needed to hide native menu bar on Windows 10 as we use custom menu bar
this.mainWindow.webContents.once('did-start-loading', () => {
this.mainView?.webContents.once('did-start-loading', () => {
logger.info(
`window-handler: main window web contents started loading for url ${this.mainWindow?.webContents.getURL()}!`,
`window-handler: main window web contents started loading for url ${this.mainView?.webContents.getURL()}!`,
);
this.finishedLoading = false;
this.listenForLoad();
if (
this.config.isCustomTitleBar === CloudConfigDataTypes.ENABLED &&
isWindowsOS &&
this.mainWindow &&
windowExists(this.mainWindow)
) {
this.mainWindow.setMenuBarVisibility(false);
}
// monitors network connection and
// displays error banner on failure
monitorNetworkInterception(
@ -441,50 +467,55 @@ export class WindowHandler {
logger.info(`window-handler: Main Window ready to show: ${event}`);
});
this.mainWindow.webContents.on('did-finish-load', async () => {
this.mainWebContents.on('did-finish-load', async () => {
// reset to false when the client reloads
this.isMana = false;
logger.info(`window-handler: main window web contents finished loading!`);
// early exit if the window has already been destroyed
if (!this.mainWindow || !windowExists(this.mainWindow)) {
if (!this.mainWebContents || this.mainWebContents.isDestroyed()) {
logger.info(
`window-handler: main window web contents destroyed already! exiting`,
);
return;
}
this.finishedLoading = true;
this.url = this.mainWindow.webContents.getURL();
if (this.url.indexOf('about:blank') === 0) {
this.url = this.mainWebContents?.getURL();
if (this.url?.indexOf('about:blank') === 0) {
logger.info(
`Looks like about:blank got loaded which may lead to blank screen`,
);
logger.info(`Reloading the app to check if it resolves the issue`);
const url = this.userConfig.url || this.globalConfig.url;
const userAgent = this.getUserAgent(this.mainWindow);
await this.mainWindow.loadURL(url, { userAgent });
const userAgent = this.getUserAgent(this.mainWebContents);
await this.mainWebContents?.loadURL(url, { userAgent });
return;
}
logger.info('window-handler: did-finish-load, url: ' + this.url);
// Injects custom title bar and snack bar css into the webContents
await injectStyles(this.mainWindow, this.isCustomTitleBar);
if (this.mainWebContents && !this.mainWebContents.isDestroyed()) {
// Injects custom title bar and snack bar css into the webContents
await injectStyles(this.mainWebContents, this.isCustomTitleBar);
this.mainWebContents.send('page-load', {
isWindowsOS,
locale: i18n.getLocale(),
resources: i18n.loadedResources,
isMainWindow: true,
});
this.mainWindow.webContents.send('page-load', {
isWindowsOS,
locale: i18n.getLocale(),
resources: i18n.loadedResources,
enableCustomTitleBar: this.isCustomTitleBar,
isMainWindow: true,
});
this.appMenu = new AppMenu();
const { permissions } = config.getConfigFields(['permissions']);
this.mainWindow.webContents.send(
'is-screen-share-enabled',
permissions.media,
);
this.appMenu = new AppMenu();
const { permissions } = config.getConfigFields(['permissions']);
this.mainWebContents.send('is-screen-share-enabled', permissions.media);
// Subscribe events for main view - snack bar
mainEvents.subscribeMultipleEvents(
MAIN_WEB_CONTENTS_EVENTS,
this.mainWebContents,
);
}
});
this.mainWindow.webContents.on(
this.mainWebContents.on(
'did-fail-load',
(_event, errorCode, errorDesc, validatedURL) => {
logger.error(
@ -494,13 +525,13 @@ export class WindowHandler {
},
);
this.mainWindow.webContents.on('did-stop-loading', async () => {
this.mainWebContents.on('did-stop-loading', async () => {
if (this.mainWindow && windowExists(this.mainWindow)) {
this.mainWindow.webContents.send('page-load-failed', {
this.mainWebContents?.send('page-load-failed', {
locale: i18n.getLocale(),
resources: i18n.loadedResources,
});
const href = await this.mainWindow.webContents.executeJavaScript(
const href = await this.mainWebContents?.executeJavaScript(
'document.location.href',
);
try {
@ -509,7 +540,7 @@ export class WindowHandler {
href === 'chrome-error://chromewebdata/'
) {
if (this.mainWindow && windowExists(this.mainWindow)) {
this.mainWindow.webContents.insertCSS(
this.mainWebContents?.insertCSS(
fs
.readFileSync(
path.join(
@ -521,7 +552,7 @@ export class WindowHandler {
)
.toString(),
);
this.mainWindow.webContents.send('network-error', {
this.mainWebContents?.send('network-error', {
error: this.loadFailError,
});
isSymphonyReachable(
@ -537,12 +568,14 @@ export class WindowHandler {
);
}
}
// Register dev tools on initial launch
this.registerGlobalShortcuts();
});
this.mainWindow.webContents.on(
'crashed',
async (_event: Event, killed: boolean) => {
if (killed) {
this.mainWebContents.on(
'render-process-gone',
async (_event: Event, details: RenderProcessGoneDetails) => {
if (details.reason === 'killed') {
logger.info(`window-handler: main window crashed (killed)!`);
return;
}
@ -581,8 +614,12 @@ export class WindowHandler {
if (this.willQuitApp) {
logger.info(`window-handler: app is quitting, destroying all windows!`);
if (this.mainWindow && this.mainWindow.webContents.isDevToolsOpened()) {
this.mainWindow.webContents.closeDevTools();
if (
this.mainWindow &&
!this.mainWebContents?.isDestroyed() &&
this.mainWebContents?.isDevToolsOpened()
) {
this.mainWebContents?.closeDevTools();
}
return this.destroyAllWindows();
}
@ -629,7 +666,7 @@ export class WindowHandler {
// Certificate verification proxy
if (!isDevEnv) {
this.mainWindow.webContents.session.setCertificateVerifyProc(
this.mainWebContents.session.setCertificateVerifyProc(
handleCertificateProxyVerification,
);
}
@ -646,25 +683,22 @@ export class WindowHandler {
preventWindowNavigation(this.mainWindow, false);
// Handle media/other permissions
handlePermissionRequests(this.mainWindow.webContents);
handlePermissionRequests(this.mainWebContents);
// Start monitoring window actions
monitorWindowActions(this.mainWindow);
// Download manager
this.mainWindow.webContents.session.on(
'will-download',
handleDownloadManager,
);
this.mainWebContents.session.on('will-download', handleDownloadManager);
// store window ref
this.addWindow(this.windowOpts.winKey, this.mainWindow);
// Handle pop-outs window
handleChildWindow(this.mainWindow.webContents);
handleChildWindow(this.mainWebContents);
if (this.config.enableRendererLogs) {
this.mainWindow.webContents.on('console-message', onConsoleMessages);
this.mainWebContents.on('console-message', onConsoleMessages);
}
return this.mainWindow;
@ -697,13 +731,12 @@ export class WindowHandler {
}
logger.info(`finished loading welcome screen.`);
if (this.url.indexOf('welcome')) {
const ssoValue =
const ssoValue = !!(
this.userConfig.url &&
this.userConfig.url.indexOf('/login/sso/initsso') > -1
? true
: false;
);
this.mainWindow.webContents.send('page-load-welcome', {
this.mainWindow.webContents?.send('page-load-welcome', {
locale: i18n.getLocale(),
resource: i18n.loadedResources,
});
@ -716,7 +749,7 @@ export class WindowHandler {
)
: this.userConfig.url;
this.mainWindow.webContents.send('welcome', {
this.mainWindow.webContents?.send('welcome', {
url: userConfigUrl || this.startUrl,
message: '',
urlValid: !!userConfigUrl,
@ -739,6 +772,20 @@ export class WindowHandler {
return this.mainWindow;
}
/**
* Gets the main browser webContents
*/
public getMainWebContents(): WebContents | undefined {
return this.mainWebContents;
}
/**
* Gets the main browser view
*/
public getMainView(): ICustomBrowserView | null {
return this.mainView;
}
/**
* Gets all the window that we have created
*
@ -749,6 +796,33 @@ export class WindowHandler {
return this.windows;
}
/**
* Gets the main window opts
*
* @return ICustomBrowserWindowConstructorOpts
*/
public getMainWindowOpts(): ICustomBrowserWindowConstructorOpts {
return this.windowOpts;
}
/**
* Sets the title bar view
*
* @param mainView
*/
public setMainView(mainView: ICustomBrowserView): void {
this.mainView = mainView;
}
/**
* Sets the title bar view
*
* @param titleBarView
*/
public setTitleBarView(titleBarView: ICustomBrowserView): void {
this.titleBarView = titleBarView;
}
/**
* Closes the window from an event emitted by the render processes
*
@ -837,12 +911,27 @@ export class WindowHandler {
/**
* Checks if the window and a key has a window
*
* @param key {string}
* @param window {Electron.BrowserWindow}
*/
public hasWindow(key: string, window: Electron.BrowserWindow): boolean {
const browserWindow = this.windows[key];
return browserWindow && window === browserWindow;
public hasWindow(window: Electron.BrowserWindow): boolean {
for (const key in this.windows) {
if (this.windows[key] === window) {
return true;
}
}
return this.aboutAppWindow === window;
}
/**
* Checks if the window and a key has a window
*
* @param webContents {Electron.webContents}
*/
public hasView(webContents: Electron.webContents): boolean {
return (
webContents === this.mainView?.webContents ||
webContents === this.titleBarView?.webContents
);
}
/**
@ -1756,15 +1845,15 @@ export class WindowHandler {
/**
* Reloads symphony in case of network failures
*/
public reloadSymphony() {
if (this.mainWindow && windowExists(this.mainWindow)) {
public async reloadSymphony() {
if (this.mainWebContents && !this.mainWebContents.isDestroyed()) {
// If the client is fully loaded, upon network interruption, load that
if (this.isLoggedIn) {
logger.info(
`window-utils: user has logged in, getting back to Symphony app`,
);
const userAgent = this.getUserAgent(this.mainWindow);
this.mainWindow.loadURL(
const userAgent = this.getUserAgent(this.mainWebContents);
await this.mainWebContents.loadURL(
this.url || this.userConfig.url || this.globalConfig.url,
{ userAgent },
);
@ -1774,10 +1863,13 @@ export class WindowHandler {
logger.info(
`window-utils: user hasn't logged in yet, loading login page again`,
);
const userAgent = this.getUserAgent(this.mainWindow);
this.mainWindow.loadURL(this.userConfig.url || this.globalConfig.url, {
userAgent,
});
const userAgent = this.getUserAgent(this.mainWebContents);
await this.mainWebContents.loadURL(
this.userConfig.url || this.globalConfig.url,
{
userAgent,
},
);
}
}
@ -1788,16 +1880,16 @@ export class WindowHandler {
setTimeout(async () => {
if (!this.finishedLoading) {
logger.info(`window-handler: Pod load failed on launch`);
if (this.mainWindow && windowExists(this.mainWindow)) {
const webContentsUrl = this.mainWindow.webContents.getURL();
if (this.mainWebContents && !this.mainWebContents.isDestroyed()) {
const webContentsUrl = this.mainWebContents?.getURL();
logger.info(
`window-handler: Current main window url is ${webContentsUrl}.`,
);
const reloadUrl =
webContentsUrl || this.userConfig.url || this.globalConfig.url;
logger.info(`window-handler: Trying to reload ${reloadUrl}.`);
const userAgent = this.getUserAgent(this.mainWindow);
await this.mainWindow.loadURL(reloadUrl, { userAgent });
const userAgent = this.getUserAgent(this.mainWebContents);
await this.mainWebContents.loadURL(reloadUrl, { userAgent });
return;
}
logger.error(
@ -1829,9 +1921,8 @@ export class WindowHandler {
*/
private registerGlobalShortcuts(): void {
logger.info('window-handler: register global shortcuts!');
globalShortcut.register(
isMac ? 'Cmd+Alt+I' : 'Ctrl+Shift+I',
this.onRegisterDevtools,
globalShortcut.register(isMac ? 'Cmd+Alt+I' : 'Ctrl+Shift+I', () =>
this.onRegisterDevtools(),
);
globalShortcut.register('CmdOrCtrl+R', this.onReload);
@ -1895,6 +1986,16 @@ export class WindowHandler {
}
const { devToolsEnabled } = config.getConfigFields(['devToolsEnabled']);
if (devToolsEnabled) {
if (
this.mainWindow &&
windowExists(this.mainWindow) &&
focusedWindow === this.mainWindow
) {
if (this.mainView && viewExists(this.mainView)) {
this.mainWebContents?.toggleDevTools();
return;
}
}
focusedWindow.webContents.toggleDevTools();
return;
}
@ -1922,7 +2023,7 @@ export class WindowHandler {
private async switchClient(clientSwitch: ClientSwitchType): Promise<void> {
logger.info(`window handler: switch to client ${clientSwitch}`);
if (!this.mainWindow || !windowExists(this.mainWindow)) {
if (!this.mainWebContents || this.mainWebContents.isDestroyed()) {
logger.info(
`window-handler: switch client - main window web contents destroyed already! exiting`,
);
@ -1933,7 +2034,7 @@ export class WindowHandler {
this.url = this.globalConfig.url;
}
const parsedUrl = parse(this.url);
const csrfToken = await this.mainWindow.webContents.executeJavaScript(
const csrfToken = await this.mainWebContents?.executeJavaScript(
`localStorage.getItem('x-km-csrf-token')`,
);
switch (clientSwitch) {
@ -1949,9 +2050,9 @@ export class WindowHandler {
default:
this.url = this.globalConfig.url + `?x-km-csrf-token=${csrfToken}`;
}
this.execCmd(this.screenShareIndicatorFrameUtil, []);
const userAgent = this.getUserAgent(this.mainWindow);
await this.mainWindow.loadURL(this.url, { userAgent });
await this.execCmd(this.screenShareIndicatorFrameUtil, []);
const userAgent = this.getUserAgent(this.mainWebContents);
await this.mainWebContents.loadURL(this.url, { userAgent });
} catch (e) {
logger.error(
`window-handler: failed to switch client because of error ${e}`,
@ -2038,12 +2139,12 @@ export class WindowHandler {
* getUserAgent retrieves current window user-agent and updates it
* depending on global config setup
* Electron user-agent is removed due to Microsoft Azure not supporting SSO if found - cf SDA-3201
* @param mainWindow
* @param webContents
* @returns updated user-agents
*/
private getUserAgent(mainWindow: ICustomBrowserWindow): string {
private getUserAgent(webContents: WebContents): string {
const doOverrideUserAgents = !!this.globalConfig.overrideUserAgent;
let userAgent = mainWindow.webContents.getUserAgent();
let userAgent = webContents.getUserAgent();
if (doOverrideUserAgents) {
const electronUserAgentRegex = /Electron/;
userAgent = userAgent.replace(electronUserAgentRegex, 'ElectronSymphony');

View File

@ -1,10 +1,12 @@
import {
app,
BrowserView,
BrowserWindow,
dialog,
nativeImage,
screen,
shell,
WebContents,
} from 'electron';
import electron = require('electron');
import fetch from 'electron-fetch';
@ -14,7 +16,13 @@ import * as path from 'path';
import { format, parse } from 'url';
import { apiName } from '../common/api-interface';
import { isDevEnv, isLinux, isMac, isNodeEnv } from '../common/env';
import {
isDevEnv,
isLinux,
isMac,
isNodeEnv,
isWindowsOS,
} from '../common/env';
import { i18n, LocaleType } from '../common/i18n';
import { logger } from '../common/logger';
import { getGuid } from '../common/utils';
@ -30,9 +38,16 @@ import { downloadHandler, IDownloadItem } from './download-handler';
import { memoryMonitor } from './memory-monitor';
import { screenSnippet } from './screen-snippet-handler';
import { updateAlwaysOnTop } from './window-actions';
import { ICustomBrowserWindow, windowHandler } from './window-handler';
import {
DEFAULT_HEIGHT,
DEFAULT_WIDTH,
ICustomBrowserView,
ICustomBrowserWindow,
windowHandler,
} from './window-handler';
import { notification } from '../renderer/notification';
import { mainEvents } from './main-event-handler';
interface IStyles {
name: styleNames;
content: string;
@ -45,7 +60,10 @@ enum styleNames {
}
const checkValidWindow = true;
const { ctWhitelist } = config.getConfigFields(['ctWhitelist']);
const { ctWhitelist, mainWinPos } = config.getConfigFields([
'ctWhitelist',
'mainWinPos',
]);
// Network status check variables
const networkStatusCheckInterval = 10 * 1000;
@ -55,6 +73,13 @@ let isNetworkMonitorInitialized = false;
const styles: IStyles[] = [];
const DOWNLOAD_MANAGER_NAMESPACE = 'DownloadManager';
const TITLE_BAR_EVENTS = [
'maximize',
'unmaximize',
'enter-full-screen',
'leave-full-screen',
];
/**
* Checks if window is valid and exists
*
@ -64,6 +89,17 @@ const DOWNLOAD_MANAGER_NAMESPACE = 'DownloadManager';
export const windowExists = (window: BrowserWindow): boolean =>
!!window && typeof window.isDestroyed === 'function' && !window.isDestroyed();
/**
* Checks if view is valid and exists
*
* @param view {BrowserView}
* @return boolean
*/
export const viewExists = (view: BrowserView): boolean =>
!!view &&
typeof view.webContents.isDestroyed === 'function' &&
!view.webContents.isDestroyed();
/**
* Prevents window from navigating
*
@ -265,9 +301,31 @@ export const isValidWindow = (
}
let result: boolean = false;
if (browserWin && !browserWin.isDestroyed()) {
// @ts-ignore
const winKey = browserWin.webContents.browserWindowOptions.winKey;
result = windowHandler.hasWindow(winKey, browserWin);
result = windowHandler.hasWindow(browserWin);
}
if (!result) {
logger.warn(
`window-utils: invalid window try to perform action, ignoring action`,
);
}
return result;
};
/**
* Ensure events comes from a view that we have created.
*
* @return {Boolean} returns true if exists otherwise false
* @param webContents
*/
export const isValidView = (webContents: Electron.webContents): boolean => {
if (!checkValidWindow) {
return true;
}
let result: boolean = false;
if (webContents && !webContents.isDestroyed()) {
result = windowHandler.hasView(webContents);
}
if (!result) {
@ -502,22 +560,22 @@ export const handleDownloadManager = (
/**
* Inserts css in to the window
*
* @param window {BrowserWindow}
* @param mainWebContents {WebContents}
*/
const readAndInsertCSS = async (window): Promise<IStyles[] | void> => {
if (window && windowExists(window)) {
return styles.map(({ content }) => window.webContents.insertCSS(content));
const readAndInsertCSS = async (mainWebContents): Promise<IStyles[] | void> => {
if (mainWebContents && !mainWebContents.isDestroyed()) {
return styles.map(({ content }) => mainWebContents.insertCSS(content));
}
};
/**
* Inserts all the required css on to the specified windows
*
* @param mainWindow {BrowserWindow}
* @param mainView {WebContents}
* @param isCustomTitleBar {boolean} - whether custom title bar enabled
*/
export const injectStyles = async (
mainWindow: BrowserWindow,
mainView: WebContents,
isCustomTitleBar: boolean,
): Promise<IStyles[] | void> => {
if (isCustomTitleBar) {
@ -578,7 +636,7 @@ export const injectStyles = async (
});
}
await readAndInsertCSS(mainWindow);
await readAndInsertCSS(mainView);
return;
};
@ -638,12 +696,12 @@ export const isSymphonyReachable = (
const podUrl = `${protocol}//${hostname}`;
logger.info(`window-utils: checking to see if pod ${podUrl} is reachable!`);
fetch(podUrl, { method: 'GET' })
.then((rsp) => {
.then(async (rsp) => {
if (rsp.status === 200 && windowHandler.isOnline) {
logger.info(
`window-utils: pod ${podUrl} is reachable, loading main window!`,
);
windowHandler.reloadSymphony();
await windowHandler.reloadSymphony();
if (networkStatusCheckIntervalId) {
clearInterval(networkStatusCheckIntervalId);
networkStatusCheckIntervalId = null;
@ -673,11 +731,15 @@ export const reloadWindow = (browserWindow: ICustomBrowserWindow) => {
}
const windowName = browserWindow.winName;
const mainWindow = windowHandler.getMainWindow();
const mainWebContents = windowHandler.getMainWebContents();
// reload the main window
if (windowName === apiName.mainWindowName) {
if (
windowName === apiName.mainWindowName &&
mainWebContents &&
!mainWebContents.isDestroyed()
) {
logger.info(`window-utils: reloading the main window`);
browserWindow.reload();
mainWebContents.reload();
windowHandler.closeAllWindows();
@ -685,11 +747,12 @@ export const reloadWindow = (browserWindow: ICustomBrowserWindow) => {
return;
}
// Send an event to SFE that restarts the pop-out window
if (mainWindow && windowExists(mainWindow)) {
if (mainWebContents && !mainWebContents.isDestroyed()) {
logger.info(`window-handler: reloading the window`, { windowName });
const bounds = browserWindow.getBounds();
mainWindow.webContents.send('restart-floater', { windowName, bounds });
mainWebContents.send('restart-floater', { windowName, bounds });
}
};
@ -903,3 +966,131 @@ export const monitorNetworkInterception = (url: string) => {
);
}
};
export const loadBrowserViews = async (
mainWindow: BrowserWindow,
url: string,
userAgent: string,
): Promise<WebContents> => {
mainWindow.setMenuBarVisibility(false);
const titleBarView = new BrowserView({
webPreferences: {
sandbox: !isNodeEnv,
nodeIntegration: isNodeEnv,
preload: path.join(__dirname, '../renderer/_preload-component.js'),
devTools: isDevEnv,
},
}) as ICustomBrowserView;
const mainView = new BrowserView({
...windowHandler.getMainWindowOpts(),
...getBounds(mainWinPos, DEFAULT_WIDTH, DEFAULT_HEIGHT),
}) as ICustomBrowserView;
mainWindow.addBrowserView(titleBarView);
mainWindow.addBrowserView(mainView);
const titleBarWindowUrl = format({
pathname: require.resolve('../renderer/react-window.html'),
protocol: 'file',
query: {
componentName: 'title-bar',
locale: i18n.getLocale(),
},
slashes: true,
});
titleBarView.webContents.once('did-finish-load', () => {
if (!titleBarView || titleBarView.webContents.isDestroyed()) {
return;
}
titleBarView?.webContents.send('page-load', {
isWindowsOS,
locale: i18n.getLocale(),
resource: i18n.loadedResources,
isMainWindow: true,
});
mainEvents.subscribeMultipleEvents(
TITLE_BAR_EVENTS,
titleBarView.webContents,
);
mainWindow?.on('enter-full-screen', () => {
if (!titleBarView || !viewExists(titleBarView)) {
return;
}
const titleBarBounds = titleBarView.getBounds();
titleBarView.setBounds({ ...titleBarBounds, ...{ height: 0 } });
if (
!mainView ||
!viewExists(mainView) ||
!mainWindow ||
!windowExists(mainWindow)
) {
return;
}
const mainWindowBounds = mainWindow.getBounds();
const mainViewBounds = mainView.getBounds();
mainView.setBounds({
width: mainWindowBounds.width,
height: mainViewBounds.height,
x: 0,
y: 0,
});
});
mainWindow?.on('leave-full-screen', () => {
if (!titleBarView || !viewExists(titleBarView)) {
return;
}
const titleBarBounds = titleBarView.getBounds();
titleBarView.setBounds({ ...titleBarBounds, ...{ height: 32 } });
if (
!mainView ||
!viewExists(mainView) ||
!mainWindow ||
!windowExists(mainWindow)
) {
return;
}
const mainWindowBounds = mainWindow.getBounds();
mainView.setBounds({
width: mainWindowBounds.width,
height: mainWindowBounds.height,
x: mainWindowBounds.x,
y: 32,
});
});
if (mainWindow?.isMaximized()) {
mainEvents.publish('maximize');
}
if (mainWindow?.isFullScreen()) {
mainEvents.publish('enter-full-screen');
}
});
await titleBarView.webContents.loadURL(titleBarWindowUrl);
titleBarView.setBounds({
...mainWindow.getBounds(),
...{ x: 0, y: 0, height: 32 },
});
titleBarView.setAutoResize({
vertical: false,
horizontal: true,
width: true,
height: false,
});
await mainView.webContents.loadURL(url, { userAgent });
mainView.setBounds({ ...mainWindow.getBounds(), ...{ y: 32 } });
mainView.setAutoResize({
horizontal: true,
vertical: false,
width: true,
height: false,
});
windowHandler.setMainView(mainView);
windowHandler.setTitleBarView(mainView);
return mainView.webContents;
};

View File

@ -48,6 +48,15 @@ export enum apiCmds {
closeAllWrapperWindows = 'close-all-windows',
setZoomLevel = 'set-zoom-level',
autoUpdate = 'auto-update',
aboutAppClipBoardData = 'about-app-clip-board-data',
closeMainWindow = 'close-main-window',
minimizeMainWindow = 'minimize-main-window',
maximizeMainWindow = 'maximize-main-window',
unmaximizeMainWindow = 'unmaximize-main-window',
getCurrentOriginUrl = 'get-current-origin-url',
isAeroGlassEnabled = 'is-aero-glass-enabled',
showScreenSharePermissionDialog = 'show-screen-share-permission-dialog',
getMediaAccessStatus = 'get-media-access-status',
}
export enum apiName {
@ -89,6 +98,10 @@ export interface IApiArgs {
theme: Themes;
zoomLevel: number;
filename: string;
clipboard: string;
clipboardType: 'clipboard' | 'selection';
requestId: number;
mediaStatus: IMediaPermission;
}
export type Themes = 'light' | 'dark';
@ -195,9 +208,14 @@ export interface IDownloadManager {
}
export interface IMediaPermission {
camera: string;
microphone: string;
screen: string;
camera: 'not-determined' | 'granted' | 'denied' | 'restricted' | 'unknown';
microphone:
| 'not-determined'
| 'granted'
| 'denied'
| 'restricted'
| 'unknown';
screen: 'not-determined' | 'granted' | 'denied' | 'restricted' | 'unknown';
}
export interface ILogMsg {

View File

@ -45,7 +45,7 @@
</style>
<link rel="stylesheet" href="download-manager.css" />
</head>
<body>
<body style="background-color: white">
<h1>Symphony Electron API Demo</h1>
<div class="origin-reminder">
<p>
@ -331,6 +331,8 @@
openConfigWin.addEventListener('click', function () {
if (window.ssf) {
ssf.showNotificationSettings();
} else if (window.manaSSF) {
window.manaSSF.showNotificationSettings();
} else {
postMessage(apiCmds.showNotificationSettings);
}
@ -431,6 +433,8 @@
badgeCount++;
if (window.ssf) {
ssf.setBadgeCount(badgeCount);
} else if (window.manaSSF) {
window.manaSSF.setBadgeCount(badgeCount);
} else {
postMessage(apiCmds.setBadgeCount, badgeCount);
}
@ -441,6 +445,8 @@
badgeCount = 0;
if (window.ssf) {
ssf.setBadgeCount(0);
} else if (window.manaSSF) {
window.manaSSF.setBadgeCount(0);
} else {
postMessage(apiCmds.setBadgeCount, 0);
}
@ -448,21 +454,23 @@
const snippetButton = document.getElementById('snippet');
snippetButton.addEventListener('click', () => {
if (window.ssf) {
const gotSnippet = (rsp) => {
if (rsp) {
if (rsp.type && rsp.type === 'ERROR') {
// called when some error occurs during capture
alert('error getting snippet' + rsp.message);
} else if (rsp.data && rsp.type === 'image/png;base64') {
const dataUrl = 'data:' + rsp.type + ',' + rsp.data;
const img = document.getElementById('snippet-img');
img.src = dataUrl;
}
const gotSnippet = (rsp) => {
if (rsp) {
if (rsp.type && rsp.type === 'ERROR') {
// called when some error occurs during capture
alert('error getting snippet' + rsp.message);
} else if (rsp.data && rsp.type === 'image/png;base64') {
const dataUrl = 'data:' + rsp.type + ',' + rsp.data;
const img = document.getElementById('snippet-img');
img.src = dataUrl;
}
};
}
};
if (window.ssf) {
const screenSnippet = new window.ssf.ScreenSnippet();
screenSnippet.capture().then(gotSnippet).catch(gotSnippet);
} else if (window.manaSSF) {
window.manaSSF.openScreenSnippet(gotSnippet);
} else {
postMessage(apiCmds.openScreenSnippet);
}
@ -473,6 +481,8 @@
if (window.ssf) {
const screenSnippet = new window.ssf.ScreenSnippet();
screenSnippet.cancelCapture();
} else if (window.manaSSF) {
window.manaSSF.closeScreenSnippet();
} else {
postMessage(apiCmds.closeScreenSnippet);
}
@ -504,6 +514,8 @@
front.addEventListener('click', () => {
if (window.ssf) {
window.ssf.activate(win.name);
} else if (window.manaSSF) {
window.manaSSF.activate(win.name);
} else {
postMessage(apiCmds.activate, win.name);
}
@ -515,6 +527,8 @@
frontWithoutFocus.addEventListener('click', () => {
if (window.ssf) {
window.ssf.bringToFront(win.name, 'notification');
} else if (window.manaSSF) {
window.manaSSF.bringToFront(win.name, 'notification');
} else {
postMessage(apiCmds.bringToFront, {
windowName: win.name,
@ -540,6 +554,15 @@
searchApiVer.innerText = versionInfo.searchApiVer;
cpuArch.innerText = versionInfo.cpuArch;
});
} else if (window.manaSSF) {
window.manaSSF.getVersionInfo().then((versionInfo) => {
apiVersionInfo.innerText = versionInfo.apiVer;
containerIdentifier.innerText = versionInfo.containerIdentifier;
version.innerText = versionInfo.containerVer;
buildNumber.innerText = versionInfo.buildNumber;
searchApiVer.innerText = versionInfo.searchApiVer;
cpuArch.innerText = versionInfo.cpuArch;
});
} else {
postRequest(apiCmds.getVersionInfo, null, {
successCallback: (versionInfo) => {
@ -767,6 +790,8 @@
*/
if (window.ssf) {
window.ssf.registerRestartFloater(onRestartFloater);
} else if (window.manaSSF) {
window.manaSSF.registerRestartFloater(onRestartFloater);
} else {
postMessage(apiCmds.registerRestartFloater);
}
@ -780,6 +805,9 @@
if (window.ssf) {
window.ssf.registerActivityDetection(5000, activityCallback);
startActivityInterval();
} else if (window.manaSSF) {
window.manaSSF.registerActivityDetection(5000, activityCallback);
startActivityInterval();
} else {
postMessage(apiCmds.registerActivityDetection, 5000);
startActivityInterval();
@ -850,6 +878,16 @@
microphonePermission.innerText = mediaPermission.microphone;
screenPermission.innerText = mediaPermission.screen;
});
} else if (window.manaSSF) {
window.manaSSF.checkMediaPermission().then((mediaPermission) => {
console.log(
'check-media-permission, mediaPermission: ' +
JSON.stringify(mediaPermission),
);
cameraPermission.innerText = mediaPermission.camera;
microphonePermission.innerText = mediaPermission.microphone;
screenPermission.innerText = mediaPermission.screen;
});
} else {
postRequest(apiCmds.checkMediaPermission, null, {
successCallback: (mediaPermission) => {
@ -902,6 +940,32 @@
);
},
);
} else if (window.manaSSF) {
window.manaSSF.getMediaSource(
{ types: ['window', 'screen'] },
function (error, source) {
if (error) throw error;
navigator.webkitGetUserMedia(
{
audio: false,
video: {
mandatory: {
chromeMediaSource: 'desktop',
chromeMediaSourceId: source.id,
minWidth: 1280,
maxWidth: 1280,
minHeight: 720,
maxHeight: 720,
},
},
},
(stream) => {
handleStream(stream, source.display_id);
},
handleError,
);
},
);
} else {
const successHandler = (data) => {
const constraints = {
@ -956,6 +1020,21 @@
}
},
);
} else if (window.manaSSF) {
window.manaSSF.showScreenSharingIndicator(
{
stream,
displayId,
},
(event) => {
console.log('screen-sharing-indicator callback', event);
if (event.type === 'stopRequested') {
stream.getVideoTracks().forEach((t) => t.stop());
document.getElementById('video').srcObject = null;
window.manaSSF.closeScreenSharingIndicator(stream.id);
}
},
);
} else {
const success = (data) => {
if (data.error) {
@ -1052,6 +1131,8 @@
const id = downloadedItem.id;
if (window.ssf) {
window.ssf.openDownloadedItem(id);
} else if (window.manaSSF) {
window.manaSSF.openDownloadedItem(id);
} else {
postMessage(apiCmds.openDownloadedItem, id);
}
@ -1066,6 +1147,8 @@
const id = downloadedItem.id;
if (window.ssf) {
window.ssf.showDownloadedItem(id);
} else if (window.manaSSF) {
window.manaSSF.showDownloadedItem(id);
} else {
postMessage(apiCmds.showDownloadedItem, id);
}
@ -1077,6 +1160,8 @@
downloadedItem = undefined;
if (window.ssf) {
window.ssf.clearDownloadedItems();
} else if (window.manaSSF) {
window.manaSSF.clearDownloadedItems();
} else {
postMessage(apiCmds.clearDownloadedItems);
}
@ -1085,6 +1170,8 @@
document.getElementById('restart-app').addEventListener('click', () => {
if (window.ssf) {
window.ssf.restartApp();
} else if (window.manaSSF) {
window.manaSSF.restartApp();
} else {
postMessage(apiCmds.restartApp);
}

View File

@ -1,8 +1,8 @@
import { remote } from 'electron';
import { ipcRenderer } from 'electron';
import { IAnalyticsData } from '../app/analytics-handler';
import {
apiCmds,
apiName,
IBoundsChange,
ILogMsg,
INotificationData,
@ -20,12 +20,12 @@ import {
import { SSFApi } from './ssf-api';
const ssf = new SSFApi();
const notification = remote.require('../renderer/notification').notification;
let ssInstance: any;
try {
const SSAPIBridge = remote.require('swift-search').SSAPIBridge;
ssInstance = new SSAPIBridge();
// TODO: remove remote module
/*const SSAPIBridge = remote.require('swift-search').SSAPIBridge;
ssInstance = new SSAPIBridge();*/
} catch (e) {
ssInstance = null;
console.warn(
@ -47,7 +47,7 @@ export class AppBridge {
return event.source && event.source === window;
}
public origin: string;
public origin: string = '';
private readonly callbackHandlers = {
onMessage: (event) => this.handleMessage(event),
@ -80,14 +80,22 @@ export class AppBridge {
constructor() {
// starts with corporate pod and
// will be updated with the global config url
const currentWindow = remote.getCurrentWindow();
// @ts-ignore
this.origin = currentWindow.origin || '';
// this.origin = '*'; // DEMO-APP: Comment this line back in only to test demo-app - DO NOT COMMIT
if (ssInstance && typeof ssInstance.setBroadcastMessage === 'function') {
ssInstance.setBroadcastMessage(this.broadcastMessage);
}
window.addEventListener('message', this.callbackHandlers.onMessage);
ipcRenderer
.invoke(apiName.symphonyApi, {
cmd: apiCmds.getCurrentOriginUrl,
})
.then((origin) => {
this.origin = origin;
// this.origin = '*'; // DEMO-APP: Comment this line back in only to test demo-app - DO NOT COMMIT
if (
ssInstance &&
typeof ssInstance.setBroadcastMessage === 'function'
) {
ssInstance.setBroadcastMessage(this.broadcastMessage);
}
window.addEventListener('message', this.callbackHandlers.onMessage);
}) // tslint:disable-next-line:no-console
.catch((reason) => console.error(reason));
}
/**
@ -200,13 +208,13 @@ export class AppBridge {
);
break;
case apiCmds.notification:
notification.showNotification(
ssf.showNotification(
data as INotificationData,
this.callbackHandlers.onNotificationCallback,
);
break;
case apiCmds.closeNotification:
await notification.hideNotification(data as number);
await ssf.closeNotification(data as number);
break;
case apiCmds.showNotificationSettings:
ssf.showNotificationSettings(data);

View File

@ -1,5 +1,7 @@
import { ipcRenderer, remote } from 'electron';
import { ipcRenderer } from 'electron';
import * as React from 'react';
import { productName } from '../../../package.json';
import { apiCmds, apiName } from '../../common/api-interface';
import { i18n } from '../../common/i18n-preload';
interface IState {
userConfig: object;
@ -86,7 +88,7 @@ export default class AboutApp extends React.Component<{}, IState> {
client,
} = this.state;
const appName = remote.app.getName() || 'Symphony';
const appName = productName || 'Symphony';
const copyright = `\xA9 ${new Date().getFullYear()} ${appName}`;
const podVersion = `${clientVersion} (${buildNumber})`;
const sdaVersionBuild = `${sdaVersion} (${sdaBuildNumber})`;
@ -165,10 +167,11 @@ export default class AboutApp extends React.Component<{}, IState> {
const { clientVersion, ...rest } = this.state;
const data = { ...{ sbeVersion: clientVersion }, ...rest };
if (data) {
remote.clipboard.write(
{ text: JSON.stringify(data, null, 4) },
'clipboard',
);
ipcRenderer.send(apiName.symphonyApi, {
cmd: apiCmds.aboutAppClipBoardData,
clipboard: data,
clipboardType: 'clipboard',
});
}
}

View File

@ -1,4 +1,4 @@
import { ipcRenderer, remote } from 'electron';
import { ipcRenderer } from 'electron';
import * as React from 'react';
import { i18n } from '../../common/i18n-preload';
@ -47,7 +47,7 @@ export default class LoadingScreen extends React.Component<{}, IState> {
*/
public render(): JSX.Element {
const { error } = this.state;
const appName = remote.app.getName() || 'Symphony';
const appName = 'Symphony';
if (error) {
return this.renderErrorContent(error);

View File

@ -1,6 +1,7 @@
import classNames from 'classnames';
import { ipcRenderer, remote } from 'electron';
import { ipcRenderer } from 'electron';
import * as React from 'react';
import { productName } from '../../../package.json';
import { apiCmds, apiName } from '../../common/api-interface';
import { isMac } from '../../common/env';
@ -40,6 +41,7 @@ export default class ScreenSharingIndicator extends React.Component<
public render(): JSX.Element {
const { id } = this.state;
const namespace = 'ScreenSharingIndicator';
const appName = productName || 'Symphony';
return (
<div className={classNames('ScreenSharingIndicator', { mac: isMac })}>
@ -48,9 +50,9 @@ export default class ScreenSharingIndicator extends React.Component<
.t(
`You are sharing your screen on {appName}`,
namespace,
)({ appName: remote.app.getName() })
.replace(remote.app.getName(), '')}
<span className='text-label2'>&nbsp;{remote.app.getName()}</span>
)({ appName })
.replace(appName, '')}
<span className='text-label2'>&nbsp;{appName}</span>
</span>
<span className='buttons'>
<button

View File

@ -1,5 +1,5 @@
import { remote } from 'electron';
import Timer = NodeJS.Timer;
import { ipcRenderer } from 'electron';
import { i18n } from '../../common/i18n-preload';
@ -17,18 +17,8 @@ export default class SnackBar {
private snackBar: HTMLElement | null = null;
constructor() {
const browserWindow = remote.getCurrentWindow();
if (
browserWindow &&
typeof browserWindow.isDestroyed === 'function' &&
!browserWindow.isDestroyed()
) {
browserWindow.on('enter-full-screen', this.eventHandlers.onShowSnackBar);
browserWindow.on(
'leave-full-screen',
this.eventHandlers.onRemoveSnackBar,
);
}
ipcRenderer.on('enter-full-screen', this.eventHandlers.onShowSnackBar);
ipcRenderer.on('leave-full-screen', this.eventHandlers.onRemoveSnackBar);
}
/**

View File

@ -1,4 +1,4 @@
import { ipcRenderer, remote } from 'electron';
import { ipcRenderer } from 'electron';
import * as React from 'react';
import { apiCmds, apiName } from '../../common/api-interface';
@ -8,12 +8,10 @@ interface IState {
title: string;
isMaximized: boolean;
isFullScreen: boolean;
titleBarHeight: string;
}
const TITLE_BAR_NAMESPACE = 'TitleBar';
export default class WindowsTitleBar extends React.Component<{}, IState> {
private readonly window: Electron.BrowserWindow;
private readonly eventHandlers = {
onClose: () => this.close(),
onMaximize: () => this.maximize(),
@ -26,26 +24,24 @@ export default class WindowsTitleBar extends React.Component<{}, IState> {
constructor(props) {
super(props);
this.window = remote.getCurrentWindow();
this.state = {
title: document.title || 'Symphony',
isFullScreen: this.window.isFullScreen(),
isMaximized: this.window.isMaximized(),
titleBarHeight: '32px',
isFullScreen: false,
isMaximized: true,
};
// Adds borders to the window
this.addWindowBorders();
this.renderMaximizeButtons = this.renderMaximizeButtons.bind(this);
// Event to capture and update icons
this.window.on('maximize', () => this.updateState({ isMaximized: true }));
this.window.on('unmaximize', () =>
ipcRenderer.on('maximize', () => this.updateState({ isMaximized: true }));
ipcRenderer.on('unmaximize', () =>
this.updateState({ isMaximized: false }),
);
this.window.on('enter-full-screen', () =>
ipcRenderer.on('enter-full-screen', () =>
this.updateState({ isFullScreen: true }),
);
this.window.on('leave-full-screen', () =>
ipcRenderer.on('leave-full-screen', () =>
this.updateState({ isFullScreen: false }),
);
}
@ -69,10 +65,6 @@ export default class WindowsTitleBar extends React.Component<{}, IState> {
characterData: true,
});
}
setTimeout(() => {
this.updateTitleBar();
}, 10000);
}
/**
@ -90,7 +82,6 @@ export default class WindowsTitleBar extends React.Component<{}, IState> {
public render(): JSX.Element | null {
const { title, isFullScreen } = this.state;
const style = { display: isFullScreen ? 'none' : 'flex' };
this.updateTitleBar();
return (
<div
@ -214,57 +205,46 @@ export default class WindowsTitleBar extends React.Component<{}, IState> {
* Method that closes the browser window
*/
public close(): void {
if (this.isValidWindow()) {
this.window.close();
}
ipcRenderer.send(apiName.symphonyApi, {
cmd: apiCmds.closeMainWindow,
});
}
/**
* Method that minimizes the browser window
*/
public minimize(): void {
if (this.isValidWindow()) {
this.window.minimize();
}
ipcRenderer.send(apiName.symphonyApi, {
cmd: apiCmds.minimizeMainWindow,
});
}
/**
* Method that maximize the browser window
*/
public maximize(): void {
if (this.isValidWindow()) {
this.window.maximize();
this.setState({ isMaximized: true });
}
ipcRenderer.send(apiName.symphonyApi, {
cmd: apiCmds.maximizeMainWindow,
});
this.setState({ isMaximized: true });
}
/**
* Method that unmaximize the browser window
*/
public unmaximize(): void {
if (this.isValidWindow()) {
this.window.isFullScreen()
? this.window.setFullScreen(false)
: this.window.unmaximize();
}
ipcRenderer.send(apiName.symphonyApi, {
cmd: apiCmds.unmaximizeMainWindow,
});
}
/**
* Method that popup the application menu
*/
public showMenu(): void {
if (this.isValidWindow()) {
ipcRenderer.send(apiName.symphonyApi, {
cmd: apiCmds.popupMenu,
});
}
}
/**
* verifies if the this.window is valid and is not destroyed
*/
public isValidWindow(): boolean {
return this.window && !this.window.isDestroyed();
ipcRenderer.send(apiName.symphonyApi, {
cmd: apiCmds.popupMenu,
});
}
/**
@ -286,61 +266,6 @@ export default class WindowsTitleBar extends React.Component<{}, IState> {
document.body.classList.add('window-border');
}
/**
* Modifies the client's DOM content
*/
private updateTitleBar(): void {
const { isFullScreen, titleBarHeight } = this.state;
const contentWrapper = document.getElementById('content-wrapper');
const appView = document.getElementsByClassName('jss1')[0] as HTMLElement;
const root = document.getElementById('root');
const railContainer = document.getElementsByClassName(
'ReactRail-container-2',
)[0] as HTMLElement;
const railList = document.getElementsByClassName(
'railList',
)[0] as HTMLElement;
if (railContainer) {
railContainer.style.height = isFullScreen
? '100vh'
: `calc(100vh - ${titleBarHeight})`;
} else if (railList) {
railList.style.height = isFullScreen
? '100vh'
: `calc(100vh - ${titleBarHeight})`;
}
if (!contentWrapper && !root) {
document.body.style.marginTop = isFullScreen ? '0px' : titleBarHeight;
return;
}
if (root) {
const rootChild = root.firstElementChild as HTMLElement;
if (rootChild && rootChild.style && rootChild.style.height === '100vh') {
rootChild.style.height = isFullScreen
? '100vh'
: `calc(100vh - ${titleBarHeight})`;
}
root.style.height = isFullScreen
? '100vh'
: `calc(100vh - ${titleBarHeight})`;
root.style.marginTop = isFullScreen ? '0px' : titleBarHeight;
} else if (contentWrapper) {
contentWrapper.style.marginTop = isFullScreen ? '0px' : titleBarHeight;
}
if (appView) {
appView.style.height = isFullScreen
? '100vh'
: `calc(100vh - ${titleBarHeight})`;
}
if (isFullScreen) {
document.body.style.removeProperty('margin-top');
}
document.body.classList.add('sda-title-bar');
}
/**
* Returns the title bar logo
*/

View File

@ -2,7 +2,6 @@ import {
desktopCapturer,
DesktopCapturerSource,
ipcRenderer,
remote,
SourcesOptions,
} from 'electron';
@ -12,7 +11,6 @@ import {
NOTIFICATION_WINDOW_TITLE,
} from '../common/api-interface';
import { isWindowsOS } from '../common/env';
import { i18n } from '../common/i18n-preload';
const includes = [''].includes;
@ -94,7 +92,9 @@ export const getSource = async (
* Setting captureWindow to false returns only screen sources
* @type {boolean}
*/
captureWindow = remote.systemPreferences.isAeroGlassEnabled();
captureWindow = await ipcRenderer.invoke(apiName.symphonyApi, {
cmd: apiCmds.isAeroGlassEnabled,
});
}
if (captureWindow) {
@ -106,23 +106,15 @@ export const getSource = async (
// displays a dialog if media permissions are disable
if (!isScreenShareEnabled) {
const focusedWindow = remote.BrowserWindow.getFocusedWindow();
if (focusedWindow && !focusedWindow.isDestroyed()) {
remote.dialog.showMessageBox(focusedWindow, {
message: `${i18n.t(
'Your administrator has disabled sharing your screen. Please contact your admin for help',
'Permissions',
)()}`,
title: `${i18n.t('Permission Denied')()}!`,
type: 'error',
});
callback({
name: 'Permission Denied',
message: 'Permission Denied',
requestId,
});
return;
}
await ipcRenderer.invoke(apiName.symphonyApi, {
cmd: apiCmds.showScreenSharePermissionDialog,
});
callback({
name: 'Permission Denied',
message: 'Permission Denied',
requestId,
});
return;
}
id = getNextId();

View File

@ -1,10 +1,11 @@
import { remote } from 'electron';
import { ipcRenderer } from 'electron';
import {
apiCmds,
apiName,
INotificationData,
NotificationActions,
} from '../common/api-interface';
const notification = remote.require('../renderer/notification').notification;
let latestID = 0;
@ -16,20 +17,47 @@ export default class SSFNotificationHandler {
public _data: INotificationData;
private readonly id: number;
private readonly eventHandlers = {
onClick: (event: NotificationActions, _data: INotificationData) =>
this.notificationClicked(event),
};
private notificationClickCallback: (({ target }) => {}) | undefined;
private notificationCloseCallback: (({ target }) => {}) | undefined;
constructor(title, options) {
this.id = latestID;
latestID++;
notification.showNotification(
{ ...options, title, id: this.id },
this.eventHandlers.onClick,
);
const notificationOpts = { ...options, title, id: this.id };
// ipc does not support sending Functions, Promises, Symbols, WeakMaps,
// or WeakSets will throw an exception
if (notificationOpts.callback) {
delete notificationOpts.callback;
}
ipcRenderer.send(apiName.symphonyApi, {
cmd: apiCmds.showNotification,
notificationOpts,
});
ipcRenderer.once('notification-actions', (_event, args) => {
if (args.id === this.id) {
switch (args.event) {
case NotificationActions.notificationClicked:
if (
this.notificationClickCallback &&
typeof this.notificationClickCallback === 'function'
) {
this.notificationClickCallback({ target: this });
}
break;
case NotificationActions.notificationClosed:
if (
this.notificationCloseCallback &&
typeof this.notificationCloseCallback === 'function'
) {
this.notificationCloseCallback({ target: this });
}
break;
default:
break;
}
}
});
this._data = options.data;
}
@ -37,7 +65,10 @@ export default class SSFNotificationHandler {
* Closes notification
*/
public close(): void {
notification.hideNotification(this.id);
ipcRenderer.send(apiName.symphonyApi, {
cmd: apiCmds.closeNotification,
notificationId: this.id,
});
}
/**
@ -73,29 +104,4 @@ export default class SSFNotificationHandler {
}
}
}
/**
* Handles the callback based on the event name
*
* @param event {NotificationActions}
*/
private notificationClicked(event: NotificationActions): void {
switch (event) {
case NotificationActions.notificationClicked:
if (
this.notificationClickCallback &&
typeof this.notificationClickCallback === 'function'
) {
this.notificationClickCallback({ target: this });
}
break;
case NotificationActions.notificationClosed:
if (
this.notificationCloseCallback &&
typeof this.notificationCloseCallback === 'function'
) {
this.notificationCloseCallback({ target: this });
}
}
}
}

View File

@ -12,6 +12,7 @@ import ScreenSharingFrame from './components/screen-sharing-frame';
import ScreenSharingIndicator from './components/screen-sharing-indicator';
import SnippingTool from './components/snipping-tool';
import Welcome from './components/welcome';
import WindowsTitleBar from './components/windows-title-bar';
const enum components {
aboutApp = 'about-app',
@ -23,6 +24,7 @@ const enum components {
notificationSettings = 'notification-settings',
welcome = 'welcome',
snippingTool = 'snipping-tool',
titleBar = 'title-bar',
}
const loadStyle = (style) => {
@ -89,6 +91,10 @@ const load = () => {
loadStyle(components.welcome);
component = Welcome;
break;
case components.titleBar:
loadStyle(components.titleBar);
component = WindowsTitleBar;
break;
}
const element = React.createElement(component);
ReactDOM.render(element, document.getElementById('Root'));

View File

@ -10,7 +10,6 @@ import MessageBanner from './components/message-banner';
import NetworkError from './components/network-error';
import SnackBar from './components/snack-bar';
import Welcome from './components/welcome';
import WindowsTitleBar from './components/windows-title-bar';
import { SSFApi } from './ssf-api';
interface ISSFWindow extends Window {
@ -126,45 +125,32 @@ const monitorMemory = (time) => {
};
// When the window is completely loaded
ipcRenderer.on(
'page-load',
(_event, { locale, resources, enableCustomTitleBar }) => {
i18n.setResource(locale, resources);
ipcRenderer.on('page-load', (_event, { locale, resources }) => {
i18n.setResource(locale, resources);
if (enableCustomTitleBar) {
// injects custom title bar
const element = React.createElement(WindowsTitleBar);
const div = document.createElement('div');
document.body.appendChild(div);
ReactDOM.render(element, div);
document.body.classList.add('sda-title-bar');
}
webFrame.setSpellCheckProvider('en-US', {
spellCheck(words, callback) {
const misspelled = words.filter((word) => {
return ipcRenderer.sendSync(apiName.symphonyApi, {
cmd: apiCmds.isMisspelled,
word,
});
webFrame.setSpellCheckProvider('en-US', {
spellCheck(words, callback) {
const misspelled = words.filter((word) => {
return ipcRenderer.sendSync(apiName.symphonyApi, {
cmd: apiCmds.isMisspelled,
word,
});
callback(misspelled);
},
});
});
callback(misspelled);
},
});
// injects snack bar
snackBar.initSnackBar();
// injects snack bar
snackBar.initSnackBar();
// injects download manager contents
const downloadManager = new DownloadManager();
downloadManager.initDownloadManager();
// injects download manager contents
const downloadManager = new DownloadManager();
downloadManager.initDownloadManager();
// initialize red banner
banner.initBanner();
banner.showBanner(false, 'error');
},
);
// initialize red banner
banner.initBanner();
banner.showBanner(false, 'error');
});
ipcRenderer.on('page-load-welcome', (_event, data) => {
const { locale, resource } = data;

View File

@ -1,7 +1,11 @@
import { ipcRenderer, remote, webFrame } from 'electron';
import { buildNumber, searchAPIVersion } from '../../package.json';
import { ipcRenderer, webFrame } from 'electron';
import {
buildNumber,
name,
searchAPIVersion,
version,
} from '../../package.json';
import { IDownloadItem } from '../app/download-handler';
import { ICustomBrowserWindow } from '../app/window-handler';
import {
apiCmds,
apiName,
@ -23,12 +27,11 @@ import {
import { i18n, LocaleType } from '../common/i18n-preload';
import { throttle } from '../common/utils';
import { getSource } from './desktop-capturer';
import SSFNotificationHandler from './notification-ssf-hendler';
import SSFNotificationHandler from './notification-ssf-handler';
import { ScreenSnippetBcHandler } from './screen-snippet-bc-handler';
const SUPPORTED_SETTINGS = ['flashing-notifications'];
const os = remote.require('os');
const MAIN_WINDOW_NAME = 'main';
let isAltKey: boolean = false;
let isMenuOpen: boolean = false;
@ -161,7 +164,7 @@ const throttledSetZoomLevel = throttle((zoomLevel) => {
let cryptoLib: ICryptoLib | null;
try {
cryptoLib = remote.require('../app/crypto-handler.js').cryptoLibrary;
// cryptoLib = remote.require('../app/crypto-handler.js').cryptoLibrary;
} catch (e) {
cryptoLib = null;
// tslint:disable-next-line
@ -172,7 +175,7 @@ try {
let swiftSearch: any;
try {
swiftSearch = remote.require('swift-search').Search;
// swiftSearch = remote.require('swift-search').Search;
} catch (e) {
swiftSearch = null;
// tslint:disable-next-line
@ -183,7 +186,7 @@ try {
let swiftSearchUtils: any;
try {
swiftSearchUtils = remote.require('swift-search').SearchUtils;
// swiftSearchUtils = remote.require('swift-search').SearchUtils;
} catch (e) {
swiftSearchUtils = null;
// tslint:disable-next-line
@ -244,9 +247,9 @@ export class SSFApi {
* Method that returns various version info
*/
public getVersionInfo(): Promise<IVersionInfo> {
const appName = remote.app.getName();
const appVer = remote.app.getVersion();
const cpuArch = os.arch() || '';
const appName = name;
const appVer = version;
const cpuArch = process.arch || '';
return Promise.resolve({
containerIdentifier: appName,
@ -481,11 +484,9 @@ export class SSFApi {
* Opens a modal window to configure notification preference.
*/
public showNotificationSettings(data: string): void {
const windowName = (remote.getCurrentWindow() as ICustomBrowserWindow)
.winName;
local.ipcRenderer.send(apiName.symphonyApi, {
cmd: apiCmds.showNotificationSettings,
windowName,
windowName: MAIN_WINDOW_NAME,
theme: data,
});
}
@ -631,10 +632,13 @@ export class SSFApi {
* Check media permission
*/
public async checkMediaPermission(): Promise<IMediaPermission> {
const mediaStatus = (await ipcRenderer.invoke(apiName.symphonyApi, {
cmd: apiCmds.getMediaAccessStatus,
})) as IMediaPermission;
return Promise.resolve({
camera: remote.systemPreferences.getMediaAccessStatus('camera'),
microphone: remote.systemPreferences.getMediaAccessStatus('microphone'),
screen: remote.systemPreferences.getMediaAccessStatus('screen'),
camera: mediaStatus.camera,
microphone: mediaStatus.microphone,
screen: mediaStatus.screen,
});
}