Skip to content

Commit 7086849

Browse files
authored
Auto-updates for macOS Binaries and change default browser to Edge on Windows env with proxy (#12)
* Auto-updates for macOS Binaries * Change default browser to Edge on Windows env with proxy
1 parent fd679e7 commit 7086849

File tree

9 files changed

+149
-36
lines changed

9 files changed

+149
-36
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "purplehatsdesktop",
33
"productName": "Purple HATS",
4-
"version": "0.9.0",
4+
"version": "0.9.1",
55
"private": true,
66
"dependencies": {
77
"axios": "^1.3.3",

public/electron/constants.js

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const { globSync } = require("glob");
55
const { get } = require("http");
66
const { silentLogger } = require("./logs.js");
77
const { execSync } = require("child_process");
8+
const { app } = require("electron");
89

910
const appPath =
1011
os.platform() === "win32"
@@ -20,11 +21,30 @@ const releaseUrl =
2021
"https://api.github.com/repos/GovTechSG/purple-hats/releases/latest";
2122

2223
const frontendReleaseUrl =
23-
"https://github.com/GovTechSG/purple-hats-desktop/releases/latest/download/purple-hats-desktop-windows.zip";
24+
os.platform() === "win32"
25+
? "https://github.com/GovTechSG/purple-hats-desktop/releases/latest/download/purple-hats-desktop-windows.zip"
26+
: "https://github.com/GovTechSG/purple-hats-desktop/releases/latest/download/purple-hats-desktop-macos.zip";
2427

2528
const backendPath = path.join(appPath, "Purple HATS Backend");
2629
const frontendPath = path.join(appPath, "Purple HATS Frontend");
2730

31+
const getMacOSExecutablePath = () => {
32+
let executablePath = require("path").dirname(
33+
require("electron").app.getPath("exe")
34+
);
35+
36+
// Retrieve the path to the executable up to the .app folder
37+
if (executablePath !== null) {
38+
executablePath = executablePath.substring(
39+
0,
40+
executablePath.lastIndexOf(".app") + 4
41+
);
42+
}
43+
44+
return executablePath;
45+
};
46+
const macOSExecutablePath = getMacOSExecutablePath();
47+
2848
const resultsPath =
2949
os.platform() === "win32"
3050
? path.join(process.env.APPDATA, "Purple HATS")
@@ -43,12 +63,12 @@ const getEngineVersion = () =>
4363

4464
const getFrontendVersion = () => {
4565
// Directory is only valid for and used by Windows
46-
if (os.platform() !== "win32") {
47-
return;
66+
if (os.platform() === "win32") {
67+
return require(path.join(frontendPath, "resources", "app", "package.json"))
68+
.version;
69+
} else {
70+
return appVersion;
4871
}
49-
50-
return require(path.join(frontendPath, "resources", "app", "package.json"))
51-
.version;
5272
};
5373

5474
const appVersion = require(path.join(
@@ -607,4 +627,5 @@ module.exports = {
607627
artifactInstallerPath,
608628
frontendReleaseUrl,
609629
installerExePath,
630+
macOSExecutablePath,
610631
};

public/electron/main.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,25 @@ app.on("ready", async () => {
101101
});
102102
});
103103

104+
updateEvent.on("frontendDownloadCompleteMacOS", (userResponse) => {
105+
launchWindow.webContents.send(
106+
"launchStatus",
107+
"frontendDownloadCompleteMacOS"
108+
);
109+
ipcMain.once("restartAppAfterMacOSFrontendUpdate", (_event, response) => {
110+
userResponse(response);
111+
});
112+
});
113+
104114
updateEvent.on("installerLaunched", () => {
105115
app.exit();
106116
});
107117

118+
updateEvent.on("restartTriggered", () => {
119+
app.relaunch();
120+
app.exit();
121+
});
122+
108123
updateEvent.on("frontendDownloadFailed", () => {
109124
launchWindow.webContents.send("launchStatus", "frontendDownloadFailed");
110125
});

public/electron/preload.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ contextBridge.exposeInMainWorld("services", {
5656
launchInstaller: (response) => {
5757
ipcRenderer.send("launchInstaller", response);
5858
},
59+
restartAppAfterMacOSFrontendUpdate: (response) => {
60+
ipcRenderer.send("restartAppAfterMacOSFrontendUpdate", response);
61+
},
5962
setUserData: (data) => {
6063
ipcRenderer.send("userDataReceived", data);
6164
},

public/electron/updateManager.js

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,19 @@ const { exec, spawn } = require("child_process");
66
const axios = require("axios");
77
const {
88
releaseUrl,
9-
enginePath,
109
getEngineVersion,
1110
getFrontendVersion,
1211
appPath,
1312
backendPath,
14-
frontendPath,
1513
updateBackupsFolder,
1614
scanResultsPath,
1715
phZipPath,
18-
createPlaywrightContext,
19-
deleteClonedProfiles,
20-
artifactInstallerPath,
2116
resultsPath,
2217
frontendReleaseUrl,
2318
installerExePath,
19+
macOSExecutablePath,
2420
} = require("./constants");
2521
const { silentLogger } = require("./logs");
26-
const { readUserDataFromFile } = require("./userDataManager");
2722

2823
let currentChildProcess;
2924

@@ -148,7 +143,6 @@ const isLatestFrontendVersion = async () => {
148143

149144
const latestVersion = data.tag_name;
150145
const frontendVersion = getFrontendVersion();
151-
152146
console.log("Frontend version installed: ", frontendVersion);
153147
console.log("Latest frontend version found: ", latestVersion);
154148

@@ -161,6 +155,10 @@ const isLatestFrontendVersion = async () => {
161155
}
162156
};
163157

158+
/**
159+
* Spawns a PowerShell process to download and unzip the frontend
160+
* @returns {Promise<boolean>} true if the frontend was downloaded and unzipped successfully, false otherwise
161+
*/
164162
const downloadAndUnzipFrontendWindows = async () => {
165163
const shellScript = `
166164
$webClient = New-Object System.Net.WebClient
@@ -208,6 +206,32 @@ const downloadAndUnzipFrontendWindows = async () => {
208206
});
209207
};
210208

209+
/**
210+
* Spawns a Shell Command process to download and unzip the frontend
211+
*/
212+
const downloadAndUnzipFrontendMac = async () => {
213+
const command = `
214+
curl -L '${frontendReleaseUrl}' -o '${resultsPath}/purple-hats-desktop-mac.zip' &&
215+
mv '${macOSExecutablePath}' '${path.join(
216+
macOSExecutablePath,
217+
".."
218+
)}/Purple Hats Old.app' &&
219+
ditto -xk '${resultsPath}/purple-hats-desktop-mac.zip' '${path.join(
220+
macOSExecutablePath,
221+
".."
222+
)}' &&
223+
rm '${resultsPath}/purple-hats-desktop-mac.zip' &&
224+
rm -rf '${path.join(macOSExecutablePath, "..")}/Purple Hats Old.app' &&
225+
find '${macOSExecutablePath}' -exec xattr -d com.apple.quarantine {} \\`;
226+
227+
await execCommand(command);
228+
};
229+
230+
/**
231+
* Spawn a PowerShell process to launch the InnoSetup installer executable, which contains the frontend and backend
232+
* upon confirmation from the user, the installer will be launched & Electron will exit
233+
* @returns {Promise<boolean>} true if the installer executable was launched successfully, false otherwise
234+
*/
211235
const spawnScriptToLaunchInstaller = () => {
212236
const shellScript = `Start-Process -FilePath "${installerExePath}"`;
213237

@@ -345,6 +369,29 @@ const run = async (updaterEventEmitter) => {
345369
}
346370
}
347371
} else {
372+
if (!(await isLatestFrontendVersion())) {
373+
updaterEventEmitter.emit("checking");
374+
const userResponse = new Promise((resolve) => {
375+
updaterEventEmitter.emit("promptFrontendUpdate", resolve);
376+
});
377+
378+
const proceedUpdate = await userResponse;
379+
380+
if (proceedUpdate) {
381+
updaterEventEmitter.emit("updatingFrontend");
382+
383+
// Relaunch the app with new binaries if the frontend update is successful
384+
// If unsuccessful, the app will be launched with existing frontend
385+
try {
386+
await downloadAndUnzipFrontendMac();
387+
currentChildProcess = null;
388+
updaterEventEmitter.emit("restartTriggered");
389+
} catch (e) {
390+
silentLogger.error(e.toString());
391+
}
392+
}
393+
}
394+
348395
if (isInterruptedUpdate) {
349396
updaterEventEmitter.emit("updatingBackend");
350397
if (!backendExists) {

public/electron/userDataManager.js

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
const fs = require("fs");
2-
const {
3-
userDataFilePath
4-
} = require("./constants");
2+
const { userDataFilePath, proxy } = require("./constants");
53
const { ipcMain } = require("electron");
64

75
const readUserDataFromFile = () => {
@@ -15,18 +13,18 @@ const writeUserDataToFile = (userData) => {
1513
}
1614

1715
const init = async () => {
18-
const userDataExists = fs.existsSync(userDataFilePath);
19-
if (!userDataExists) {
20-
const defaultSettings = {
21-
name: "",
22-
email: "",
23-
autoSubmit: true,
24-
event: false,
25-
browser: "chrome",
26-
autoUpdate: true
27-
};
28-
fs.writeFileSync(userDataFilePath, JSON.stringify(defaultSettings));
29-
}
16+
const userDataExists = fs.existsSync(userDataFilePath);
17+
if (!userDataExists) {
18+
const defaultSettings = {
19+
name: "",
20+
email: "",
21+
autoSubmit: true,
22+
event: false,
23+
browser: proxy ? "edge" : "chrome",
24+
autoUpdate: true,
25+
};
26+
fs.writeFileSync(userDataFilePath, JSON.stringify(defaultSettings));
27+
}
3028

3129
ipcMain.handle("getUserData", (_event) => {
3230
const data = readUserDataFromFile();

src/LaunchWindow/index.jsx

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ const LaunchWindow = () => {
4747
sub: "This may take a few minutes. Please do not close the application.",
4848
},
4949
updatingFrontend: {
50-
main: "Downloading new installer",
51-
sub: "This may take a few minutes. The application will close after and you will be guided through the reinstallation.",
50+
main: "Downloading",
51+
sub: "This may take a few minutes. Please do not close the application.",
5252
},
5353
offline: {
5454
main: "No internet connection",
@@ -70,6 +70,11 @@ const LaunchWindow = () => {
7070
// setPromptUpdate(false);
7171
};
7272

73+
const handlePromptRestartAppResponse = (response) => () => {
74+
window.services.restartAppAfterMacOSFrontendUpdate(response);
75+
// setPromptUpdate(false);
76+
};
77+
7378
if (promptUpdate) {
7479
return (
7580
<div id="launch-window">
@@ -95,7 +100,7 @@ const LaunchWindow = () => {
95100
return (
96101
<div id="launch-window">
97102
<div>
98-
<h1>Installer has been downloaded</h1>
103+
<h1>New installer has been downloaded</h1>
99104
<p>Would you like to run the installer now?</p>
100105
<Button
101106
type="secondary"
@@ -115,6 +120,30 @@ const LaunchWindow = () => {
115120
);
116121
}
117122

123+
if (launchStatus === "frontendDownloadCompleteMacOS") {
124+
return (
125+
<div id="launch-window">
126+
<div>
127+
<h1>New App has been downloaded</h1>
128+
<p>Would you like to restart the application?</p>
129+
<Button
130+
type="secondary"
131+
onClick={handlePromptRestartAppResponse(false)}
132+
>
133+
Later
134+
</Button>
135+
<Button
136+
id="proceed-button"
137+
type="primary"
138+
onClick={handlePromptRestartAppResponse(true)}
139+
>
140+
Run
141+
</Button>
142+
</div>
143+
</div>
144+
);
145+
}
146+
118147
const { main: displayedMessage, sub: displayedSub } = messages[launchStatus];
119148
return (
120149
<div id="launch-window">

src/MainWindow/HomePage/index.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ const HomePage = ({ isProxy, appVersion, setCompletedScanId }) => {
5454
};
5555

5656
const startScan = async (scanDetails) => {
57-
scanDetails.browser = browser;
57+
scanDetails.browser = isProxy ? "edge" : browser;
5858

5959
if (scanDetails.scanUrl.length === 0) {
6060
setPrevUrlErrorMessage("URL cannot be empty.");
@@ -150,7 +150,7 @@ const HomePage = ({ isProxy, appVersion, setCompletedScanId }) => {
150150
/>
151151
<h1 id="app-title">Accessibility Site Scanner</h1>
152152
<InitScanForm
153-
isProxy = {isProxy}
153+
isProxy={isProxy}
154154
startScan={startScan}
155155
prevUrlErrorMessage={prevUrlErrorMessage}
156156
/>

0 commit comments

Comments
 (0)