Browsersync Middleware to Replace String - middleware

I am trying to create a Browsersync middleware to replace a string in HTML files before they are served to the browser.
I'm not entirely sure this is even possible.
So far I am able to identify when a HTML file is being requested by:
function hublMiddleware (req, res, next) {
var parsed = require("url").parse(req.url);
if (parsed.pathname.match(/\.html$/)) {}
next();
};
I can put a console.log() inside the if statement so I know it's working.
But from here I am genuinely stuck. I have searched for examples of how this may be done, e.g.
res.removeHeader('Content-Length');
res.pipe($.replace(/({{\s|\s}})|({%.*%})/g, '<!---->'))
.pipe(res);
return next();
But to no avail.
I should say I am using Browsersync with Gulp. Any help with this would be much appreciated!

This one does exactly what you want:
bs-rewrite-rules
Here's how I used it:
gulp.task('serve', function () {
browserSync({
port: 8000,
server: {
baseDir: './'
},
plugins: ['bs-rewrite-rules'],
rewriteRules: [
{
match: 'YOUR_GOOGLE_MAPS_API_KEY',
replace:'<MY_ACTUAL_API_KEY>'
}
]
});
gulp.watch(['*.html', 'css/**/*.css', 'js/**/*.js'], reload);
});

Related

Swagger UI & Vercel: Unexpected token < in JSON at position 1

Code
Full codebase & folder structure can be seen in GitHub
Here is the Swagger related route (had to make it a standalone server)
// api/v1.ts
import express = require("express");
import swaggerJSDoc = require("swagger-jsdoc");
import swaggerUi = require("swagger-ui-express");
import packageJSON = require("../package.json");
import path = require("path");
const app = express();
app.use(express.json());
app.use(express.static(path.resolve(__dirname, "../", "public")));
const swaggerSpec = swaggerJSDoc({
swaggerDefinition: some_spec,
apis: ["api/*"]
});
const cssOpts = some_css_override;
app.use("/api/v1", swaggerUi.serve, swaggerUi.setup(swaggerSpec, cssOpts));
module.exports = app;
Problem
When I run vercel dev (locally- localhost:3000/api/v1), I see documentation as expected:
However when I push my code to a branch which triggers a vercel build, I see the following:
Checking the console, I see:
DevTools failed to load source map: Could not parse content for https://colormaster-1unjfn63b-lbragile.vercel.app/api/v1/swagger-ui-bundle.js.map: Unexpected token < in JSON at position 1
DevTools failed to load source map: Could not parse content for https://colormaster-1unjfn63b-lbragile.vercel.app/api/v1/swagger-ui-standalone-preset.js.map: Unexpected token < in JSON at position 1
Even though they respond with 200
I understand that this has something to do with JSON.parse() of HTML content, but not sure how to fix this. Any ideas?
I am facing the exact same problem of you, trying without success to deploy Swagger to Vercel with Express.
I did one step more, and now I'm seen an error in my console:
Refused to apply style from
'https://myurlishere.vercel.app/api-docs/swagger-ui.css' because its
MIME type ('text/html') is not a supported stylesheet MIME type, and
strict MIME checking is enabled.
What I did was, adding a file routes.ts
import { Router } from 'express';
import LanguageController from './controller/LanguageController';
import WordController from './controller/WordController';
const routes = Router();
routes.get("/word", WordController.find);
routes.get("/word/:wordName/language/:languageId", WordController.findByWordAndLanguage);
routes.post("/word", WordController.create);
routes.get("/language", LanguageController.find);
export default routes;
And my server.ts looks like that:
import mongoose from 'mongoose';
import routes from './routes';
const express = require("express");
if (process.env.NODE_ENV !== 'production') {
require('dotenv').config();
}
const app = express();
const cors = require('cors');
mongoose.connect(process.env.MONGODB_URI || "", {
dbName: "WordsThatIKnowMongoDB"
})
.then(() => console.debug("Database connected!"))
.catch(err => { console.debug(err) });
app.use(express.json());
app.use(express.static("/api-docs"));
app.use(cors());
app.use(routes);
const swaggerUi = require('swagger-ui-express');
const swaggerDocument = require('./swagger.json');
routes.use('/api-docs', swaggerUi.serve);
routes.get('/api-docs', swaggerUi.setup(swaggerDocument));
app.listen(5000, () => {
console.debug("Running on port 5000.");
});
// Export the Express API
module.exports = app;
You will see in the file above that I changed app. to routes. like this:
routes.use('/api-docs', swaggerUi.serve);
routes.get('/api-docs', swaggerUi.setup(swaggerDocument));
I still can't solve this problem, but maybe this new error can help you find the solution. I'm also looking for that.
EDIT: It's solved.
This is the code that solved my problem:
server.ts
import path from 'path';
import cors from 'cors';
import bodyParser from 'body-parser';
import mongoose from 'mongoose';
import routes from './routes';
const express = require("express");
const app = express();
const ROOT_FOLDER = path.join(__dirname, '..');
const SRC_FOLDER = path.join(ROOT_FOLDER, 'src');
// parse requests of content-type - application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: true }));
// parse requests of content-type - application/json
app.use(bodyParser.json());
app.use(cors());
app.use(routes);
if (process.env.NODE_ENV !== 'production') {
require('dotenv').config();
}
mongoose.connect(process.env.MONGODB_URI || "", {
dbName: "WordsThatIKnowMongoDB"
})
.then(() => console.debug("Database connected!"))
.catch(err => { console.debug(err) });
const swaggerUi = require('swagger-ui-express');
const swaggerDocument = require('./swagger.json');
const options = { customCssUrl: '/public/swagger-ui.css', customSiteTitle: "The Words That I Know API - Swagger" };
app.use('/public', express.static(path.join(SRC_FOLDER, 'public')));
app.use('/', swaggerUi.serve);
app.get('/', swaggerUi.setup(swaggerDocument, options));
app.listen(5000, () => {
console.debug("Running on port 5000.");
});
export default app;
Don't forget to put the styles from Swagger at '/public/swagger-ui.css'. Create a public folder inside src and include a swagger-ui.css file. Inside of this, past swagger styles. You can find swagger styles using inspect on browser, and going to source tab. There you'll find the swagger-ui.css file; remove the commented line after pasting the styles code.
If you prefer an easy way to get the styles code, get this file. https://github.com/deywersonp/ghibli-50-api/blob/main/src/public/css/swagger-ui.css
hey I got the same problem ! the solution I use isn't optimal but it worked
knowing the css file is been wel process localy,all i did is to add custom css to my swagger-ui documentation. so as the css is working localy I copied all the css ( inspected web browser saw the file swagger-ui.css file source code ) , created a css file , paste the css to it , then i added my css file to my static folder.
here is how to add costum css
const options = { customCssUrl: '/public/css/swagger-ui.css',};
router.use('/api-docs-ui', function(req, res, next){
swaggerDocument.host = req.get('host');
req.swaggerDoc = swaggerDocument;
next();
}, swaggerUi.serve, swaggerUi.setup(swaggerDocument, options));
here is how you define your statics files
app.use('/public/css', express.static('public/css'));
so now localy I have 2 css file working but on vercel just one is working!
hope it could help
Also you can use a CDN for you Swagger styles if you don't wont to put the css files into your public folder.

NestJS Alphabetize Endpoints in SwaggerUI

This SO answer shows that SwaggerUi will sort endpoints alphabetically if it is passed apisSorter : "alpha" when instantiated. In NestJS the config options are passed in the SwaggerModule.createDocument. I cannot see where in the config eg here I can pass this.
You can pass it as the fourth parameter to the SwaggerModule.setup method like so:
const document = SwaggerModule.createDocument(app, options);
SwaggerModule.setup('docs', app, document, {
swaggerOptions: {
tagsSorter: 'alpha',
operationsSorter: 'alpha',
},
});
swaggerOptions is untyped which is why you just have to know what you're passing. Found the answer in the discord server so hopefully that link doesn't expire.
To anyone trying #midopa's solution for FastifySwagger, pass the tagsSorter and operationsSorter values to uiConfig instead of swaggerOptions.
const doc = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('docs', app, doc, {
uiConfig: {
tagsSorter: 'alpha',
operationsSorter: 'alpha',
},
});

How to populate Adal8Service 's configOptions using config settings in Angular 7 and load when the application loads

I am using import { Adal8Service, Adal8HTTPService } from 'adal-angular8'; for Azure authentication. I am using the below in app.module.ts:
export function appInit(appConfigService: AppInitService) {
return (): any => {
appConfigService.getApplicationConfig().subscribe((res) =>{
sessionStorage.setItem("appConfig",JSON.stringify(res));
timeout(500);
});
}
}
my getApplicationConfig() is below:
public getApplicationConfig() {
return this.http.get('assets/config.json');}
and in the providers [] the below:
AuthenticationService,
AppInitService,
{
provide: APP_INITIALIZER,
useFactory: appInit,
deps: [AppInitService],
multi: true
},
Adal8Service,
{ provide: Adal8HTTPService,
useFactory: Adal8HTTPService.factory,
deps: [HttpClient, Adal8Service],
multi: true
},
The here is the appInit function does not block (even removing the timeout()) the application loading and proceeds to to the
this.adalService.init(this.adalConfig);
this.adalService.handleWindowCallback();
(where this.adalConfig = sessionStorage.getItem("appConfig")).
If I refresh the page, then I am getting redirected to the Azure Ad login page properly or if I am hardcoding the configOptions of the this.adalService.init("HARDOCDE all values") then it works fine. How do I make the application block the configuration. I am storing the config values under /assets/config.json. I am not sure what I am doing wrong here. I did try reading the "json" file, but again I have to change it before proceeding to production. How do I make the application wait, there are also other config values for the application stored in the /assets/config.json file. Is the way I use the APP_INITIALIZER correct? Please point me to right direction.
The problem is not related to ADAL but related to how asynchronous functions works in javascript.
In order to block the execution of the function, you can either write down a function which waits till the response is returned by the http request or you can use library like waitfor-ES6 which can help you do that.
Change needs to be done at
export function appInit(appConfigService: AppInitService) {
return (): any => {
response = yield wait.for(appConfigService.getApplicationConfig);
sessionStorage.setItem("appConfig",JSON.stringify(response));
}
}
Please note this is not exact change but the direction of the change that you will need to perform. Hope this helps.

Passing arguments to a running electron app

I have found some search results about using app.makeSingleInstance and using CLI arguments, but it seems that the command has been removed.
Is there any other way to send a string to an already started electron app?
One strategy is to have your external program write to a file that your electron app knows about. Then, your electron app can listen for changes to that file and can read it to get the string:
import fs
fs.watch("shared/path.txt", { persistent: false }, (eventType: string, fileName: string) => {
if (eventType === "change") {
const myString: string = fs.readFileSync(fileName, { encoding: "utf8" });
}
});
I used the synchronous readFileSync for simplicity, but you might want to consider the async version.
Second, you'll need to consider the case where this external app is writing so quickly that maybe the fs.watch callback is triggered only once for two writes. Could you miss a change?
Otherwise, I don't believe there's an Electron-native way of getting this information from an external app. If you were able to start the external app from your Electron app, then you could just do cp.spawn(...) and use its stdout pipe to listen for messages.
If shared memory were a thing in Node, then you could use that, but unfortunately it's not.
Ultimately, the most elegant solution to my particular problem was to add a http api endpoint for the Electron app using koa.
const Koa = require("koa");
const koa = new Koa();
let mainWindow;
function createWindow() {
let startServer = function() {
koa.use(async ctx => {
mainWindow.show();
console.log("text received", ctx.request.query.text);
ctx.body = ctx.request.query.text;
});
koa.listen(3456);
};
}
Now I can easily send texts to Electron from outside the app using the following url:
localhost:3456?text=myText

Setting service worker to exclude certain urls only

I built an app using create react which by default includes a service worker. I want the app to be run anytime someone enters the given url except when they go to /blog/, which is serving a set of static content. I use react router in the app to catch different urls.
I have nginx setup to serve /blog/ and it works fine if someone visits /blog/ without visiting the react app first. However because the service worker has a scope of ./, anytime someone visits any url other than /blog/, the app loads the service worker. From that point on, the service worker bypasses a connection to the server and /blog/ loads the react app instead of the static contents.
Is there a way to have the service worker load on all urls except /blog/?
So, considering, you have not posted any code relevant to the service worker, you might consider adding a simple if conditional inside the code block for fetch
This code block should already be there inside your service worker.Just add the conditionals
self.addEventListener( 'fetch', function ( event ) {
if ( event.request.url.match( '^.*(\/blog\/).*$' ) ) {
return false;
}
// OR
if ( event.request.url.indexOf( '/blog/' ) !== -1 ) {
return false;
}
// **** rest of your service worker code ****
note you can either use the regex or the prototype method indexOf.
per your whim.
the above would direct your service worker, to just do nothing when the url matches /blog/
Another way to blacklist URLs, i.e., exclude them from being served from cache, when you're using Workbox can be achieved with workbox.routing.registerNavigationRoute:
workbox.routing.registerNavigationRoute("/index.html", {
blacklist: [/^\/api/,/^\/admin/],
});
The example above demonstrates this for a SPA where all routes are cached and mapped into index.html except for any URL starting with /api or /admin.
here's whats working for us in the latest CRA version:
// serviceWorker.js
window.addEventListener('load', () => {
if (isAdminRoute()) {
console.info('unregistering service worker for admin route')
unregister()
console.info('reloading')
window.location.reload()
return false
}
we exclude all routes under /admin from the server worker, since we are using a different app for our admin area. you can change it of course for anything you like, here's our function in the bottom of the file:
function isAdminRoute() {
return window.location.pathname.startsWith('/admin')
}
Here's how you do it in 2021:
import {NavigationRoute, registerRoute} from 'workbox-routing';
const navigationRoute = new NavigationRoute(handler, {
allowlist: [
new RegExp('/blog/'),
],
denylist: [
new RegExp('/blog/restricted/'),
],
});
registerRoute(navigationRoute);
How to Register a Navigation Route
If you are using or willing to use customize-cra, the solution is quite straight-forward.
Put this in your config-overrides.js:
const { adjustWorkbox, override } = require("customize-cra");
module.exports = override(
adjustWorkbox(wb =>
Object.assign(wb, {
navigateFallbackWhitelist: [
...(wb.navigateFallbackWhitelist || []),
/^\/blog(\/.*)?/,
],
})
)
);
Note that in the newest workbox documentation, the option is called navigateFallbackAllowlist instead of navigateFallbackWhitelist. So, depending on the version of CRA/workbox you use, you might need to change the option name.
The regexp /^/blog(/.*)?/ matches /blog, /blog/, /blog/abc123 etc.
Try using the sw-precache library to overwrite the current service-worker.js file that is running the cache strategy. The most important part is setting up the config file (i will paste the one I used with create-react-app below).
Install yarn sw-precache
Create and specify the config file which indicates which URLs to not cache
modify the build script command to make sure sw-precache runs and overwrites the default service-worker.js file in the build output directory
I named my config file sw-precache-config.js is and specified it in build script command in package.json. Contents of the file are below. The part to pay particular attention to is the runtimeCaching key/option.
"build": "NODE_ENV=development react-scripts build && sw-precache --config=sw-precache-config.js"
CONFIG FILE: sw-precache-config.js
module.exports = {
staticFileGlobs: [
'build/*.html',
'build/manifest.json',
'build/static/**/!(*map*)',
],
staticFileGlobsIgnorePatterns: [/\.map$/, /asset-manifest\.json$/],
swFilePath: './build/service-worker.js',
stripPrefix: 'build/',
runtimeCaching: [
{
urlPattern: /dont_cache_me1/,
handler: 'networkOnly'
}, {
urlPattern: /dont_cache_me2/,
handler: 'networkOnly'
}
]
}
Update (new working solution)
In the last major release of Create React App (version 4.x.x), you can easily implement your custom worker-service.js without bleeding. customize worker-service
Starting with Create React App 4, you have full control over customizing the logic in this service worker, by creating your own src/service-worker.js file, or customizing the one added by the cra-template-pwa (or cra-template-pwa-typescript) template. You can use additional modules from the Workbox project, add in a push notification library, or remove some of the default caching logic.
You have to upgrade your react script to version 4 if you are currently using older versions.
Working solution for CRA v4
Add the following code to the file service-worker.js inside the anonymous function in registerRoute-method.
// If this is a backend URL, skip
if (url.pathname.startsWith("/backend")) {
return false;
}
To simplify things, we can add an array list of items to exclude, and add a search into the fetch event listener.
Include and Exclude methods below for completeness.
var offlineInclude = [
'', // index.html
'sitecss.css',
'js/sitejs.js'
];
var offlineExclude = [
'/networkimages/bigimg.png', //exclude a file
'/networkimages/smallimg.png',
'/admin/' //exclude a directory
];
self.addEventListener("install", function(event) {
console.log('WORKER: install event in progress.');
event.waitUntil(
caches
.open(version + 'fundamentals')
.then(function(cache) {
return cache.addAll(offlineInclude);
})
.then(function() {
console.log('WORKER: install completed');
})
);
});
self.addEventListener("fetch", function(event) {
console.log('WORKER: fetch event in progress.');
if (event.request.method !== 'GET') {
console.log('WORKER: fetch event ignored.', event.request.method, event.request.url);
return;
}
for (let i = 0; i < offlineExclude.length; i++)
{
if (event.request.url.indexOf(offlineExclude[i]) !== -1)
{
console.log('WORKER: fetch event ignored. URL in exclude list.', event.request.url);
return false;
}
}

Resources