Chrome extension: DOMParser is not defined with Manifest v3 - google-chrome-extension

I have developped an extension to scrape some content from web page and up to now it was working fine but since I switched to manifest v3, the parsing doesn't work anymore.
I use the following script to read the source code:
chrome.scripting.executeScript(
{
target: {tabId: tab.id, allFrames: true},
files: ['GetSource.js'],
}, async function(results)
{
// GETTING HTML
parser = new DOMParser();
content = parser.parseFromString(results, "text/html");
... ETC ...
This code used to work fine but now I get the following message in my console:
Uncaught (in promise) ReferenceError: DOMParser is not defined
The code is part of a promise but I don't think the promise is the problem here. I basically need to load the source code into a variable so that I can parse it afterwards.
I've checked the documentation but I haven't found something mentionned that DOMParser was not going to work with v3.
Any idea?
Thanks

Since service workers don't have access to DOM, it's not possible for
an extension's service worker to access the DOMParser API or create an
to parse and traverse documents.
More detail
And I solve the problem by using library dom-parser.The code could be like this
import DomParser from "dom-parser";
const parser = new DomParser();
const dom = parser.parseFromString('you html string');

From the docs:
Since service workers don't have access to DOM, it's not possible for an extension's service worker to access the DOMParser API or create an to parse and traverse documents.
Using an external library just for doing what DomParser already does?? It is too heavy.
To work-around with it, we can use an offscreen document. It's just invisible webpage where you can run fetch, audio, DomParser, ... and communicate with background (service_worker) via chrome.runtime.
See an example below:
background.js
chrome.offscreen.createDocument({
url: chrome.runtime.getURL('xam.html'),
reasons: [chrome.offscreen.Reason.DOM_PARSER],
justification: 'reason for needing the document',
});
// Just a test
setTimeout(() => {
const onDone = (msg) => {
console.log(msg);
chrome.runtime.onMessage.removeListener(onDone);
};
chrome.runtime.onMessage.addListener(onDone);
chrome.runtime.sendMessage('from-background-page');
}, 3000);
xam.html
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="xam.js">
</script>
</body>
</html>
xam.js
async function main() {
const v = await fetch('https://......dev/').then((t) => t.text());
const d = new DOMParser().parseFromString(v, 'text/html');
const options = Array.from(d.querySelector('select').options)
.map((v) => `${v.value}|${v.text}`)
.join('\n');
chrome.runtime.sendMessage(options);
}
chrome.runtime.onMessage.addListener(async (msg) => {
console.log(msg);
main();
});
manifest.json
"permissions": [
// ...
"offscreen"
]
https://developer.chrome.com/docs/extensions/reference/offscreen/
The extension's permissions carry over to offscreen documents, but extension API access is heavily limited. Currently, an offscreen document can only use the chrome.runtime APIs to send and receive messages; all other extension APIs are not exposed.
Notes:
I haven't tested how long this offscreen document alive.
Just sample codes, it should work. Customzie as your own cases.

Related

How to return a 404 Not found page in an Express App?

I have an express app, in which I have the following code:
app.get('*', (req, res) => {
res.send('404', {
title: 404,
name: 'James Olaleye',
errorMessage: 'Page not found',
});
});
However, My IDE is warning about this message:
express deprecated res.send(status, body): Use
res.status(status).send(body) instead
And with the above code, My Browser is returning the following payload as a JSON object:
{
"title": 404,
"name": "James Olaleye",
"errorMessage": "Page not found"
}
What I want, is to display a 404 Not found page to the user, how can this be achived?
You have two seperate problem
1: you are using an old way to response to the request insted use this res.status(STATUS_CODE).send(BODY)
2: you are sending a json yet you want to display a 404 page in this case you need to send a html template
so your code should look like this
app.get('*', (req, res) => {
res.status(404).send("<div>404 Not Found</div>");
});
I updated your question a bit to make it clearer for future references.
the method res.send is deprecated, among other things because it's usages is too ambiguous. A server response, can be a lot of things, it can be a page, it can be a file, and it can be a simple JSON object (which you have here).
In your case, when you run res.send(404,{ /*...*/ }), the express app assumes you want to send a JSON object, so it does just that.
There are multiple possible ways, to achieve what you want, but I will stick to the most simple solution.
If you want to display an HTML page, in the most simplest form, you can actually just change your piece of code to do this instead:
app.status(404).send(`<h1>Page not found</h1>`)
This will essentially, show a page, instead of a JSON object.
You can even define the whole HTML file if you like:
app.status(404).send(
`
<html>
<!DOCTYPE html>
<head>
<title>404</title>
</head>
<body>
<h1>James Olaleye</h1>
<h1>Page Not Found</h1>
</body>
</html>
`
)
This would be the fastest way to achieve what you want.
A step further, would be to create an HTML file some where in your app, and to send the HTML file instead.
If your source code looks like this:
/
src/
index.js
htmls/
404.html
<!-- htmls/404.html -->
<html>
<!DOCTYPE html>
<head>
<title>404</title>
</head>
<body>
<h1>James Olaleye</h1>
<h1>Page Not Found</h1>
</body>
</html>
// src/index.js
const express = require('express');
const app = express();
const path = require('path');
const PORT = 3000;
app.get('/', function(req, res) {
const options = {
root: path.join(__dirname, '..', 'htmls')
};
res.sendFile('404.html', options, function (err) {
if (err) {
next(err);
} else {
console.log('Sent:', fileName);
}
});
});
This would allow you to have multiple HTML files which you can send around.
There are like I stated, other options as well, but that would make this answer way too long and out of scope. If you are interested, you can research Using template engines with Express and start with the following link.
Happy coding :)

Ejs doesn't load when I call a function from another file

Basicly I want to run a function when I clicked the button, but it works when I started the server and go to localhost one time, here's what's supposed to happen, after that localhost page doesn't load. (Unable to connect error)
If I remove the function there is no problem. How can I get it to work only when I click the button ?
Many thanks.
My func.js
const mongoose = require("mongoose");
const axios = require('axios');
async function func() {
//MyCodes
}
module.exports = {
func: func
}
My index.ejs
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<button type="button" onClick= <%= func.func() %> >Click</button>
//Other codes are independent the button
</body>
</html>
My res.render codeblocks in app.js
var func = require('./func');
app.get("/", (req, res) => {
res.render('index', {
cName: name,
symbol: symbol,
len: finds[0].result.length,
cPrice: price,
cDate: date,
func:func
});
});
});
})
You are misunderstanding. You cannot call an internal nodejs function(backend) from the html (frontend). If your frontend need to execute some backend operation like query to mongo, you have these options:
#1 client side rendering (Modern)
This is the most used in the modern world: Ajax & Api
your backend exposes a rest endpoints like /products/search who recieve a json and return another json
this endpoints should be consumed with javascript on some js file of your frontend:
html
<html lang="en">
<head>
<script src="./controller.js"></script>
</head>
<body>
<button type="button" onClick="search();" >Click</button>
</body>
</html>
controller.js
function search(){
$.ajax({
url:"./api/products/search",
type:"POST",
data:JSON.stringify(fooObject),
contentType:"application/json; charset=utf-8",
dataType:"json",
success: function(response){
...
}
})
}
Note 1: controller.js contains javascript for browser not for backend : nodejs
Note 2: ejs is only used to return the the initial html, so it is better to use another frameworks like:react, angular, vue
#2 server side rendering (Legacies)
In this case, ajax and js for browser are not strictly required.
Any event on your html should use <form> to trigger an entire page reload
You backend receives any parameter from the , make some operations like mongo queries and returns html instead json, using res.render in your case
Note
Ejs is for SSR = server side rendering, so add ajax could be complex for novices. In this case, use the option #2
You cannot use a nodejs function (javascript for server) in the client side (javascript for browser). Maybe some workaround are able to do that but, don't mix different things.

Get URL for EcmaScript module after importing in browser

My start HTML:
<!DOCTYPE html>
<html lang="en">
<head>
<script type="module" src="/way/to/app.js" defer></script>
</head>
<body>...</body>
</html>
My app.js script is an EcmaScript Module and loads another EcmaScript Module:
import Mod from "./path/to/module.js";
...
this is module.js:
// const url = ???
export default class Module {
constructor() {
// console.log(url);
}
}
Does exist any method to get loading URL "/way/to/path/to/module.js" in module.js? Something like these variables in nodejs but for browser:
const dir = __dirname;
const file = __filename;
location.href gives an URL for start html page.
Google Chrome and Firefox both support import.meta:
<script type="module">
console.log(import.meta);
</script>
In my console that prints:
Object { url: "file:///D:/testImportMeta.html" }
I think it's not really suited for production, unless Babel supports it. But if you're not over-concerned with lazy browsers, it works well.
It does not work in Node.JS yet.

Creating HapiJs server that can serve complex web pages

I have just started learning Node.js and hapi.js. What I am trying to accomplish now is build a REST web server that should also have a web interface for configuration and statistics collection.
I found that Inert plugin allows serving static pages and, as I understand, this limits me to loading a single web page that consists of a single file.
However, what I do not understand is whether it is possible to setup hapi.js to serve a full dynamic webpage with css, js and other files referenced within its body.
Am I heading the wrong direction with this or else how can I setup my scenario?
You can serve multiple static files from a specified directory.
Just tried out inert with hapi, using this example:
https://github.com/hapijs/inert#static-file-server
Inert has no problem serving multiple static files from a given directory, e.g public.
So you'll have no issue serving multiple static html, css, js files from a specified dir. You can then build a dynamic JSON api using Hapi, and have that consumed by your js client-side code, served from static js files in your public dir.
If you are needing templating, where you generate dynamic content on the serverside, hapi can do that out of the box, check out:
http://hapijs.com/tutorials/views
Sorry if this isn't what you mean, do feel free to clarify if not :-)
Hope that helps!
The vision plugin is used for templating, which is what I think you're after. If you want to bundle css and js files along with your pages, you can put them in a public directory and serve that with the inert plugin. And then you only need to reference the relative path in whatever html file you're trying to render.
Here's a simple example that uses handlebars. Inert is responsible for serving your css and js files while vision still renders your templates.
./index.js
var hapi = require('hapi');
var server = new hapi.Server();
server.connection({port: 5555});
server.register([require('vision'), require('inert')], (err) => {
if(err){
throw err;
}
server.views({
engines: {
html: require('handlebars')
},
relativeTo: __dirname + '/',
path: 'www'
});
var homeroute = {
method: 'GET',
path: '/',
handler: (request, reply) => {
reply.view('index', {name: 'cuthbert'});
}
};
var publicassetsroute = {
method: 'GET',
path: '/public/{param*}',
handler: {
directory: {
path: './public',
listing: false,
index: false
}
}
};
server.route(homeroute);
server.route(publicassetsroute);
server.start((err) => {
console.log('server started -- ' + server.info.uri)
});
});
www/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hapi Test</title>
<link rel="stylesheet" href="../public/index.css">
</head>
<body>
<h1>A Hapi Happy Test.</h1>
<p>This is a test page. Woo!</p>
<p>My name is {{name}}.</p>
</body>
</html>
public/index.css
p {
color: blue;
}

Consuming a Stream create using Node.JS

I have an application, which streams an MP3 using Node.JS. Currently this is done through the following post route...
app.post('/item/listen',routes.streamFile)
...
exports.streamFile = function(req, res){
console.log("The name is "+ req.param('name'))
playlistProvider.streamFile(res, req.param('name'))
}
...
PlaylistProvider.prototype.streamFile = function(res, filename){
res.contentType("audio/mpeg3");
var readstream = gfs.createReadStream(filename, {
"content_type": "audio/mpeg3",
"metadata":{
"author": "Jackie"
},
"chunk_size": 1024*4 });
console.log("!")
readstream.pipe(res);
}
Is there anyone that can help me read this on the client side? I would like to use either JPlayer or HTML5, but am open to other options.
So the real problem here was, we are "requesting a file" so this would be better as a GET request. In order to accomplish this, I used the express "RESTful" syntax '/item/listen/:name'. This then allows you to use the JPlayer the way specified in the links provided by the previous poster.
I'm assuming you didn't bother visiting their site because had you done so, you would have seen several examples of how to achieve this using HTML5/JPlayer. The following is a bare-bones example provided by their online developer's documentation:
<html>
<head>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6/jquery.min.js"></script>
<script type="text/javascript" src="/js/jquery.jplayer.min.js"></script>
<script type="text/javascript">
$(document).ready(function(){
$("#jquery_jplayer_1").jPlayer({
ready: function() {
$(this).jPlayer("setMedia", {
mp3: "http://www.jplayer.org/audio/mp3/Miaow-snip-Stirring-of-a-fool.mp3"
}).jPlayer("play");
var click = document.ontouchstart === undefined ? 'click' : 'touchstart';
var kickoff = function () {
$("#jquery_jplayer_1").jPlayer("play");
document.documentElement.removeEventListener(click, kickoff, true);
};
document.documentElement.addEventListener(click, kickoff, true);
},
loop: true,
swfPath: "/js"
});
});
</script>
</head>
<body>
<div id="jquery_jplayer_1"></div>
</body>
</html>

Resources