diff --git a/CHANGELOG.md b/CHANGELOG.md
index e099bf5b..2293e5ea 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,21 @@
All notable changes to this project will be documented in this file.
+## [5.0.0](https://github.com/Netflix-Skunkworks/stethoscope-app/tree/v5.0.0)
+
+Fix auto update issue, caused by undocumented changes in Electron
+
+### Changed
+- stethoscope://update deep link now forces update without prompting
+
+## [4.0.2](https://github.com/Netflix-Skunkworks/stethoscope-app/tree/v4.0.2)
+
+Bug fixes
+
+### Changed
+- Config files are loaded directly by React app now, removing XHR requests.
+- Ensure window is not destroyed before autoscanning
+
## [4.0.1](https://github.com/Netflix-Skunkworks/stethoscope-app/tree/v4.0.1)
### Added
diff --git a/package.json b/package.json
index 7f2d4186..35bb5a05 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "Stethoscope",
- "version": "4.0.1",
+ "version": "5.0.0",
"private": true,
"homepage": "./",
"author": "Netflix",
@@ -67,6 +67,7 @@
"extend": "^3.0.2",
"fast-glob": "^2.2.6",
"generic-pool": "^3.4.2",
+ "glob": "^7.1.6",
"graphql": "^14.3.1",
"graphql-server-express": "^1.2.0",
"graphql-tools": "^2.12.0",
@@ -150,11 +151,15 @@
"gatekeeperAssess": false,
"hardenedRuntime": true,
"entitlements": "entitlements.mac.plist",
- "entitlementsInherit": "entitlements.mac.plist"
+ "entitlementsInherit": "entitlements.mac.plist",
+ "extraResources": [
+ "src/practices"
+ ]
},
"win": {
"target": "nsis",
"extraResources": [
+ "src/practices",
"bitlocker-status/bitlocker-status.exe",
"bitlocker-status/bitlocker-status.exe.confg",
"bitlocker-status/bitlocker-status.pdb",
@@ -173,7 +178,10 @@
},
"linux": {
"target": "AppImage",
- "category": "System"
+ "category": "System",
+ "extraResources": [
+ "src/practices"
+ ]
},
"protocols": [
{
diff --git a/src/App.js b/src/App.js
index 807088d3..fadec790 100644
--- a/src/App.js
+++ b/src/App.js
@@ -1,4 +1,4 @@
-/* global fetch, Notification */
+/* global Notification */
import React, { Component } from 'react'
import Stethoscope from './lib/Stethoscope'
import Device from './Device'
@@ -13,11 +13,16 @@ import { HOST } from './constants'
import appConfig from './config.json'
import pkg from '../package.json'
import ErrorMessage from './ErrorMessage'
+import yaml from 'js-yaml'
import './App.css'
const socket = openSocket(HOST)
+
// CRA doesn't like importing native node modules
// have to use window.require AFAICT
const os = window.require('os')
+const glob = window.require('glob')
+const { readFileSync } = window.require('fs')
+const path = window.require('path')
const { shell, remote, ipcRenderer } = window.require('electron')
const settings = window.require('electron-settings')
const log = remote.getGlobal('log')
@@ -53,7 +58,11 @@ class App extends Component {
this.setState({ recentHang: settings.get('recentHang', 0) > 1 })
ipcRenderer.send('scan:init')
// perform the initial policy load & scan
- await this.loadPractices()
+ try {
+ await this.loadPractices()
+ } catch (e) {
+ console.error('Unable to load practices')
+ }
// flag ensures the download:start event isn't sent multiple times
this.downloadStartSent = false
// handle context menu
@@ -63,7 +72,8 @@ class App extends Component {
// handles any errors that occur when updating (restores window size, etc.)
ipcRenderer.on('download:error', this.onDownloadError)
// trigger scan from main process
- ipcRenderer.on('autoscan:start', ({ notificationOnViolation = false }) => {
+ ipcRenderer.on('autoscan:start', (args = {}) => {
+ // const { notificationOnViolation = false } = args
if (!this.state.scanIsRunning) {
ipcRenderer.send('scan:init')
if (Object.keys(this.state.policy).length) {
@@ -196,30 +206,33 @@ class App extends Component {
* using them
*/
loadPractices = () => {
- this.setState({ loading: true }, () => {
- const files = ['config', 'policy', 'instructions']
- const promises = files.map(item =>
- fetch(`${HOST}/${item}`)
- .then(async res => {
- if (!res.ok) {
- log.error(`Unable to locate ${item}`)
- const response = await res.json()
- throw new Error(response.error || `Unable to locate ${item}`)
+ return new Promise((resolve, reject) =>
+ this.setState({ loading: true }, () => {
+ const process = remote.process
+ const dev = process.env.STETHOSCOPE_ENV === 'development'
+ const basePath = `${dev ? '.' : process.resourcesPath}/src/practices`
+
+ glob(`${basePath}/*.yaml`, (err, files) => {
+ if (err || !files.length) {
+ reject(err)
+ }
+ const configs = {}
+ files.forEach(filePath => {
+ const parts = path.parse(filePath)
+ const handle = readFileSync(filePath, 'utf8')
+ configs[parts.name.split('.').shift()] = yaml.safeLoad(handle)
+ })
+
+ this.setState({ ...configs, loading: false }, () => {
+ if (!this.state.scanIsRunning) {
+ this.handleScan()
}
- return res
})
- .then(res => res.json())
- .catch(this.handleErrorYAML)
- )
- Promise.all(promises).then(([config, policy, instructions]) => {
- this.setState({ config, policy, instructions }, () => {
- if (!this.state.scanIsRunning) {
- this.handleScan()
- }
+ resolve()
})
- }).catch(this.handleErrorGraphQL)
- })
+ })
+ )
}
/**
@@ -322,33 +335,37 @@ class App extends Component {
// assume no errors and loaded state
if (!content) {
const args = [policy, result, device, instructions.practices, platform]
- const secInfo = Stethoscope.partitionSecurityInfo(...args)
- const decoratedDevice = Object.assign({}, device, secInfo, { lastScanTime })
- const lastScanFriendly = moment(lastScanTime).fromNow()
-
- content = (
-
-
-
-
- )
+ try {
+ const secInfo = Stethoscope.partitionSecurityInfo(...args)
+ const decoratedDevice = Object.assign({}, device, secInfo, { lastScanTime })
+ const lastScanFriendly = moment(lastScanTime).fromNow()
+
+ content = (
+
+
+
+
+ )
+ } catch (e) {
+ throw new Error(`Unable to partition data: ${e.message}\n${args.join(', ')}`)
+ }
}
return (
diff --git a/src/Footer.js b/src/Footer.js
index 29fb63a5..23fd78bc 100644
--- a/src/Footer.js
+++ b/src/Footer.js
@@ -17,7 +17,7 @@ export default function Footer (props) {
onClick={onRescan}
>
- {instructions.strings.rescanButton}
+ {instructions && instructions.strings && instructions.strings.rescanButton}
{webScopeLink && (