diff --git a/apps/front/.env b/apps/front/.env index e911688..2b3741f 100644 --- a/apps/front/.env +++ b/apps/front/.env @@ -1,8 +1,9 @@ - # ------------------------------------------------------------------------------ BASE # base start & end with slash: /{base}/ VITE_APP_BASE=/ +# protocol for server: http or https (default: http) +PROTOCOL=http # ------------------------------------------------------------------------------ DOCKER diff --git a/apps/front/README.md b/apps/front/README.md index 680c90e..c6f307a 100644 --- a/apps/front/README.md +++ b/apps/front/README.md @@ -11,6 +11,7 @@ - [scaffold](#scaffold) - [generate](#generate) - [Vite plugins](#vite-plugins) +- [Setup local SSL](#setup-local-ssl) ## About @@ -200,6 +201,28 @@ Second part of the configuration is defined from [config/config.js](config/confi htaccessTemplateFilePath: resolve("src/.htaccess") ``` +## Setup local SSL + +- Generate a self-signed certificate from `apps/front` root folder: + + ```shell + sudo openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes + ``` + +- Trust the certificate: + + ```shell + sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain cert.pem + ``` + +- Update .env var `PROTOCOL` to `https` + + ```dotenv + PROTOCOL=https + ``` + +When you run `npm run dev`, you should see the app running on https://localhost:3000 + ## Credits Developed by [cher-ami](https://github.com/cher-ami) team. diff --git a/apps/front/server.js b/apps/front/server.js index 867e77c..5297911 100644 --- a/apps/front/server.js +++ b/apps/front/server.js @@ -1,77 +1,112 @@ -import * as React from "react" +import debug from "@wbe/debug" import express from "express" -import { createServer } from "vite" +import * as https from "https" +import * as mfs from "@wbe/mfs" import portFinderSync from "portfinder-sync" -import config from "./config/config.js" import { renderToPipeableStream } from "react-dom/server" -import debug from "@wbe/debug" +import { createServer, loadEnv } from "vite" +import config from "./config/config.js" const log = debug("server:server") + +const loadEnvVars = loadEnv(process.env.NODE_ENV, process.cwd(), "") const isProduction = process.env.NODE_ENV === "production" const port = process.env.DOCKER_NODE_PORT ?? portFinderSync.getPort(3000) +const protocol = loadEnvVars.PROTOCOL ?? "http" +const isSSL = protocol === "https" -/** - * Dev server - * - * - */ -async function createDevServer() { - const app = express() - - // dev script to inject - const devScripts = { - js: [{ tag: "script", attr: { type: "module", src: "/src/index.tsx" } }], +;(async () => { + // Get cert and key for https + let key, cert + if (isSSL) { + if (!(await mfs.fileExists("key.pem")) || !(await mfs.fileExists("cert.pem"))) { + console.error( + "You need to generate a key and a cert file with openssl in the apps/front/ directory. Follow the README documentation 'setup-local-ssl'." + ) + process.exit(1) + } + key = await mfs.readFile("key.pem") + cert = await mfs.readFile("cert.pem") } - // Create Vite server in middleware mode. - // This disables Vite's own HTML serving logic and let the parent server take control. - // https://vitejs.dev/config/server-options.html#server-middlewaremode - const vite = await createServer({ - logLevel: "info", - server: { middlewareMode: true }, - appType: "custom", - }) + /** + * Dev server + * + * + */ + async function createDevServer() { + const app = express() - // use vite's connect instance as middleware - app.use(vite.middlewares) - app.use("*", async (req, res, next) => { - try { - // Transforms the ESM source code to be usable in Node.js - const { render } = await vite.ssrLoadModule( - `${config.srcDir}/server/index-server.tsx` - ) - // Get react-dom from the render method - const dom = await render(req.originalUrl, devScripts, false) - // Create stream with renderToPipeableStream to support Suspense API - const stream = renderToPipeableStream(dom, { - onShellReady() { - res.setHeader("Content-type", "text/html") - res.statusCode = 200 - stream.pipe(res) - }, - onError(e) { - res.statusCode = 500 - console.error(e) - }, + // dev script to inject + const devScripts = { + js: [{ tag: "script", attr: { type: "module", src: "/src/index.tsx" } }], + } + + // Create Vite server in middleware mode. + // This disables Vite's own HTML serving logic and let the parent server take control. + // https://vitejs.dev/config/server-options.html#server-middlewaremode + const vite = await createServer({ + logLevel: "info", + server: { + middlewareMode: true, + https: (isSSL && { key, cert }) || false, + cors: false, + }, + appType: "custom", + }) + + // use vite's connect instance as middleware + app.use(vite.middlewares) + app.use("*", async (req, res, next) => { + try { + // Transforms the ESM source code to be usable in Node.js + const { render } = await vite.ssrLoadModule( + `${config.srcDir}/server/index-server.tsx` + ) + // Get react-dom from the render method + const dom = await render(req.originalUrl, devScripts, false) + // Create stream with renderToPipeableStream to support Suspense API + const stream = renderToPipeableStream(dom, { + onShellReady() { + res.setHeader("Content-type", "text/html") + res.statusCode = 200 + stream.pipe(res) + }, + onError(e) { + res.statusCode = 500 + console.error(e) + }, + }) + } catch (e) { + vite.ssrFixStacktrace(e) + next(e) + } + }) + + let sslServer + if (isSSL) { + sslServer = https.createServer({ key, cert }, app) + sslServer.on("error", (error) => { + log(`Error on server: ${error}`) }) - } catch (e) { - vite.ssrFixStacktrace(e) - next(e) } - }) - return { app, vite } -} -/** - * Production server - * TODO - * - * - */ -async function createProdServer() {} + // return vite, app and server + return { vite, app, sslServer } + } -/** - * Let's go! - */ + /** + * Production server + * TODO + * + * + */ + async function createProdServer() {} -;(isProduction ? createProdServer : createDevServer)().then(({ app }) => app.listen(port)) + /** + * Let's go! + */ + ;(isProduction ? createProdServer : createDevServer)().then(({ app, sslServer }) => + (sslServer ?? app).listen(port) + ) +})() diff --git a/apps/front/vite.config.ts b/apps/front/vite.config.ts index 94eb389..e130d5d 100644 --- a/apps/front/vite.config.ts +++ b/apps/front/vite.config.ts @@ -22,11 +22,12 @@ const log = debug("config:vite.config") export default defineConfig(({ command, mode }: ConfigEnv): UserConfig => { const isDevelopment = mode === "development" const ipAddress = ip.address() - const protocol: "http" | "https" = "http" // get env variables from selected .env (depend of mode) const loadEnvVars = loadEnv(mode, process.cwd(), "") + const protocol: "http" | "https" = (loadEnvVars.PROTOCOL as "http" | "https") ?? "http" + // merge loadEnv selected by vite in process.env process.env = { ...loadEnvVars, @@ -67,7 +68,7 @@ export default defineConfig(({ command, mode }: ConfigEnv): UserConfig => { cors: true, host: true, port: process.env.PORT as any, - https: process.env.PROTOCOL === "https", + https: protocol === "https", origin: `${protocol}://${process.env.HOST}:${process.env.PORT}`, watch: { // do not watch .env files to avoid reloading when build-dotenv is processed