diff --git a/.firebase/hosting.ZGlzdA.cache b/.firebase/hosting.ZGlzdA.cache new file mode 100644 index 0000000..a25b6bd --- /dev/null +++ b/.firebase/hosting.ZGlzdA.cache @@ -0,0 +1,10 @@ +index.html,1605445365695,31e756df6cc5f4a8c0929159b7138da8a9e83bf048a8cb8f12e8260410721e49 +icon-180.png,1605445365697,92b3a619d6458a45d8a0c0fe1cc30d00d2b8667f7c7779a1ee4047743ad878c4 +manifest.json,1605445365697,f9f961e572d8185f8193c231ce9387026d500a021e88cabce702f4a4b5ddca62 +style.css,1605445365695,585b07896f1e83b449fc299fe3768854d25e0159ef7d1cc79029d012f234d2e9 +sw.js,1605445365695,256cd2d7ba9008c58800eb0f9b3d9a821feb3b2f65ca44528396427b4ea6548d +workbox-468c4d03.js,1605445365697,ea5fb6b5fef5cc7f273684fd2e59ac0cebd89aa17ee65eb3025aae71ad25caf5 +icon-192.png,1605445365700,019b6801c3ed961b26e089a9834c76036841020c6e1b32dad09b99011a46fd4f +favicon.ico,1605445365697,8bd2763f4080b268398c699ed305c33029429fb7c2f01f7fc8a2526a76e9beab +icon-512.png,1605445365695,5546a701cef8feed3551ec81d90e96227387f5b2f5c7170edf54a9f45c26273f +main.eaaaf6bc3fbc63029ff8.js,1605445365695,afa58da02ab17097146b0eaa731f3cdd2913895d0e259873fac5bdbf6533a902 diff --git a/.firebaserc b/.firebaserc new file mode 100644 index 0000000..a3c51b2 --- /dev/null +++ b/.firebaserc @@ -0,0 +1,5 @@ +{ + "projects": { + "default": "fb2ical-3051b" + } +} diff --git a/firebase.json b/firebase.json new file mode 100644 index 0000000..15d17eb --- /dev/null +++ b/firebase.json @@ -0,0 +1,25 @@ +{ + "emulators": { + "functions": { + "port": 5001 + }, + "ui": { + "enabled": true, + "port": 5002 + } + }, + "hosting": { + "public": "dist/", + "ignore": [ + "firebase.json", + "**/.*", + "**/node_modules/**" + ], + "rewrites": [ + { + "source": "/**", + "function": "app" + } + ] + } +} diff --git a/functions/index.js b/functions/index.js index 58c59a0..3b1c1ac 100644 --- a/functions/index.js +++ b/functions/index.js @@ -2,19 +2,19 @@ const functions = require('firebase-functions'); const admin = require('firebase-admin') const { configureApplication } = require('./lib/app') -const { createAppLogger } = require('../lib/log-utils') -const { - createRouteLogger, - createErrorLogger, -} = require('../lib/middlewares') +// const { createAppLogger } = require('./lib/log-utils') +// const { +// createRouteLogger, +// createErrorLogger, +// } = require('./lib/middlewares') admin.initializeApp() const isDevelopment = process.env.NODE_ENV === 'development' -const appLogger = createAppLogger({ dev: isDevelopment }) -const errorLogger = createErrorLogger({ dev: isDevelopment }) -const routeLogger = isDevelopment ? createRouteLogger({ dev: isDevelopment }) : null +// const appLogger = createAppLogger({ dev: isDevelopment }) +// const errorLogger = createErrorLogger({ dev: isDevelopment }) +// const routeLogger = isDevelopment ? createRouteLogger({ dev: isDevelopment }) : null const corsOptions = isDevelopment ? { origin: 'http://localhost:5000', } : null diff --git a/lib/app/index.js b/lib/app/index.js new file mode 100644 index 0000000..2789372 --- /dev/null +++ b/lib/app/index.js @@ -0,0 +1,106 @@ +const express = require('express') +const bodyParser = require('body-parser') +const path = require('path') +const favicon = require('serve-favicon') +const rateLimit = require('express-rate-limit') +const cors = require('cors') + +const { + error, + notFound, + download, + downloadHTML, +} = require('../routes') + +const { + genericErrorHandler, + checkURLParameter, + forceSecure, +} = require('../middlewares') + +const certEndpoint = process.env.CERT_ENDPOINT || '' +const certSecret = process.env.CERT_SECRET || '' +const enforceHTTPS = Boolean(process.env.ENFORCE_HTTPS) + +const configureApplication = ({ + appLogger, + errorLogger, + routeLogger, + corsOptions, + rateLimitEnabled, +}) => { + const pkg = require('../../package.json') + const version = pkg.version + + const app = express() + + if (corsOptions) { + app.use(cors(corsOptions)) + } + + // Force app to always redirect to HTTPS + // use when you can't configure web server + if (enforceHTTPS) { + app.use(forceSecure) + } + + if (appLogger) { + app.post('/download/html', downloadHTML(appLogger)) + app.post('/download', download(appLogger)) + } + + // Server logs You can alternatively enable these to mimic logs created + // by your web server + if (routeLogger) { + app.use(routeLogger) + } + + if (errorLogger) { + app.use(errorLogger) + } + + app.set('view engine', 'ejs') + app.set('views', path.join(__dirname, '..', 'views')) + app.set('trust proxy', 1) + + app.use(express.static(path.join(__dirname, '..', '..', 'dist'))) + // app.use(favicon(path.join(__dirname, '..', 'dist', 'favicon.ico'))) + app.use(bodyParser.urlencoded({ extended: true })) + + if (rateLimitEnabled) { + const limiter = rateLimit({ + windowMs: 60 * 1000, + max: 10, + }) + + app.use('/download/html', limiter) + app.use('/download', limiter) + } + + if (certEndpoint) { + app.get(`/${certEndpoint}`, (req, res) => { + res.status(200).send(certSecret) + }) + } + + app.get('/error', error) + + app.get('/about', (req, res) => { + res.render('about', { version }) + }) + + // NOTE: Capture all unkown URLs + app.get('*', notFound) + + app.use('/download/html', checkURLParameter) + + app.use('/download', checkURLParameter) + + app.use(genericErrorHandler) + + return app +} + +module.exports = { + configureApplication, +} diff --git a/lib/index.js b/lib/index.js index 0f9c776..5f50d75 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,87 +1,22 @@ -const express = require('express') -const bodyParser = require('body-parser') -const path = require('path') -const favicon = require('serve-favicon') -const rateLimit = require('express-rate-limit') - +const { configureApplication } = require('./app') +const { createAppLogger } = require('./log-utils') const { - error, - notFound, - download, - downloadHTML, -} = require('./routes') - -const { - genericErrorHandler, - checkURLParameter, - forceSecure, createRouteLogger, createErrorLogger, } = require('./middlewares') -const { createAppLogger } = require('./log-utils') -const port = process.env.PORT -const certEndpoint = process.env.CERT_ENDPOINT || '' -const certSecret = process.env.CERT_SECRET || '' const isDevelopment = process.env.NODE_ENV === 'development' -const enforceHTTPS = Boolean(process.env.ENFORCE_HTTPS) +const port = process.env.PORT -const app = express() const appLogger = createAppLogger({ dev: isDevelopment }) -const limiter = rateLimit({ - windowMs: 60 * 1000, - max: 10, +const errorLogger = createErrorLogger({ dev: isDevelopment }) +const routeLogger = isDevelopment ? createRouteLogger({ dev: isDevelopment }) : null + +const app = configureApplication({ + appLogger, + errorLogger, + routeLogger, + rateLimitEnabled: true }) -const pkg = require('../package.json') -const version = pkg.version || '' - -// Force app to always redirect to HTTPS -// use when you can't configure web server -if (enforceHTTPS) { - app.use(forceSecure) -} - -// Server logs -// You can alternatively enable these to mimic logs created -// by your web server -if (isDevelopment) { - app.use(createRouteLogger({ dev: isDevelopment })) -} - -app.set('view engine', 'ejs') -app.set('views', path.join(__dirname, 'views')) -app.set('trust proxy', 1) - -app.use(express.static(path.join(__dirname, '..', 'dist'))) -//app.use(favicon(path.join(__dirname, '..', 'dist', 'favicon.ico'))) -app.use(bodyParser.urlencoded({ extended: true })) - -const indexFile = path.join(__dirname, '..', 'dist', 'index.html') - -if (certEndpoint) { - app.get(`/${certEndpoint}`, (req, res) => { - res.status(200).send(certSecret) - }) -} - -app.get('/error', error) - -app.get('/about', (req, res) => { - res.render('about', { version }) -}) - -// NOTE: Capture all unkown URLs -app.get('*', notFound) - -app.use('/download/html', limiter) -app.use('/download/html', checkURLParameter) -app.post('/download/html', downloadHTML(appLogger)) - -app.use('/download', limiter) -app.use('/download', checkURLParameter) -app.post('/download', download) - -app.use(createErrorLogger({ dev: isDevelopment })) -app.use(genericErrorHandler) app.listen(port) diff --git a/lib/routes/download.js b/lib/routes/download.js index 3733bec..0e04ec8 100644 --- a/lib/routes/download.js +++ b/lib/routes/download.js @@ -18,12 +18,12 @@ const downloadHTML = (logger) => (async (req, res, next) => { } }) -const download = async (req, res, next) => { +const download = (logger) => (async (req, res, next) => { try { const { url } = req.body const ics = await retrieveICS(url, { - logger: appLogger, + logger, crawl, }) @@ -34,7 +34,7 @@ const download = async (req, res, next) => { } catch (err) { next(err) } -} +}) module.exports = { downloadHTML, diff --git a/lib/services/crawler.js b/lib/services/crawler.js index 30b8156..681ff96 100644 --- a/lib/services/crawler.js +++ b/lib/services/crawler.js @@ -28,3 +28,4 @@ const crawl = async (url, { logger }) => { } module.exports = crawl + diff --git a/lib/static/404.html b/lib/static/404.html new file mode 100644 index 0000000..829eda8 --- /dev/null +++ b/lib/static/404.html @@ -0,0 +1,33 @@ + + + + + + Page Not Found + + + + +
+

404

+

Page Not Found

+

The specified file was not found on this website. Please check the URL for mistakes and try again.

+

Why am I seeing this?

+

This page was generated by the Firebase Command-Line Interface. To modify it, edit the 404.html file in your project's configured public directory.

+
+ + diff --git a/lib/static/index.html b/lib/static/index.html index 415e1ac..4f70a40 100644 --- a/lib/static/index.html +++ b/lib/static/index.html @@ -77,5 +77,11 @@ SourceAbout + + diff --git a/package-lock.json b/package-lock.json index afeb6f1..67d783c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4122,6 +4122,15 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, "create-ecdh": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", @@ -9544,8 +9553,7 @@ "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, "object-copy": { "version": "0.1.0", diff --git a/package.json b/package.json index a5d0129..cddbef2 100644 --- a/package.json +++ b/package.json @@ -8,11 +8,15 @@ "node": "10.15.0" }, "scripts": { - "build": "webpack --mode=production", - "build:dev": "webpack --mode=development", + "build": "npm run clean:build && webpack --mode=production", + "build:dev": "npm run clean:build && webpack --mode=development", + "build:firebase:hosting": "npm run clean:build && NODE_ENV=production NODE_APP=firebase webpack --mode=production", + "build:firebase:hosting:dev": "npm run clean:build && NODE_ENV=development NODE_APP=firebase webpack --mode=development", + "clean:build": "rm dist/** || true", "start": "npm run build && node lib/index.js", "start:dev": "concurrently \"npm run build:dev\" \"NODE_ENV=development PORT=3000 nodemon lib/index.js\"", "start:dev:inspect": "npm run build:dev && NODE_ENV=development PORT=3000 nodemon --inspect lib/index.js", + "start:dev:firebase": "npm run build:firebase:hosting:dev && NODE_ENV=development firebase emulators:start", "test": "jest" }, "keywords": [ @@ -27,6 +31,7 @@ "dependencies": { "body-parser": "^1.19.0", "cheerio": "^1.0.0-rc.3", + "cors": "^2.8.5", "dayjs": "^1.8.16", "ejs": "^2.7.1", "express": "^4.17.1", diff --git a/webpack.config.js b/webpack.config.js index 5a0e29e..5003137 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,4 +1,5 @@ const path = require('path') +const fs = require('fs') const pkg = require('./package.json') const HtmlWebpackPlugin = require('html-webpack-plugin') const CopyWebpackPlugin = require('copy-webpack-plugin') @@ -6,10 +7,30 @@ const { GenerateSW } = require('workbox-webpack-plugin') const destination = path.join(__dirname, 'dist') const isDevelopment = Boolean(process.argv[2] && process.argv[2].includes('mode=development')) +const isFirebaseEnv = process.env.NODE_APP === 'firebase' +const firebaseConfigFilePath = path.join(__dirname, '.firebaserc') +const hasFirebaseConfig = fs.existsSync(firebaseConfigFilePath) + +if (isFirebaseEnv && hasFirebaseConfig) { + console.info('Prepare build for Firebase hosting') +} + +const getFirebaseUrl = () => { + const contents = fs.readFileSync(firebaseConfigFilePath) + const rawContents = contents.toString() + const json = JSON.parse(rawContents) + const projectName = json.projects ? json.projects.default : null + + if (isDevelopment) { + return `http://localhost:5001/${projectName}/uscentral-1/app` + } + + return `${projectName}.web.app/app` +} module.exports = { entry: path.join(__dirname, 'lib', 'static', 'index.js'), - watch: isDevelopment, + watch: isDevelopment && !isFirebaseEnv, output: { filename: '[name].[hash].js', path: destination, @@ -23,6 +44,7 @@ module.exports = { new HtmlWebpackPlugin({ template: path.join(__dirname, 'lib', 'static', 'index.html'), version: pkg.version, + serverURL: (isFirebaseEnv && hasFirebaseConfig) ? getFirebaseUrl() : '', inject: 'body', }), new GenerateSW({