Task: Offline first;
I have some pages (which end in ?ind${number}.html and these pages are not caching. I resolve this problem with passing a second argument onto caches.match(event.request.url))
let CACHE = 'network-or-cache-v1';
self.addEventListener('install', function(event){
let offlinePage = new Request('/index.html');
self.skipWaiting();
event.waitUntil(
caches
.open(CACHE)
.then((cache) => cache.addAll(arrayToCache))
);
});
self.addEventListener('fetch', function(event){
const url = new URL(event.request.url);
console.log(url);
caches.match(event.request.url, {ignoreSearch: true}).then(function(response) {
return response || fetch(event.request);
})
event.waitUntil(update(event.request));
});
You could try creating a new request object and then pass it in to caches.match like
const requestNew = new Request(event.request, {ignoreSearch: 'true' });
caches.match(requestNew).then(///)
Related
Imagine a case where an editor adds a “Latest Products” component to a page using dynamic zone: they add a title, a summary, and then the latest products will automatically be fetched to be available in the response. How can I add this data to the response of the component?
I know we can override the response for content types using a custom controller, but I can't find anything for how to modify the response for a component.
Maybe there's an alternative approach I haven't thought of, but coming from a Drupal preprocess-everything background this is all I can think of.
Any help appreciated!
I'm sure this isn't the best way, but I created a service for components that can be used in the content type controller to modify the response. Any improvements appreciated!
/api/custom-page/controllers/custom-page.js
'use strict';
/**
* custom-page controller
*/
const { createCoreController } = require('#strapi/strapi').factories;
module.exports = createCoreController('api::custom-page.custom-page', ({ strapi }) => ({
async find(ctx) {
const componentService = strapi.service('api::components.components');
let { data, meta } = await super.find(ctx);
data = await Promise.all(data.map(async (entry, index) => {
if(entry.attributes.sections){
await Promise.all(entry.attributes.sections.map(async (section, index) => {
const component = await componentService.getComponent(section);
entry.attributes.sections[index] = component;
}));
}
return entry;
}));
return { data, meta };
},
}));
/components/services/components.js
'use strict';
/**
* components service.
*/
module.exports = () => ({
getComponent: async (input) => {
// Latest products
if(input.__component === 'sections.latest-products'){
input.products = 'customdatahere';
}
return input;
}
});
I have a question about caching in PWA.
I would like to let user choose a list to cache. For example:
User creating few lists, and then he choose one, to save for offline.
When he is offline he can only open 2 views:
smth like "u are offline, do you want to open saved list?" (if it exist)
View with saved list.
At this moment, I am caching all views that user visited, but can't cache views with dynamic data.
I'm using PWA.essentials to do PWA.
services.AddProgressiveWebApp(new PwaOptions
{
RegisterServiceWorker = true,
RegisterWebmanifest = false,
Strategy = ServiceWorkerStrategy.NetworkFirst,
RoutesToPreCache = "/, /Home/Offline, /Home/Saved_list",
OfflineRoute="Offline.html"
});
I have created manifest.json. When im using dev mode in chrome, I can see that at this moment, I'm caching all views execpt views with more complicated path (like /Controller/View/something).
I'm saving list, that user choose in a "Offline.json" file, that is cached too,but when user changes list to save, file "offline.json" is still no updated. I mean my PWA doesn't replace it with new one.
So I have a question about how can I save dynamic list to browser cache, and then set offline route to it.
Service Worker Created by pwa.essentials:
(function () {
'use strict';
// Update 'version' if you need to refresh the cache
var version = 'v1.0::NetworkFirst';
var offlineUrl = "Offline.html";
// Store core files in a cache (including a page to display when offline)
function updateStaticCache() {
return caches.open(version)
.then(function (cache) {
return cache.addAll([
offlineUrl,
'/','/Home','/Generate/Select_mode'
]);
});
}
function addToCache(request, response) {
if (!response.ok)
return;
var copy = response.clone();
caches.open(version)
.then(function (cache) {
cache.put(request, copy);
});
}
self.addEventListener('install', function (event) {
event.waitUntil(updateStaticCache());
});
self.addEventListener('activate', function (event) {
event.waitUntil(
caches.keys()
.then(function (keys) {
// Remove caches whose name is no longer valid
return Promise.all(keys
.filter(function (key) {
return key.indexOf(version) !== 0;
})
.map(function (key) {
return caches.delete(key);
})
);
})
);
});
self.addEventListener('fetch', function (event) {
var request = event.request;
// Always fetch non-GET requests from the network
if (request.method !== 'GET') {
event.respondWith(
fetch(request)
.catch(function () {
return caches.match(offlineUrl);
})
);
return;
}
event.respondWith(
fetch(request)
.then(function (response) {
// Stash a copy of this page in the cache
addToCache(request, response);
return response;
})
.catch(function () {
return caches.match(request)
.then(function (response) {
return response || caches.match(offlineUrl);
})
.catch(function () {
if (request.headers.get('Accept').indexOf('image') !== -1) {
return new Response('<svg role="img" aria-labelledby="offline-title" viewBox="0 0 400 300" xmlns="http://www.w3.org/2000/svg"><title id="offline-title">Offline</title><g fill="none" fill-rule="evenodd"><path fill="#D8D8D8" d="M0 0h400v300H0z"/><text fill="#9B9B9B" font-family="Helvetica Neue,Arial,Helvetica,sans-serif" font-size="72" font-weight="bold"><tspan x="93" y="172">offline</tspan></text></g></svg>', { headers: { 'Content-Type': 'image/svg+xml' } });
}
});
})
);
});
})();
You are saving the user selected pages’ list at server side in offline.json and you are caching that file too. As per working of PWA cached resources are served from cache and not from server until cache is removed/cleared.
You can take 2 approach.
Whenever user adds to the offline.json you need enforce service worker to update the offline file.
You can simply store the user selecting in local storage and from there you add pages to cache list.
In defaut registration API, I need to uplaod the image of user in registration API. So how could I manage it ? I'm sending in a formData and it works fine. I can see (binary) in network.
I tried to add image field and it works in admin panel but from API side I tried to send the file in key names like files, profileImage.
I didn't get the error in res. I got success in res.
Issue: When I reload the admin panel, I didn't get user's profile image.
Try this way. I used in react and it works fine for me.
signUpHandler = () => {
console.log("SignUp data ::: ", this.state);
let data = {
username: this.state.signUpForm.username.value,
phone: this.state.signUpForm.phone.value,
email: this.state.signUpForm.email.value,
password: this.state.signUpForm.password.value
}
axios.post('http://0.0.0.0:1337/auth/local/register', data)
.then(res => {
console.log(res);
return res.data.user.id;
})
.then(refId =>{
const data = new FormData();
data.append('files', this.state.selectedFile);
data.append('refId', refId);
data.append('ref', 'user');
data.append('source', 'users-permissions');
data.append('field', 'profileImage');
return axios.post('http://0.0.0.0:1337/upload', data)
})
.then(res =>{
console.log(res);
alert("You registered successfully...");
this.props.history.push('/login');
})
.catch(error =>{
console.log(error);
})
}
First, you will have to customize your user-permission
To do so, you will have to understand this concept: https://strapi.io/documentation/3.0.0-beta.x/concepts/customization.html
Then you will have to find the function you want to update - in your case, the register function.
And tada here it is https://github.com/strapi/strapi/blob/master/packages/strapi-plugin-users-permissions/controllers/Auth.js#L383.
So you will have to create ./extensions/users-permissions/controllers/Auth.js with the same content as the original file.
Then you will have to add
const { parseMultipartData, sanitizeEntity } = require('strapi-utils');
const uploadFiles = require('strapi/lib/core-api/utils/upload-files');
on the top of your file.
And in your function use this
const { data, files } = parseMultipartData(ctx); to parse data and files.
Then you will have to replace ctx.request.body by data to make sure to use the correct data.
After that you will have to add this after the user creation line
https://github.com/strapi/strapi/blob/master/packages/strapi-plugin-users-permissions/controllers/Auth.js#L510
if (files) {
// automatically uploads the files based on the entry and the model
await uploadFiles(user, files, { model: strapi.plugins['users-permissions'].models.user })
}
Solution for Strapi v4:
var myHeaders = new Headers();
myHeaders.append("Authorization", "Bearer XXXX");
var formdata = new FormData();
formdata.append("files", fileInput.files[0], "XXX.png");
formdata.append("refId", "46");
formdata.append("field", "image");
formdata.append("ref", "plugin::users-permissions.user");
var requestOptions = {
method: 'POST',
headers: myHeaders,
body: formdata,
redirect: 'follow'
};
fetch("http://localhost:1337/api/upload", requestOptions)
.then(response => response.text())
.then(result => console.log(result))
.catch(error => console.log('error', error));
My Service Worker:
importScripts('https://storage.googleapis.com/workbox-
cdn/releases/3.0.0/workbox-sw.js');
//Use Workbox Precache for our static Assets
workbox.precaching.precacheAndRoute([]);
console.log('this is my custom service worker');
//Create articles Cache from online resources
const onlineResources = workbox.strategies.networkFirst({
cacheName: 'articles-cache',
plugins: [
new workbox.expiration.Plugin({
maxEntries: 50,
}),
],
});
workbox.routing.registerRoute('https://newsapi.org/(.*)', args => {
return onlineResources.handle(args);
});
The precache cache works but the onlineResources Cache is never created.
A look at my file structure:
So I don't think scope is an issue even though I cant see clients in my service worker on Chrome dev tools.
Lastly here is my app.js file:
//main populates main tags in indexpage
const main = document.querySelector('main');
//this populates the source dropdown menu with sources
const sourceSelector = document.querySelector('#sourceSelector');
//set default source so page loads this
const defaultSource = 'bbc-news';
//on window load call update news and when update
window.addEventListener('load', async e => {
updateNews();
await updateSources();
sourceSelector.value = defaultSource;
//when sourceSelector is changed update the news with the new source
sourceSelector.addEventListener('change',e =>{
updateNews(e.target.value);
});
//checks for serviceWorker in browser
if('serviceWorker'in navigator){
try{
//if there is register it from a path
navigator.serviceWorker.register('/sw.js');
console.log('registered!');
} catch(error){
console.log('no register!',error);
}
}
});
async function updateNews(source= defaultSource){
//response awaits a fetch of the news API call
const res = await fetch(`https://newsapi.org/v2/top-headlines?sources=${source}&apiKey=82b0c1e5744542bdb8c02b61d6499d8f`);
const json = await res.json();
//fill the html with the retrieved json articles
main.innerHTML = json.articles.map(createArticle).join('\n');
}
//Update the news source
async function updateSources(){
const res = await fetch(`https://newsapi.org/v2/sources?apiKey=82b0c1e5744542bdb8c02b61d6499d8f`);
const json = await res.json();
//Whatever source the sourceSelector picks gets mapped and we take the id of it - It is used with updateNews();
sourceSelector.innerHTML = json.sources
.map(src=>`<option value="${src.id}">${src.name}</option>`).join('\n');
}
function createArticle(article){
return ` <div class="article">
<a href="${article.url}">
<h2>${article.title}</h2>
<img src="${article.urlToImage}">
<p>${article.description}</p>
</a>
</div>
`;
}
App.js plugs into newsAPI and outputs the JSON to the pages HTML.
When you register the route you seem to be trying to use a regex as a string. I think it is literally interpreting the route as a string that includes .*. Instead try the regex /^https:\/\/newsapi\.org/ which per the docs will match from the beginning of the url.
im having this issue where i send a request to the API to retrieve all users, the login function is called(index.vue) when called it tries to go to api/users/all which in this case should return all the users in that collection.
using Postman the API returns the correct results and if i console.log the output in the routeUsers before i send the response back, it outputs all the correct data to the console
when it returns to index.vue, the response status code is 0.
ive had a look online and some things are mentioning about CORS Headers but i dont think thats applicable to me and other things about the response has been cancelled,
can anyone shed some light on this for me and help me try to fix it?!
API main.js
var app = express();
var users = require('./routes/routeUsers');
app.use('/users', users);
module.exports = app;
api/models/users.js
var db = require('../Utilities/db')
module.exports.all = function(cb) {
var collection = db.get().collection('users')
collection.find().toArray(function(err, docs) {
cb(err, docs)
})
}
api/routes/routeUsers.js
var express = require('express')
, router = express.Router()
var user = require('../models/users');
router.get('/all', function(req, res) {
user.all(function(err, users) {
res.send(users);
})
})
Index.vue
export default {
data: function () {
return {
username: '',
password: '',
users: []
}
},
methods: {
login: function() {
Vue.http.get('/api/users/all').then((response) => {
console.log("SUCCESS",response);
this.users = response.body;
console.log(users);
}, function (error) {
console.log("Error", error.status); // handle error
});
}
}
};
The issue was that the inputs were in a form tag. removed Form tag and worked fine.