How add custom Service Worker on Flutter Web to receive push notification - notifications

I'm trying to add a custom service worker to read web push notifations from external service. The problem is that my custom service worker "worker.js" is not registered in application.
Next, the code:
WORKER.JS
console.log("Service worker loaded...");
self.addEventListener("install", event => {
console.log("Service worker installed");
});
self.addEventListener("activate", event => {
console.log("Service worker activated");
});
self.addEventListener('message', event => {
console.log(event)
});
self.addEventListener('push', function(e) {
console.log('push event',e)
const data = e.data
self.registration.showNotification(
// data.title,
"hello",
{
// body: data.body,
body:"how are you?",
}
);
})
INDEX.HTML (part)
<head>
...
...
<script>
// The value below is injected by flutter build, do not touch.
var serviceWorkerVersion = null;
</script>
<!-- This script adds the flutter initialization JS code -->
<script src="flutter.js" defer></script>
<!-- MY CUSTOM SERVICE WORKER INITS HERE -->
<script src="./client.js" defer></script>
</head>
<body>
<script>
window.addEventListener('load', function(ev) {
// Download main.dart.js
_flutter.loader.loadEntrypoint(
{
serviceWorker: {
serviceWorkerVersion: serviceWorkerVersion,
}
}).then(function(engineInitializer) {
return engineInitializer.initializeEngine();
}).then(function(appRunner) {
return appRunner.runApp();
})
});
</script>
CLIENT.JS
if('serviceWorker' in navigator) {
registerServiceWorker().catch(console.log)
}
async function registerServiceWorker() {
console.log("Before register worker...")
const register = await navigator.serviceWorker.register('./worker.js', {
scope: '/'
});
const PUBLIC_VAPID_KEY = "ApiKeyGeneratedFromService";
function urlBase64ToUint8Array(base64String) {
const padding = "=".repeat((4 - (base64String.length % 4)) % 4);
const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/");
const rawData = window.atob(base64);
const outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
}
console.log("Before subscription")
const subscription = await register.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey:urlBase64ToUint8Array(PUBLIC_VAPID_KEY)});
console.log("subscription:", subscription.endpoint);
console.log("Before fecth")
await fetch("{UrlFromCustomService}/notifier/webpush/subscription?operation=subscribe", {
method: "POST",
body: JSON.stringify(subscription),
headers: {
"Content-Type": "application/json",
}
})
When I starts application, the "worker.js" get the fetch operation correctly, but the push notification never arrive. Notification permission is also displayed correctly.
When open service worker register...
It's like "flutter_service_worker" replaces my custom worker.
I can only have one service worker at a time ?
Is this the correct way to implement a custom service worker (WITHOUT FIREBASE)?

Related

How to get Twilio Video API working in VueJS

I have followed through the following post on how to set up Twilio video API with javascript: https://www.twilio.com/docs/video/tutorials/get-started-with-twilio-video-node-express-server
This works fine but I am now running into problems trying to convert this code to work with VueJS. Everything seems to run and I see the HTML appended for the audio and video, however no video element appears on the page. How do I fix this
This is the code I have written/adapted
<script setup>
import {ref} from "vue";
const isFormVisible = ref(true);
const roomName = ref("");
const participants = ref([]);
import Twilio, { connect, createLocalTracks, createLocalVideoTrack } from 'twilio-video'
const startRoom = async (event) => {
console.log('started');
isFormVisible.value = false;
const response = await fetch("http://localhost:3000/join-room", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({ roomName: roomName.value }),
});
const { token } = await response.json();
console.log(token);
// join the video room with the token
const room = await joinVideoRoom(roomName.value, token);
// render the local and remote participants' video and audio tracks
handleConnectedParticipant(room.localParticipant);
room.participants.forEach(handleConnectedParticipant);
room.on("participantConnected", handleConnectedParticipant);
room.on("participantDisconnected", handleDisconnectedParticipant);
window.addEventListener("pagehide", () => room.disconnect());
window.addEventListener("beforeunload", () => room.disconnect());
}
const handleDisconnectedParticipant = (participant) => {
// stop listening for this participant
participant.removeAllListeners();
// remove this participant's div from the page
//const participantDiv = document.getElementById(participant.identity);
//participantDiv.remove();
};
const handleConnectedParticipant = (participant) => {
// create a div for this participant's tracks
participants.value.push(participant);
// iterate through the participant's published tracks and
// call `handleTrackPublication` on them
participant.tracks.forEach((trackPublication) => {
handleTrackPublication(trackPublication, participant);
});
// listen for any new track publications
participant.on("trackPublished", handleTrackPublication);
};
const handleTrackPublication = (trackPublication, participant) => {
function displayTrack(track) {
// track.attach creates an HTMLVideoElement or HTMLAudioElement
// (depending on the type of track) and adds the video or audio stream
if(!participant.track) {
participant.track = track.attach().outerHTML;
}
else {
participant.track += track.attach().outerHTML;
}
console.log(track.attach().outerHTML);
console.log(track.attach());
}
// check if the trackPublication contains a `track` attribute. If it does,
// we are subscribed to this track. If not, we are not subscribed.
if (trackPublication.track) {
displayTrack(trackPublication.track);
}
// listen for any new subscriptions to this track publication
trackPublication.on("subscribed", displayTrack);
};
const joinVideoRoom = async (roomName, token) => {
// join the video room with the Access Token and the given room name
let connectOptions = {
name: roomName,
// logLevel: 'debug',
audio: true,
video: { width: 400 }
};
const room = await Twilio.connect(token, connectOptions);
return room;
};
</script>

Setup of swagger-ui-express with API Gateway

I am not able to figure out the correct setup of either my API Gateway or swagger-ui-express.
The lambda function runs successful and and return the html but the related resouces are not able to be loaded and get an 404 error.
const app = express();
const swaggerUI = require('swagger-ui-express');
const serverlessExpress = require('#vendia/serverless-express');
const Stage = process.env.Stage;
const apiId = process.env.apiId;
const options = {
customCss: '.swagger-ui .topbar { display: none }',
customCss: '.swagger-ui .topbar { background-color: red }'
}
let serverlessExpressInstance
async function asyncTask() {
// load latest spec from API Gateway export removed to simplicity reasons.
const swaggerDocument = spec;
console.log("load swagger file complete.");
return swaggerDocument
}
async function setup(event, context) {
const swaggerDocument = await asyncTask()
console.log(swaggerDocument)
app.use('/api-doc', swaggerUI.serveWithOptions({ redirect: false }));
app.use('/api-doc', swaggerUI.setup(swaggerDocument, options));
console.log("setup of swagger complete");
serverlessExpressInstance = serverlessExpress({ app })
return serverlessExpressInstance(event, context)
}
function handler(event, context) {
if (serverlessExpressInstance) return serverlessExpressInstance(event, context)
return setup(event, context)
}
exports.handler = handler
Setup on API Gateway is the following:
Both resources are pointing to the lambda function.
When I load the page via: https://.execute-api..amazonaws.com/dev/api-doc
The following errors are raised:
How can I ensure that the resources are loaded correctly via the correct path ...dev/api/doc/...
You're probbably getting these errors because swagger-ui is configured to look for the image/js resources and express doesn't serve these files. You could potentially use something like serverless-http like this, but I'm not really sure it's a good idea to try and force express into the response of an API gateway lambda function.
As an alternative I managed to get swagger up and running on an API Gateway lambda function, fetching the swagger JSON directly.
import { APIGateway } from "aws-sdk";
import { env } from "../../helpers/env";
const API_ID = env("API_ID", "");
export const handler = async () => {
const apiGateway = new APIGateway({
apiVersion: "2018-11-29",
});
const apiGatewayExport = await apiGateway
.getExport({
exportType: "swagger",
restApiId: API_ID,
stageName: "prod",
accepts: "application/json",
parameters: {
extensions: "apigateway",
},
})
.promise();
if (!apiGatewayExport.body) {
throw new Error("No body found in API Gateway Export");
}
return {
statusCode: 200,
headers: {
"Content-Type": "text/html",
},
body: generateSwaggerPageBody(apiGatewayExport.body?.toString()),
};
};
const generateSwaggerPageBody = (swaggerSpec: string) => `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Swagger</title>
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist#3/swagger-ui.css">
</head>
<body>
<div id="swagger"></div>
<script src="https://unpkg.com/swagger-ui-dist#3/swagger-ui-bundle.js"></script>
<script>
SwaggerUIBundle({
dom_id: '#swagger',
spec: ${swaggerSpec}
});
</script>
</body>
</html>`;
I'm taking the API information directly from API Gateway via SDK and then I just return an html page with tags loading swagger directly - no express overhead needed.

How to send a request from Nuxt.js client over Nuxt.js server and receive the response back to the client

I'm developing a Vue.js application which has only frontend (no server) and send a lot of requests to different APIs. The originally quite simple app became more complex. And there are problems with some APIs, because browsers do not accept the responses due to CORS. That is why I'm trying to test, if I can migrate the app to Nuxt.js.
My approach is as follows (inspired by this comment), but I expect, that there is probably a better way to send the requests from the client over the server.
pages/test-page.vue
methods: {
async sendRequest(testData) {
const response = await axios.post('api', testData)
// Here can I use the response on the page.
}
}
nuxt.config.js
serverMiddleware: [
{ path: '/api', handler: '~/server-middleware/postRequestHandler.js' }
],
server-middleware/postRequestHandler.js
import axios from 'axios'
const configs = require('../store/config.js')
module.exports = function(req, res, next) {
let body = ''
req.on('data', (data) => {
body += data
})
req.on('end', async () => {
if (req.hasOwnProperty('originalUrl') && req.originalUrl === '/api') {
const parsedBody = JSON.parse(body)
// Send the request from the server.
const response = await axios.post(
configs.state().testUrl,
body
)
req.body = response
}
next()
})
}
middleware/test.js (see: API: The Context)
export default function(context) {
// Universal keys
const { store } = context
// Server-side
if (process.server) {
const { req } = context
store.body = req.body
}
}
pages/api.vue
<template>
{{ body }}
</template>
<script>
export default {
middleware: 'test',
computed: {
body() {
return this.$store.body
}
}
}
</script>
When the user makes an action on the page "test", which will initiate the method "sendRequest()", then the request "axios.post('api', testData)" will result in a response, which contains the HTML code of the page "api". I can then extract the JSON "body" from the HTML.
I find the final step as suboptimal, but I have no idea, how can I send just the JSON and not the whole page. But I suppose, that there must be a much better way to get the data to the client.
There are two possible solutions:
Proxy (see: https://nuxtjs.org/faq/http-proxy)
API (see: https://medium.com/#johnryancottam/running-nuxt-in-parallel-with-express-ffbd1feef83c)
Ad 1. Proxy
The configuration of the proxy can look like this:
nuxt.config.js
module.exports = {
...
modules: [
'#nuxtjs/axios',
'#nuxtjs/proxy'
],
proxy: {
'/proxy/packagist-search/': {
target: 'https://packagist.org',
pathRewrite: {
'^/proxy/packagist-search/': '/search.json?q='
},
changeOrigin: true
}
},
...
}
The request over proxy can look like this:
axios
.get('/proxy/packagist-search/' + this.search.phpLibrary.searchPhrase)
.then((response) => {
console.log(
'Could get the values packagist.org',
response.data
)
}
})
.catch((e) => {
console.log(
'Could not get the values from packagist.org',
e
)
})
Ad 2. API
Select Express as the project’s server-side framework, when creating the new Nuxt.js app.
server/index.js
...
app.post('/api/confluence', confluence.send)
app.use(nuxt.render)
...
server/confluence.js (simplified)
const axios = require('axios')
const config = require('../nuxt.config.js')
exports.send = function(req, res) {
let body = ''
let page = {}
req.on('data', (data) => {
body += data
})
req.on('end', async () => {
const parsedBody = JSON.parse(body)
try {
page = await axios.get(
config.api.confluence.url.api + ...,
config.api.confluence.auth
)
} catch (e) {
console.log('ERROR: ', e)
}
}
res.json({
page
})
}
The request over API can look like this:
this.$axios
.post('api/confluence', postData)
.then((response) => {
console.log('Wiki response: ', response.data)
})
.catch((e) => {
console.log('Could not update the wiki page. ', e)
})
Now with nuxtjs3 :
nuxtjs3 rc release
you have fetch or useFetch no need to import axios or other libs, what is great, automatic parsing of body, automatic detection of head
fetching data
you have middleware and server api on same application, you can add headers on queries, hide for example token etc
server layer
a quick example here in vue file i call server api :
const { status } = await $fetch.raw( '/api/newsletter', { method: "POST", body: this.form.email } )
.then( (response) => ({
status: response.status,
}) )
.catch( (error) => ({
status: error?.response?.status || 500,
}) );
it will call a method on my server, to init the server on root directory i created a folder name server then api, and a file name newsletter.ts (i use typescript)
then in this file :
export default defineEventHandler(async (event) => {
const {REST_API, MAILINGLIST_UNID, MAILINGLIST_TOKEN} = useRuntimeConfig();
const subscriber = await readBody(event);
console.log("url used for rest call" + REST_API);
console.log("token" + MAILINGLIST_TOKEN);
console.log("mailing list unid" + MAILINGLIST_UNID);
let recipientWebDTO = {
email: subscriber,
subscriptions: [{
"mailingListUnid": MAILINGLIST_UNID
}]
};
const {status} = await $fetch.raw(REST_API, {
method: "POST",
body: recipientWebDTO,
headers: {
Authorization: MAILINGLIST_TOKEN,
},
}).then((response) => ({
status: response.status,
}))
.catch((error) => ({
status: error?.response?.status || 500,
}));
event.res.statusCode = status;
return "";
})
What are the benefits ?
REST_API,MAILING_LIST_UNID, MAILING_LIST_TOKEN are not exposed on
client and even file newsletter.ts is not available on debug browser.
You can add log only on server side You event not expose api url to avoid some attacks
You don't have to create a new backend just to hide some criticals token or datas
then it is up to you to choose middleware route or server api. You don't have to import new libs, h3 is embedded via nitro with nuxtjs3 and fetch with vuejs3
for proxy you have also sendProxy offered by h3 : sendProxy H3
When you build in dev server and client build in same time(and nothing to implement or configure in config file), and with build to o, just don deploy your project in static way (but i think you can deploy front in static and server in node i don't know)

How use axios in web worker with vue?

I've try use axios request in web worker by vue plugin
My code looks like this:
//worker.js
import axios from 'axios';
export default function getApiData(args) {
axios.get('/api/test').then(response => {
console.log(response);
});
}
And main file with Vue
//main.js
import getApiData from './worker.js';
Vue.use(VueWorker);
window.vueApp = new Vue({
//...
created: function() {
this.updateWorker = this.$worker.create([
{
message: 'getApiData ',
func: getApiData
}
]);
this.testWorker.postMessage('getApiData ', [this.args])
.then(result => {
console.log(result);
})
},
//...
}
And I got this error
Uncaught ReferenceError: axios__WEBPACK_IMPORTED_MODULE_0___default is
not defined
What I'm doing wrong?
I had the same problem and solved it by going back to the basics - using plain old XMLRequest (see below).
You can't use axios inside the worker because the blob that's created behind the scenes to load the worker.js runs in a different context than your main.js. In order for axios to work, you would have to setup a new webpack bundler to just bundle the worker by itself. I didn't want to do that - it makes the project structure unnecessary complex.
Here is a simple solution (works with json and non-json response).
// worker.js
export default () => {
self.addEventListener('message', e => {
if (!e) return;
// wrap in try/catch if you want to support IE11 and older browsers
// that don't support Promises. The implementation below doesn't work
// even when polyfills are loaded in the main project because
// the worker runs in a different context, ie no webpack bundles here.
try {
const fetchData = (url, isJSON = true) => {
return new Promise((resolve, reject) => {
function reqListener() {
if (this.status !== 200) {
return reject();
}
const response = isJSON
? JSON.parse(this.responseText)
: this.responseText;
resolve(response);
}
const oReq = new XMLHttpRequest();
oReq.addEventListener('load', reqListener);
oReq.open('GET', url);
oReq.send();
});
};
const baseUrl = 'https://static.newsfilter.io/';
const { articleId } = e.data;
const jsonUrl = baseUrl + articleId + '.json';
const htmlUrl = baseUrl + articleId + '.html';
// My use case requires 2 requests in parallel.
const tasks = [fetchData(jsonUrl), fetchData(htmlUrl, false)];
Promise.all(tasks)
.then(data => {
postMessage({ json: data[0], html: data[1] });
})
.catch(error => {
postMessage({ isError: true, error });
});
} catch (error) {
postMessage({ isError: true });
}
});
};

How to test vue component method with ajax request in Mocha?

I have a vue component with methods send out ajax request, I tried done() provided by Mocha to test this method, but it seems do not work correctly by the coverage report(the success and complete methods are not covered according to the report). The codes are as follows:
Vue component code in demo.vue
```
loadData: function(cb) {
var that = this;
$.ajax(
{
url: "/api/nodesWithTask",
async: true,
success: function(data) {
that.nodes = data;
},
complete: function(xhr, status) {
if (cb && typeof (cb) == 'function') {
cb.call();
}
}
}
);
}
```
Test code
```
import Vue from 'vue'
import demo from '#/components/demo'
describe('demo.vue', () => {
var mockData = {"id":"abc"};
var server;
var vm;
before(function () {
server = sinon.fakeServer.create();
//mock response for ajax request
server.respondWith("/api/nodesWithTask",[200,{"Content-Type":"application/json"},JSON.stringify(mockData)]);
const Constructor = Vue.extend(demo);
vm = new Constructor().$mount();
});
after(function () { server.restore(); });
it('load data async', (done) => {
vm.loadData(function(){
done();
});
})
})
```
Thanks for any suggestion in advance.
Advised by my leader, I find one magic config option of sinon fake server as follows
before(function () {
server = sinon.fakeServer.create();
//mock response for ajax request
server.respondWith("/api/nodesWithTask",[200,{"Content-Type":"application/json"},JSON.stringify(mockData)]);
server.respondImmediately = true;
const Constructor = Vue.extend(demo);
vm = new Constructor().$mount();
});
After I set the respondImmediately option to true, the test can run correctly.