PWA problem with Vue3+service worker+keep-alive - progressive-web-apps

I have a problem with Vue3+service worker+keep-alive.
I use keep-live in template
<q-page-container>
<router-view v-slot="{ Component }">
<keep-alive :include="['WorkPage']">
<component :is="Component" :key="$route.fullPath"/>
</keep-alive>
</router-view>
</q-page-container>
create queue
createWorkQueue = new Queue('createWorkQueue', {
onSync: async ( {queue} ) => {
let entry
while (entry = await queue.shiftRequest()) {
try {
await fetch(entry.request);
const channel = new BroadcastChannel('sw-messages-work');
channel.postMessage({msg: 'offline-work-uploaded'});
} catch (error) {
await queue.unshiftRequest(entry);
throw error;
}
}
}
})
addEventListener('fetch'
self.addEventListener('fetch', (event) => {
if (event.request.url.endsWith('/api/ins_new_work')) {
const bgSyncLogic = async () => {
try {
const response = await fetch(event.request.clone())
return response
} catch (error) {
await createWorkQueue.pushRequest({request: event.request})
return error
}
}
event.respondWith(bgSyncLogic())
}
})
when in offline I send form - createWorkQueue.pushRequest hangs to 5 minutes
if I delete from keep-alive - WorkPage - then pushRequest works well
but I need keep-alive page. How can I solve this?

I found!!
I use IndexedDB library and for show offline message I read from DB information
const db = await openDB('workbox-background-sync')
but in first time - table 'requests' don't create
I insert next code
const db = await openDB('workbox-background-sync', undefined, { upgrade(db) { db.createObjectStore('requests') }})
and works well

Related

Flutter PWA service worker fails to successfully register

I've built my flutter app and have the pwa version of it, hosted it on windows server IIS 10 with a valid ssl certificate on subdomain like Https://pwa.mydomain.com, but when I visit the page, it doesn't show the Install app button on chrome. How can I solve this?
What I've done so far:
Removed the references to files that did not exist anymore in the project.
Here is my service worker:
'use strict';
const MANIFEST = 'flutter-app-manifest';
const TEMP = 'flutter-temp-cache';
const CACHE_NAME = 'flutter-app-cache';
const RESOURCES = {
".dart_tool/package_config.json": "23791ac6df06edd3cb7ca99ab6949a5a",
".dart_tool/package_config_subset": "8453ab6a2adbaeda61d2d58965841223",
".dart_tool/version": "a13ed25c1389038783693bec771e71f6",
"assets/assets/images/svgIcon/wifiOff.svg": "ca97536b42e250214b667a606455a58f",
"assets/FontManifest.json": "fba83cca6cbe4083e6cc0498e15a1a6f",
"assets/fonts/MaterialIcons-Regular.otf": "4e6447691c9509f7acdbf8a931a85ca1",
"assets/NOTICES": "0e94f133a57e1e05b650363a497d95b0",
"assets/packages/cupertino_icons/assets/CupertinoIcons.ttf": "6d342eb68f170c97609e9da345464e5e",
"favicon.png": "19e188c1100dbd706d3d81309d478996",
"icons/launcher_logo192.png": "ea22e4258d33a9a184a426b38f3e6f46",
"icons/launcher_logo512.png": "19e188c1100dbd706d3d81309d478996",
"index.html": "fde3d71f9f8a4623bd98e956040169d2",
"/": "fde3d71f9f8a4623bd98e956040169d2",
"lib/main.dart": "5515740aeb62c25aa7692184d00b90b1",
"lib/src/qr_code_scanner_web.dart": "50b70d762e80fedd228974e786e8f38a",
"lib/src/qr_code_scanner_web_impl.dart": "a0f12d21cc75aefe84f6fbf9c1e0307e",
"lib/src/qr_code_scanner_web_impl_no_web.dart": "11e3ab6339d41ded6e650c96120e7560",
"main.dart.js": "6161190dc35f953037fb5c0e7db4f0e6",
"manifest.json": "a281fd45bea620b437523746b29b93d2",
"pubspec.lock": "6c06cc012f6524824b77f7286b6e256e",
"pubspec.yaml": "b36c6ef43dcaa5a79fc412a8368306cf",
"version.json": "cbca785ad23c1984a075073af0a61003"
//Removed for brevity
};
// The application shell files that are downloaded before a service worker can
// start.
const CORE = [
"/",
"main.dart.js",
"index.html",
"assets/NOTICES",
"assets/AssetManifest.json",
"assets/FontManifest.json"];
// During install, the TEMP cache is populated with the application shell files.
self.addEventListener("install", (event) => {
self.skipWaiting();
return event.waitUntil(
caches.open(TEMP).then((cache) => {
return cache.addAll(
CORE.map((value) => new Request(value, {'cache': 'reload'})));
})
);
});
// During activate, the cache is populated with the temp files downloaded in
// install. If this service worker is upgrading from one with a saved
// MANIFEST, then use this to retain unchanged resource files.
self.addEventListener("activate", function(event) {
return event.waitUntil(async function() {
try {
var contentCache = await caches.open(CACHE_NAME);
var tempCache = await caches.open(TEMP);
var manifestCache = await caches.open(MANIFEST);
var manifest = await manifestCache.match('manifest');
// When there is no prior manifest, clear the entire cache.
if (!manifest) {
await caches.delete(CACHE_NAME);
contentCache = await caches.open(CACHE_NAME);
for (var request of await tempCache.keys()) {
var response = await tempCache.match(request);
await contentCache.put(request, response);
}
await caches.delete(TEMP);
// Save the manifest to make future upgrades efficient.
await manifestCache.put('manifest', new Response(JSON.stringify(RESOURCES)));
return;
}
var oldManifest = await manifest.json();
var origin = self.location.origin;
for (var request of await contentCache.keys()) {
var key = request.url.substring(origin.length + 1);
if (key == "") {
key = "/";
}
// If a resource from the old manifest is not in the new cache, or if
// the MD5 sum has changed, delete it. Otherwise the resource is left
// in the cache and can be reused by the new service worker.
if (!RESOURCES[key] || RESOURCES[key] != oldManifest[key]) {
await contentCache.delete(request);
}
}
// Populate the cache with the app shell TEMP files, potentially overwriting
// cache files preserved above.
for (var request of await tempCache.keys()) {
var response = await tempCache.match(request);
await contentCache.put(request, response);
}
await caches.delete(TEMP);
// Save the manifest to make future upgrades efficient.
await manifestCache.put('manifest', new Response(JSON.stringify(RESOURCES)));
return;
} catch (err) {
// On an unhandled exception the state of the cache cannot be guaranteed.
console.error('Failed to upgrade service worker: ' + err);
await caches.delete(CACHE_NAME);
await caches.delete(TEMP);
await caches.delete(MANIFEST);
}
}());
});
// The fetch handler redirects requests for RESOURCE files to the service
// worker cache.
self.addEventListener("fetch", (event) => {
if (event.request.method !== 'GET') {
return;
}
var origin = self.location.origin;
var key = event.request.url.substring(origin.length + 1);
// Redirect URLs to the index.html
if (key.indexOf('?v=') != -1) {
key = key.split('?v=')[0];
}
if (event.request.url == origin || event.request.url.startsWith(origin + '/#') || key == '') {
key = '/';
}
// If the URL is not the RESOURCE list then return to signal that the
// browser should take over.
if (!RESOURCES[key]) {
return;
}
// If the URL is the index.html, perform an online-first request.
if (key == '/') {
return onlineFirst(event);
}
event.respondWith(caches.open(CACHE_NAME)
.then((cache) => {
return cache.match(event.request).then((response) => {
// Either respond with the cached resource, or perform a fetch and
// lazily populate the cache.
return response || fetch(event.request).then((response) => {
cache.put(event.request, response.clone());
return response;
});
})
})
);
});
self.addEventListener('message', (event) => {
// SkipWaiting can be used to immediately activate a waiting service worker.
// This will also require a page refresh triggered by the main worker.
if (event.data === 'skipWaiting') {
self.skipWaiting();
return;
}
if (event.data === 'downloadOffline') {
downloadOffline();
return;
}
});
// Download offline will check the RESOURCES for all files not in the cache
// and populate them.
async function downloadOffline() {
var resources = [];
var contentCache = await caches.open(CACHE_NAME);
var currentContent = {};
for (var request of await contentCache.keys()) {
var key = request.url.substring(origin.length + 1);
if (key == "") {
key = "/";
}
currentContent[key] = true;
}
for (var resourceKey of Object.keys(RESOURCES)) {
if (!currentContent[resourceKey]) {
resources.push(resourceKey);
}
}
return contentCache.addAll(resources);
}
// Attempt to download the resource online before falling back to
// the offline cache.
function onlineFirst(event) {
return event.respondWith(
fetch(event.request).then((response) => {
return caches.open(CACHE_NAME).then((cache) => {
cache.put(event.request, response.clone());
return response;
});
}).catch((error) => {
return caches.open(CACHE_NAME).then((cache) => {
return cache.match(event.request).then((response) => {
if (response != null) {
return response;
}
throw error;
});
});
})
);
}
This is the error I'm getting:
Uncaught (in promise) TypeError: Failed to execute 'addAll' on 'Cache': Request failed
I'm curious whether this error has something to do with CORS or not.
Thanks.

Why is Flutter Dart application throwing an XMLHttpRequest error when test programme and POSTMAN do not?

I have an application that was working doing http.get requests on an Ubuntu virtual machine hosting a postgres DB and an API using Hapi/node. The VM disk become corrpupted and following the rebuild, the http.get now throws an XMLHttpRequest error. However, a test programme doing the same request works fine and a test GET using POSTMAN works fine as well. I'm stumped as to why this is the case.
The code that is throwing the error is as follows (the getScores() function):
import 'package:noxo/functions.dart' as func;
import 'dart:convert';
import 'package:http/http.dart' as http;
class DataManager {
Map<dynamic, dynamic> results = {}; // Return values from DB parameter query
double difficulty = 5; // Level of difficulty (1- easiest to 5-hardest)
// Address of API for data requests
final _apiAddress = 'http://192.168.1.201:4044/';
bool _httpAvailable = true; // Assume that http is available
int getHumanScore() {
func.debugPrint('httpAvailable is $_httpAvailable');
if (_httpAvailable && results.isNotEmpty) {
func.debugPrint('Returning HUMAN score');
return results['humanwin'];
} else {
return 0;
}
}
int getDrawScore() {
if (_httpAvailable && results.isNotEmpty) {
return results['draws'];
} else {
return 0;
}
}
int getComputerScore() {
if (_httpAvailable && results.isNotEmpty) {
return results['computerwin'];
} else {
return 0;
}
}
void setDifficulty(double value) {
func.debugPrint('Set difficulty = $value');
difficulty = value;
}
double getDifficulty() {
func.debugPrint('Get difficulty is $difficulty');
return difficulty;
}
void getScores(Function() updateScores) async {
if (_httpAvailable) {
// If we haven't had a previous http error - read the scores
try {
dynamic _parsedAddress = Uri.parse(_apiAddress);
final response = await http.get(_parsedAddress);
func.debugPrint(
'doing getScores. Address = $_apiAddress. statusCode = ${response.statusCode}');
Map decodedResponse = jsonDecode(response.body) as Map;
if (response.statusCode == 200) {
func.debugPrint('getScores response: $decodedResponse');
results = decodedResponse;
updateScores(); // Update scores on main.dart
} else {
throw Exception('Unable to fetch products from the REST API');
}
} catch (e) {
func.debugPrint('getScores. Error is $e.');
_httpAvailable = false; // Disable http checks because we had an error
results =
{}; // Return an empty map if the internet connection is not available
}
}
}
// Put data about scores into the database
Future<void> putScores(String resultPath) async {
// Only try to put the scores if http is available
if (_httpAvailable) {
try {
String address = '$_apiAddress$resultPath';
func.debugPrint('http address: $address');
var response = await http.post(Uri.parse(address));
func.debugPrint('http response: ${response.body.toString()}');
// Check for sucess, throw an error if it didn't work
if (response.statusCode == 200 ||
response.statusCode == 201 ||
response.statusCode == 204) {
return;
} else {
throw Exception(
'Unable to update results from the REST API. Status Code: ' +
response.statusCode.toString());
}
} catch (e) {
_httpAvailable = false; // Disable http requests
}
}
}
}
The output when "getScores()" is called is:
getScores. Error is XMLHttpRequest error..
The Hapi interface code is as follows:
'use strict';
const Hapi = require('#hapi/hapi');
const { options } = require('#hapi/hapi/lib/cors');
const Inert = require('#hapi/inert');
const HapiPostgresConnection = require('hapi-postgres-connection');
const path = require('path');
const debug = true;
const init = async () => {
const server = Hapi.server({
port: 4044,
host: '192.168.1.201',
//host: '0.0.0.0',
routes: {
cors: false
/*
{
origin: ['192.168.*'], // an array of origins or 'ignore'
headers: ['Authorization'], // an array of strings - 'Access-Control-Allow-Headers'
exposedHeaders: ['Accept'], // an array of exposed headers - 'Access-Control-Expose-Headers',
additionalExposedHeaders: ['Accept'], // an array of additional exposed headers
maxAge: 60,
credentials: false // boolean - 'Access-Control-Allow-Credentials'
*/
}
});
await server.register([{
plugin: HapiPostgresConnection
},
{
plugin: Inert
}]);
server.route({
method: 'GET',
path: '/',
handler: async function (request, h) {
let id = '1';
let statement = `SELECT * FROM scores WHERE id = ${id}`;
debugPrint(`Doing GET. Statement = ${statement}`);
try {
const result = await request.pg.client.query(statement);
debugPrint(`After GET. Result = ${result}. Response = ${h.response()}`);
return h.response(result.rows[0]);
} catch (err) {
console.log(err);
}
}
});
server.route({
method: 'GET',
path: '/noxo',
handler: (request, h) => {
return h.file('/home/mike/programming/noxo/index.html');
}
});
server.route({
method: 'GET',
path: '/fwebtest',
handler: (request, h) => {
return h.file('index.html', options [{confine: false}]);
},
options: {
files: {
relativeTo: path.join(__dirname, 'fwebtest')
},
},
});
server.route({
method: 'GET',
path: '/webtest',
handler: (request, h) => {
return h.file('./webtest/index.html', options [{confine: false}])
}
});
server.route({
method: ['PUT', 'POST'],
path: '/',
handler: async function (request, h) {
let statement = 'update scores set';
var jsonData = request.payload;
if (jsonData.hasOwnProperty('id')) {
delete jsonData.id;
}
var first = true
for (var key of Object.keys(jsonData)) {
if (!first) {
statement = statement + ",";
}
statement = statement + ' ' + key + ' = ' + jsonData[key];
first = false;
}
statement = statement + ' where id = 1'
debugPrint(`Doing PUT. Statement = ${statement}`);
try {
const result = await request.pg.client.query(statement);
return h.response(result.rows[0]);
} catch (err) {
console.log(err);
}
}
});
await server.start();
console.log('Server running on %s', server.info.uri);
};
function buildStatement(element, index, array) {
if (index != 'id') {
statement = statement + index + ' = ' + value + ' ';
}
return this;
}
function debugPrint(value){
if (debug == true){
console.log(value);
}
}
process.on('unhandledRejection', (err) => {
console.log(err);
process.exit(1);
});
init();
The test dart programme, which works, is as follows:
import 'dart:convert';
import 'package:http/http.dart' as http;
// Address of API for data requests
final _apiAddress = 'http://192.168.1.201:4044/';
// Global variables
bool _httpAvailable = true; // Has http conect worked? Assume yes.
const bool debug = true; // Global bebug print variable.
void main() async {
// Get the scores from the API
Map scores = await _getScores();
}
// Function to get the scores using http command
Future<Map> _getScores() async {
debugPrint('Doing GET...');
dynamic response;
Map decodedResponse = {};
try {
dynamic _parsedAddress = Uri.parse(_apiAddress);
response = await http.get(_parsedAddress);
decodedResponse = jsonDecode(response.body) as Map;
} catch (e) {
debugPrint('getScores. Error is $e.');
_httpAvailable = false; // Disable http checks because we had an error
decodedResponse =
{}; // Return an empty map if the internet connection is not available
}
if (response.statusCode == 200) {
debugPrint('response.body:');
debugPrint(response.body);
debugPrint('GET successful... code: ' + response.statusCode.toString());
debugPrint(' ');
return decodedResponse;
} else {
throw Exception('Unable to fetch products from the REST API. Error code: ' +
response.statusCode.toString());
}
}
debugPrint(String message) {
if (debug == true) {
print(message);
}
}
The output from this programme is:
D:\Sync\Programming\Flutter\http_get>dart run
Building package executable...
Built http_test:http_test.
Doing GET...
response.body:
{"id":1,"humanwin":3,"draws":0,"computerwin":0}
GET successful... code: 200
The POSTMAN results are:
I assume that this is a CORS issue from what I have read about XMLHttpRequest errors, but believe I have disabled CORS in the Hapi interface. The application class, test programme and POSTMAN test all pre-date the Ubuntu VM rebuild so the only thing that has changed is the VM. The Hapi code was backed up so that shouldn't have changed either, although I can't quite remember when I took the last backup.
Does anyone have any ideas about why the test programme and POSTMAN work, but my DataManager class in my application doesn't?
In the routes definition for Hapi, cors needs to be set to true as below.
const server = Hapi.server({
port: xxxx,
host: 'xxx.xxx.xxx.xxx',
routes: {
cors: true
}
});

Post Api not return any response in nest js

I use nestjs and psql and I want upload files and save the url in the database . when I run the api , data save on db but it doesn’t return any response .
this is my service:
async uploadFiles(files){
if (!files) {
throw new HttpException(
{
errorCode: UploadApplyOppErrorEnum.FileIsNotValid,
message: UploadApplyOppMsgEnum.FileIsNotValid,
},
HttpStatus.UNPROCESSABLE_ENTITY,
);
}
const filedata = OrderFilesData(files);
return filedata.map(async(filePath) => {
let orderFile = new OrderFile();
orderFile.fileUrl = filePath.fileUrl;
orderFile.type = filePath.fileType;
try {
let result = await this.orderFileRepository.save(orderFile);
return await result
} catch (error) {
throw new BadRequestException(error.detail);
}
});
}
and this is my controller
#UploadOrderFilesDec()
#Post('upload')
uploadFiles(#UploadedFiles() files){
return this.ordersService.uploadFiles(files);
}
You can't return an array of async methods without using Promise.all(), otherwise the promises haven't resolved yet. You can either use return Promise.all(fileData.map(asyncFileMappingFunction)) or you can use a regular for loop and await over the results.

about vscode api for executeCommand("explorer.newFolder")

I want callback for after newFolder,
executeCommand("explorer.newFolder").then(value => console.log(value))
but The value is not the value of folderName.
I analyzed the code of the command you executed.
The explorer.newFolder command is defined as NEW_FOLDER_COMMAND_ID, and is connected with the openExplorerAndCreate handler. (The result is received through the handler of CommndsRegistry.)
The return of the openExplorerAndCreateis a callback function (Promise).
When i look closely inside,, if the input parameter is not isFolder, that is, if it is a file, a callback function is provided by the return commandService.executeCommand(NEW_UNTITLED_FILE_COMMAND_ID); command.\
but if it is a folder, there is no return value. That is undifined.
in vscode API documentation
return type of executeCommand() is Thenable<T | undefined>. That is, in the case of the corresponding command, it is returned as undefined, not Thenable<T>. If an error is not thrown and undefined is returned, it can be determined that it has been normally executed.
// src/vs/workbench/contrib/files/browser/fileAction.ts
...
export const NEW_FOLDER_COMMAND_ID = 'explorer.newFolder';
...
CommandsRegistry.registerCommand({
id: NEW_FOLDER_COMMAND_ID,
handler: async (accessor) => {
await openExplorerAndCreate(accessor, true);
}
});
...
async function openExplorerAndCreate(accessor: ServicesAccessor, isFolder: boolean): Promise<void> {
const explorerService = accessor.get(IExplorerService);
const fileService = accessor.get(IFileService);
const editorService = accessor.get(IEditorService);
const viewsService = accessor.get(IViewsService);
const notificationService = accessor.get(INotificationService);
const commandService = accessor.get(ICommandService);
const wasHidden = !viewsService.isViewVisible(VIEW_ID);
const view = await viewsService.openView(VIEW_ID, true);
if (wasHidden) {
// Give explorer some time to resolve itself #111218
await timeout(500);
}
if (!view) {
// Can happen in empty workspace case (https://github.com/microsoft/vscode/issues/100604)
if (isFolder) {
throw new Error('Open a folder or workspace first.');
}
return commandService.executeCommand(NEW_UNTITLED_FILE_COMMAND_ID);
}
const stats = explorerService.getContext(false);
const stat = stats.length > 0 ? stats[0] : undefined;
let folder: ExplorerItem;
if (stat) {
folder = stat.isDirectory ? stat : (stat.parent || explorerService.roots[0]);
} else {
folder = explorerService.roots[0];
}
if (folder.isReadonly) {
throw new Error('Parent folder is readonly.');
}
const newStat = new NewExplorerItem(fileService, folder, isFolder);
folder.addChild(newStat);
const onSuccess = async (value: string): Promise<void> => {
try {
const resourceToCreate = resources.joinPath(folder.resource, value);
await explorerService.applyBulkEdit([new ResourceFileEdit(undefined, resourceToCreate, { folder: isFolder })], {
undoLabel: nls.localize('createBulkEdit', "Create {0}", value),
progressLabel: nls.localize('creatingBulkEdit', "Creating {0}", value)
});
await refreshIfSeparator(value, explorerService);
if (isFolder) {
await explorerService.select(resourceToCreate, true);
} else {
await editorService.openEditor({ resource: resourceToCreate, options: { pinned: true } });
}
} catch (error) {
onErrorWithRetry(notificationService, error, () => onSuccess(value));
}
};
await explorerService.setEditable(newStat, {
validationMessage: value => validateFileName(newStat, value),
onFinish: async (value, success) => {
folder.removeChild(newStat);
await explorerService.setEditable(newStat, null);
if (success) {
onSuccess(value);
}
}
});
}

Puppeteer and express can not load new data using REST API

I'm using puppeteer to scrape page that has contents that change periodically and use express to present data in rest api.
If I turn on headless chrome to see what is being shown in the browser, the new data is there, but the data is not showing up in get() and http://localhost:3005/api-weather. The normal browser only shows the original data.
const express = require('express');
const server = new express();
const cors = require('cors');
const morgan = require('morgan');
const puppeteer = require('puppeteer');
server.use(morgan('combined'));
server.use(
cors({
allowHeaders: ['sessionId', 'Content-Type'],
exposedHeaders: ['sessionId'],
origin: '*',
methods: 'GET, HEAD, PUT, PATCH, POST, DELETE',
preflightContinue: false
})
);
const WEATHER_URL = 'https://forecast.weather.gov/MapClick.php?lat=40.793588904953985&lon=-73.95738513173298';
const hazard_url2 = `file://C:/Users/xdevtran/Documents/vshome/wc_api/weather-forecast-nohazard.html`;
(async () => {
try {
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
await page.setRequestInterception(true);
page.on("request", request => {
console.log(request.url());
request.continue();
});
await page.goto(hazard_url2, { timeout: 0, waitUntil: 'networkidle0' });
hazard = {
"HazardTitle": "stub",
"Hazardhref": "stub"
}
let forecast = await page.evaluate(() => {
try {
let forecasts = document.querySelectorAll("#detailed-forecast-body.panel-body")[0].children;
let weather = [];
for (var i = 0, element; element = forecasts[i]; i++) {
period = element.querySelector("div.forecast-label").textContent;
forecast = element.querySelector("div.forecast-text").textContent;
weather.push(
{
period,
forecast
}
)
}
return weather;
} catch (err) {
console.log('error in evaluate: ', err);
res.end();
}
}).catch(err => {
console.log('err.message :', err.message);
});
weather = forecast;
server.get('/api-weather', (req, res) => {
try {
res.end(JSON.stringify(weather, null, ' '));
console.log(weather);
} catch (err) {
console.log('failure: ', err);
res.sendStatus(500);
res.end();
return;
}
});
} catch (err) {
console.log('caught error :', err);
}
browser.close();
})();
server.listen(3005, () => {
console.log('http://localhost:3005/api-weather');
});
I've tried several solutions WaitUntil, WaitFor, .then and sleep but nothing seems to work.
I wonder if it has something to do with express get()? I'm using res.end() instead of res.send() is because the json looks better when I use res.end(). I don't really know the distinction.
I'm also open to using this reload solution. But I received errors and didn't use it.
I also tried waitForNavigation(), but I don't know how it works, either.
Maybe I'm using the wrong search term to find the solution. Could anyone point me in the right direction? Thank you.