From 353e69ccc9d2c9f0e14f976cda586d42f15989d8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E2=80=9Emarianfoo=E2=80=9C?=
<13335743+marianfoo@users.noreply.github.com>
Date: Mon, 4 Nov 2024 13:31:52 +0100
Subject: [PATCH] fix: add https support
---
.../lib/livereload.js | 97 +++++++++++++------
1 file changed, 69 insertions(+), 28 deletions(-)
diff --git a/packages/ui5-middleware-livereload/lib/livereload.js b/packages/ui5-middleware-livereload/lib/livereload.js
index d0585983..c4bfd2b4 100644
--- a/packages/ui5-middleware-livereload/lib/livereload.js
+++ b/packages/ui5-middleware-livereload/lib/livereload.js
@@ -1,10 +1,12 @@
/* eslint-disable no-unused-vars, no-undef */
const path = require("path");
const fs = require("fs");
+const os = require("os");
const portfinder = require("portfinder");
const chokidar = require("chokidar");
// eslint-disable-next-line no-redeclare
const WebSocket = require("ws");
+const yargs = require("yargs");
/**
* @typedef {object} [configuration] configuration
@@ -13,6 +15,7 @@ const WebSocket = require("ws");
* @property {string|yo} [watchPath] path inside `$yourapp` the reload server monitors for changes
* @property {string} [exclusions] one or many `regex`. By default, this includes `.git/`, `.svn/`, and `.hg/`
* @property {boolean|yo} [debug] see output
+ * @property {boolean} [usePolling] - enable polling for file changes
*/
/**
@@ -21,7 +24,7 @@ const WebSocket = require("ws");
*
* @param {object} options the entered config option
* @param {number} defaultPort the port which is defaulted
- * @returns {number} a port which is free
+ * @returns {Promise} - a port which is free
*/
const getPortForLivereload = async (options, defaultPort) => {
if (options.configuration && options.configuration.port) {
@@ -41,6 +44,7 @@ const getPortForLivereload = async (options, defaultPort) => {
* ATTENTION: this is a hack to be compatible with UI5 tooling 2.x and 3.x
*
* @param {module:@ui5/fs.AbstractReader} collection Reader or Collection to read resources of the root project and its dependencies
+ * @param {boolean} skipFwkDeps whether to skip framework dependencies
* @returns {string[]} source paths
*/
const determineSourcePaths = (collection, skipFwkDeps) => {
@@ -124,24 +128,35 @@ module.exports = async ({ log, resources, options, middlewareUtil }) => {
} else if (exclusions) {
exclusions = [new RegExp(exclusions)];
}
- let extraExts = options?.configuration?.extraExts || "js,html,css,jsx,ts,tsx,xml,json,properties";
- let debug = options?.configuration?.debug;
- let usePolling = options?.configuration?.usePolling;
-
- // Ensure watchPath is always an array
- const watchPaths = Array.isArray(watchPath) ? watchPath : [watchPath];
+ const extraExts = options?.configuration?.extraExts || "js,html,css,jsx,ts,tsx,xml,json,properties";
+ const debug = options?.configuration?.debug;
+ const usePolling = options?.configuration?.usePolling;
+
+ // SSL Configuration via command-line arguments
+ const cli = yargs(process.argv.slice(2)).argv;
+
+ let serverOptions = {};
+ let useHttps = false;
+ if (cli.h2) {
+ useHttps = true;
+ const sslKeyPath = cli.key ? cli.key : path.join(os.homedir(), ".ui5", "server", "server.key");
+ const sslCertPath = cli.cert ? cli.cert : path.join(os.homedir(), ".ui5", "server", "server.crt");
+ if (fs.existsSync(sslKeyPath) && fs.existsSync(sslCertPath)) {
+ serverOptions = {
+ key: fs.readFileSync(sslKeyPath),
+ cert: fs.readFileSync(sslCertPath),
+ };
+ debug && log.info(`Livereload using SSL key ${sslKeyPath} and certificate ${sslCertPath}`);
+ } else {
+ log.warn("SSL key or certificate not found, falling back to HTTP.");
+ }
+ }
- // Create complete paths with extensions
- const pathsToWatch = watchPaths.flatMap((basePath) => extraExts.split(",").map((ext) => path.join(basePath, `**/*.${ext.trim()}`)));
-
- // Set up WebSocket server
- const wss = new WebSocket.Server({ port });
- wss.on("connection", (ws) => {
- debug && log.info("WebSocket client connected");
- });
+ // Build array of file extensions to watch
+ const extsToWatch = extraExts.split(",");
// Set up chokidar watcher
- const watcher = chokidar.watch(pathsToWatch, {
+ const watcher = chokidar.watch(watchPath, {
ignored: exclusions,
ignoreInitial: true,
usePolling: usePolling,
@@ -150,14 +165,37 @@ module.exports = async ({ log, resources, options, middlewareUtil }) => {
depth: Infinity,
});
+ // Set up WebSocket server
+ let server;
+ if (useHttps && Object.keys(serverOptions).length > 0) {
+ // HTTPS Server
+ server = require("https").createServer(serverOptions);
+ } else {
+ // HTTP Server
+ server = require("http").createServer();
+ }
+ server.listen(port, () => {
+ debug && log.info(`Livereload server started on port ${port}`);
+ });
+
+ const wss = new WebSocket.Server({ server });
+
+ wss.on("connection", (ws) => {
+ debug && log.info("WebSocket client connected");
+ });
+
watcher.on("all", (event, filePath) => {
- debug && log.info(`File ${event}: ${filePath}`);
- // Notify all connected clients to reload
- wss.clients.forEach((client) => {
- if (client.readyState === WebSocket.OPEN) {
- client.send("reload");
- }
- });
+ // Check if the changed file matches the extensions
+ const ext = path.extname(filePath).substring(1);
+ if (extsToWatch.includes(ext)) {
+ debug && log.info(`File ${event}: ${filePath}`);
+ // Notify all connected clients to reload
+ wss.clients.forEach((client) => {
+ if (client.readyState === WebSocket.OPEN) {
+ client.send("reload");
+ }
+ });
+ }
});
// Middleware function to inject the client-side script
@@ -174,21 +212,26 @@ module.exports = async ({ log, resources, options, middlewareUtil }) => {
let body = "";
res.write = function (chunk) {
- body += chunk.toString();
+ body += chunk instanceof Buffer ? chunk.toString() : chunk;
};
res.end = function (chunk) {
if (chunk) {
- body += chunk.toString();
+ body += chunk instanceof Buffer ? chunk.toString() : chunk;
}
// Check if response is HTML
if (res.getHeader("Content-Type") && res.getHeader("Content-Type").includes("text/html")) {
+ // Determine protocol
+ const protocol = useHttps ? "wss" : "ws";
// Inject the client-side script
const script = `