How to setup custom middleware in strapi? - middleware

I just wanted to setup a simple custom middleware in strapi. I have tried what they are writing in docs but I found that environments folder and inside configurations are removed. Follwing that currently I have writtent.
/config/environments/development/middleware.json
{
"subscribers": {
"enabled": true
}
}
/config/middleware.json
{
"timeout": 100,
"load": {
"before": ["responseTime", "logger", "cors", "responses", "gzip"],
"order": ["parser", "subscribers"],
"after": ["router"]
}
}
/middlewares/subscribers/index.js
module.exports = (strapi) => {
return {
initialize() {
strapi.app.use(async (ctx, next) => {
console.log("I have been called!");
await next();
});
},
};
};
Please help me to implement a middleware in strapi api.Thanks beforehand.

I just did what is written in the docs and I will do the same in my answer!
Initially I was reading from an older version of documentation which is mentioned by #Derrick Mehaffy. I found the correct docs url and read through its middleware implementation. [LINK TO THE DOCS] (Below explanations are obtained from docs)
------------------------------------------------------------------------------------
Examples: Create your custom middleware. [Path — ./middlewares/timer/index.js]
module.exports = strapi => {
return {
initialize() {
strapi.app.use(async (ctx, next) => {
const start = Date.now();
// I just add custom code that logs `I have been called!`
console.log('I have been called!');
await next();
const delta = Math.ceil(Date.now() - start);
ctx.set('X-Response-Time', delta + 'ms');
});
},
};
};
Enable the middleware in environments settings.
Load a middleware at the very first place - !You can do at the proper order
Path — ./config/middleware.js
module.exports = {
load: {
before: ["timer", "responseTime", "logger", "cors", "responses", "gzip"],
order: ["parser", ],
after: ["router", ],
},
settings: {
timer: {
enabled: true,
},
},
};
Basically I just copied and pasted the answer from docs, but it might be helpful for future use that's I have left the question

Related

How to Cache Additional Items in a Workbox Plugin

I'd like to write a Workbox plugin that intercepts a particular API call, does some logic (omitted from the code below) that determines other URL's that can be cached, then adds them to the runtime cache.
export class PreCachePlugin implements WorkboxPlugin {
fetchDidSucceed: WorkboxPlugin["fetchDidSucceed"] = async ({
response,
}) => {
if (response.ok) {
const clonedResponse = await response.clone();
const json = await clonedResponse.json();
const urls: string[] = getUrlsToCache(json); // Omitting this logic.
await Promise.all(urls.map(url => {
const response = await fetch(url);
// TODO: How do I fetch these URL's and put them in the cache configured
// below while still respecting the expiration.maxEntries setting below?
}));
}
}
}
I am using GenerateSW with workbox-webpack-plugin and adding my plugin:
new GenerateSW({
include: [/\.js$/, /\.json$/, /\.html$/],
runtimeCaching: [
{
urlPattern: ({ url }) => url.toString().endsWith("/foo"),
handler: "StaleWhileRevalidate",
options: {
cacheName: "Foo",
expiration: { maxEntries: 100 },
plugins: [
new PreCachePlugin(),
],
},
},
],
swDest: "./sw.js",
}
How do I fetch the above new URL's and put them in the configured runtime cache while still respecting the expiration.maxEntries settings?

Video as a background image not working in Gatsby PWA on iOS

I created a opt-in app for potential interims for our company, i worked with Gatsby and for now am quite satisfied with the result. I made it an Progressive Web App as that is fairly easy with the gatsby plugin.
The PWA works great on Android and shows the background video as expected, but on iOS the video doesn't show.
I updated all the packages and dependencies to the last versions but that doesn't change a thing. I tried googling the issue but got a lot of search results off people trying to let a PWA play video in the background when the app is closed (not my case).
{
resolve: `gatsby-plugin-manifest`,
options: {
name: `Afstuderen bij Arcady`,
short_name: `Afstuderen`,
start_url: `/`,
background_color: `#FFF`,
theme_color: `#00a667`,
display: `standalone`,
icon: `src/images/bear_green.png`,
},
},
'gatsby-plugin-offline',
And the content of the service worker
importScripts("workbox-v3.6.3/workbox-sw.js");
workbox.setConfig({modulePathPrefix: "workbox-v3.6.3"});
workbox.core.setCacheNameDetails({prefix: "gatsby-plugin-offline"});
workbox.skipWaiting();
workbox.clientsClaim();
/**
* The workboxSW.precacheAndRoute() method efficiently caches and responds to
* requests for URLs in the manifest.
*/
self.__precacheManifest = [
{
"url": "webpack-runtime-aec2408fe3a97f1352af.js"
},
{
"url": "app-5b624d17337895ddf874.js"
},
{
"url": "component---node-modules-gatsby-plugin-offline-app-shell-js-b97c345e19bb442c644f.js"
},
{
"url": "offline-plugin-app-shell-fallback/index.html",
"revision": "ac0d57f6ce61fac4bfa64e7e08d076c2"
},
{
"url": "0-d2c3040ae352cda7b69f.js"
},
{
"url": "component---src-pages-404-js-cf647f7c3110eab2f912.js"
},
{
"url": "static/d/285/path---404-html-516-62a-0SUcWyAf8ecbYDsMhQkEfPzV8.json"
},
{
"url": "static/d/604/path---offline-plugin-app-shell-fallback-a-30-c5a-BawJvyh36KKFwbrWPg4a4aYuc8.json"
},
{
"url": "manifest.webmanifest",
"revision": "5a580d53785b72eace989a49ea1e24f7"
}
].concat(self.__precacheManifest || []);
workbox.precaching.suppressWarnings();
workbox.precaching.precacheAndRoute(self.__precacheManifest, {});
workbox.routing.registerRoute(/(\.js$|\.css$|static\/)/, workbox.strategies.cacheFirst(), 'GET');
workbox.routing.registerRoute(/^https?:.*\.(png|jpg|jpeg|webp|svg|gif|tiff|js|woff|woff2|json|css)$/, workbox.strategies.staleWhileRevalidate(), 'GET');
workbox.routing.registerRoute(/^https?:\/\/fonts\.googleapis\.com\/css/, workbox.strategies.staleWhileRevalidate(), 'GET');
/* global importScripts, workbox, idbKeyval */
importScripts(`idb-keyval-iife.min.js`)
const WHITELIST_KEY = `custom-navigation-whitelist`
const navigationRoute = new workbox.routing.NavigationRoute(({ event }) => {
const { pathname } = new URL(event.request.url)
return idbKeyval.get(WHITELIST_KEY).then((customWhitelist = []) => {
// Respond with the offline shell if we match the custom whitelist
if (customWhitelist.includes(pathname)) {
const offlineShell = `/offline-plugin-app-shell-fallback/index.html`
const cacheName = workbox.core.cacheNames.precache
return caches.match(offlineShell, { cacheName }).then(cachedResponse => {
if (cachedResponse) return cachedResponse
console.error(
`The offline shell (${offlineShell}) was not found ` +
`while attempting to serve a response for ${pathname}`
)
return fetch(offlineShell).then(response => {
if (response.ok) {
return caches.open(cacheName).then(cache =>
// Clone is needed because put() consumes the response body.
cache.put(offlineShell, response.clone()).then(() => response)
)
} else {
return fetch(event.request)
}
})
})
}
return fetch(event.request)
})
})
workbox.routing.registerRoute(navigationRoute)
let updatingWhitelist = null
function rawWhitelistPathnames(pathnames) {
if (updatingWhitelist !== null) {
// Prevent the whitelist from being updated twice at the same time
return updatingWhitelist.then(() => rawWhitelistPathnames(pathnames))
}
updatingWhitelist = idbKeyval
.get(WHITELIST_KEY)
.then((customWhitelist = []) => {
pathnames.forEach(pathname => {
if (!customWhitelist.includes(pathname)) customWhitelist.push(pathname)
})
return idbKeyval.set(WHITELIST_KEY, customWhitelist)
})
.then(() => {
updatingWhitelist = null
})
return updatingWhitelist
}
function rawResetWhitelist() {
if (updatingWhitelist !== null) {
return updatingWhitelist.then(() => rawResetWhitelist())
}
updatingWhitelist = idbKeyval.set(WHITELIST_KEY, []).then(() => {
updatingWhitelist = null
})
return updatingWhitelist
}
const messageApi = {
whitelistPathnames(event) {
let { pathnames } = event.data
pathnames = pathnames.map(({ pathname, includesPrefix }) => {
if (!includesPrefix) {
return `${pathname}`
} else {
return pathname
}
})
event.waitUntil(rawWhitelistPathnames(pathnames))
},
resetWhitelist(event) {
event.waitUntil(rawResetWhitelist())
},
}
self.addEventListener(`message`, event => {
const { gatsbyApi } = event.data
if (gatsbyApi) messageApi[gatsbyApi](event)
})
I expect the iOS PWA (safari) to show the video as it does on Android but instead it gives a grey screen.
I hope some one can help me out or point me in the right direction.
How big is your video ?
Last time I checked, iOS has a limit of 50MB for the cache of a PWA, so if your video is bigger than 50MB, that may be the reason it works only on Android (which doesn't have such restrictions).
I found this blog post that helped me fix this problem
To add Range request handling to gatsby-plugin-offline, I added a script called range-request-handler.js with the following:
// range-request-handler.js
// Define workbox globally
importScripts('https://storage.googleapis.com/workbox-cdn/releases/5.0.0/workbox-sw.js');
// Bring in workbox libs
const { registerRoute } = require('workbox-routing');
const { CacheFirst } = require('workbox-strategies');
const { RangeRequestsPlugin } = require('workbox-range-requests'); // The fix
// Add Range Request support to fetching videos from cache
registerRoute(
/(\.webm$|\.mp4$)/,
new CacheFirst({
plugins: [
new RangeRequestsPlugin(),
],
})
);
Then in my gatsby-config.js I configured the plugin to append the above script:
// gatsby-config.js
module.exports = {
// ...
plugins: [
// ...plugins
{
resolve: 'gatsby-plugin-offline',
options: {
appendScript: require.resolve('./range-request-handler.js'),
},
},
// ...plugins
],
// ...
};
Videos now work in the Safari browser for me. If there is a better way to implement this, I am all ears. For now it works as intended

How to get model on onInit?

In manifest.json, I have following model definition:
{
"sap.ui5": {
"models": {
"SalesInvoices": {
"type": "sap.ui.model.odata.v2.ODataModel",
"settings": {
"defaultOperationMode": "Server",
"defaultCountMode": "Request"
},
"dataSource": "ZAM_SALES_STATISTICS_CDS",
"preload": true
}
}
}
}
As you can see, SalesInvoices is connected to the OData service.
Now on the onInit function in the controller, I am trying to get Metadata from OData as following:
{ // Controller
onInit: function() {
const oPayerModel = this.getView().getModel("SalesInvoices");
console.log(oPayerModel.getMetadata());
setTimeout(() => {
const oPayerModel = this.getView().getModel("SalesInvoices");
console.log(oPayerModel.getMetadata());
}, 600);
},
// ...
}
As you can see, I have to delay to get the OData instance.
setTimeout is not recommended to use in SAPUI5, how can I do it better?
You can avoid setTimeout, as mentioned in this answer, by using the v2.ODataModel API metadataLoaded instead which returns a promise. The promise is fulfilled once the service metadata is loaded successfully.
onInit: async function() {
const oPayerModel = this.getOwnerComponent().getModel("SalesInvoices");
try {
await oPayerModel.metadataLoaded(true);
const oServiceMetadata = oPayerModel.getServiceMetadata(); // NOT .getMetadata()
// ...
} catch (oError) {/* ... */}
},
About the model being undefined in onInit, here are answers with better explanations:
Nabi's answer
My other answer
I think you are running into the issue I reported some time ago: Component + default OData model: this.getView().getModel() returns undefined in onInit() of controllers:
don't use this.getView().getModel() directly in onInit()
instead use this.getOwnerComponent().getModel() in onInit()
anywhere else in the controller you can use this.getView().getModel()
In your case you should be fine changing the suggestion of #boghyon slightly:
onInit: function() {
const oPayerModel = this.getOwnerComponent().getModel("SalesInvoices");
oPayerModel.metadataLoaded().then(this.onMetadataLoaded.bind(this, oPayerModel));
},
onMetadataLoaded: function(myODataModel) {
const metadata = myODataModel.getServiceMetadata(); // NOT .getMetadata()
// ...
},
This way you can get rid of setTimeout(...).

How to implement logout in apollo-client

The authentication example found in the apollo docs is as follows:
https://github.com/apollostack/core-docs/blob/master/source/network.md#afterware
```
networkInterface.useAfter([{
applyAfterware({ response }, next) {
if (response.status === 401) {
logout();
}
next();
}
}]);
```
But what does the logout function look like? I'm unclear how to actually dispatch this as an action if it is indeed an action. The docs are really unclear on this and I haven't been able to find any example code anywhere. Any thoughts?
Look at my implementation:
const withUserHandlers = withHandlers({
logout: props => () => {
const { client, navigation, setUser } = props; // eslint-disable-line
removeTokenFromUse(client);
client.resetStore();
setUser(null);
const resetAction = NavigationActions.reset({
index: 0,
actions: [ NavigationActions.navigate({ routeName: 'Login' }) ],
});
navigation.dispatch(resetAction);
}
});

executing query via middleware

While using strongloop loopback I want check the database for accesstoken and username existence in every request.
So i am making a middleware code:
module.exports = function() {
return function xAuth(req, res, next) {
console.log(req);
};
};
I have added it to :initial" middleware json
"initial": {
"compression": {},
"cors": {
"params": {
"origin": true,
"credentials": true,
"maxAge": 86400
}
},
"./middleware/trumptAuth": {},
"helmet#xssFilter": {},
"helmet#frameguard": {
"params": [
"deny"
]
},
"helmet#hsts": {
"params": {
"maxAge": 0,
"includeSubdomains": true
}
},
"helmet#hidePoweredBy": {},
"helmet#ieNoOpen": {},
"helmet#noSniff": {},
"helmet#noCache": {
"enabled": false
}
}
i want to execute an sql query here but i have no idea on how can i do that, I probably just need "app" variable access or directly "dataSource" access.
Any help is appreciated.
From the docs, "Using variables in middleware" (https://docs.strongloop.com/display/LB/Defining+middleware#Definingmiddleware-Usingvariablesinvalues), it looks like you can pass any part of the app object using this syntax: ${var}. The datasource property exists under the core app object, so you should be able to pass it in that way.
To access "app" variable from middleware use
app.use(function(req, res, next) {
var app = req.app;
...
});
To access "dataSource" variable from middleware use
app.use(function(req, res, next) {
var dataSource = app.datasources.db;
...
});
To access an specific model from middleware use
app.use(function(req, res, next) {
var app = req.app;
var modelName = app.model.modelName;
...
});
for more methods/sources use this documentation link
https://docs.strongloop.com/display/public/LB/Working+with+LoopBack+objects

Resources