Version 0.1.2

This commit is contained in:
Fabio Manganiello 2023-01-18 19:07:04 +01:00
parent cb8c201b95
commit d32b0243d8
5 changed files with 114 additions and 25 deletions

View file

@ -11,7 +11,7 @@ import commonjs from '@rollup/plugin-commonjs';
import replace from '@rollup/plugin-replace'; import replace from '@rollup/plugin-replace';
import postcss from 'rollup-plugin-postcss'; import postcss from 'rollup-plugin-postcss';
import alias from 'rollup-plugin-alias'; import alias from 'rollup-plugin-alias';
import _dotenv from 'dotenv/config'; // import _dotenv from 'dotenv/config';
import path from "path"; import path from "path";
export default { export default {

View file

@ -1,8 +1,69 @@
import browser from 'webextension-polyfill'; import browser from 'webextension-polyfill';
let awaitingResponse = false
const onFeedDownloaded = (req: XMLHttpRequest) => {
return async () => {
awaitingResponse = false
if (req.status >= 400) {
console.error(
`Could not load URL feed: ${req.responseURL}: ` +
`${req.status}: ${req.statusText}`
)
return
}
const [tab] = await browser.tabs.query({
active: true,
currentWindow: true
})
if (!tab || tab.id === -1)
return
await browser.tabs.sendMessage(
tab.id, {
type: 'renderFeed',
document: req.responseText
}
)
}
}
const renderFeed = (url: string) => {
awaitingResponse = true
const req = new XMLHttpRequest()
req.onload = onFeedDownloaded(req)
req.open('GET', url)
req.responseType = 'text'
req.send()
}
browser.webNavigation.onCompleted.addListener( browser.webNavigation.onCompleted.addListener(
async (event: {tabId: string;}) => { async (event: {tabId: Number}) => {
const { tabId } = event const { tabId } = event
await browser.tabs.sendMessage(tabId, {type: 'renderFeed'}) await browser.tabs.sendMessage(tabId, {type: 'renderFeed'})
} }
) )
browser.webRequest.onHeadersReceived.addListener(
async (event: {
url: string,
responseHeaders: Array<{name: string, value: string}>
}) => {
if (awaitingResponse)
return
const {url, responseHeaders} = event
const contentType = responseHeaders.find(
h => h.name.toLowerCase() === 'content-type'
)?.value || ''
if (contentType.startsWith('application/rss+xml'))
renderFeed(url)
},
{urls: ['<all_urls>']},
['blocking', 'responseHeaders']
)

View file

@ -103,9 +103,9 @@ const parseFeed = (channel: Element) => {
} }
} }
const getFeedRoot = () => { const getFeedRoot = (): HTMLElement | null => {
const xmlDoc = document.documentElement const xmlDoc = document.documentElement
if (xmlDoc.tagName === 'rss') if (xmlDoc.tagName.toLowerCase() === 'rss')
return xmlDoc return xmlDoc
// Chrome-based browsers may wrap the XML into an HTML view // Chrome-based browsers may wrap the XML into an HTML view
@ -116,28 +116,31 @@ const getFeedRoot = () => {
// For some ugly reasons, some RSS feeds are rendered inside of a <pre> in a normal HTML DOM // For some ugly reasons, some RSS feeds are rendered inside of a <pre> in a normal HTML DOM
const preElements = document.getElementsByTagName('pre') const preElements = document.getElementsByTagName('pre')
if (preElements.length !== 1) if (preElements.length !== 1)
return return null
const text = preElements[0].innerText return preElements[0]
}
const textToDOM = (text: string) => {
const parser = new DOMParser() const parser = new DOMParser()
let innerXmlDoc = null let xmlDoc = null
try { try {
// @ts-ignore // @ts-ignore
innerXmlDoc = parser.parseFromString(text, 'text/xml') xmlDoc = parser.parseFromString(text, 'text/xml')
} catch (e) { } } catch (e) { }
if (!innerXmlDoc) if (!xmlDoc)
return return
// @ts-ignore // @ts-ignore
const root = innerXmlDoc.documentElement const root = xmlDoc.documentElement
if (root.tagName === 'rss') if (root.tagName.toLowerCase() === 'rss')
return root return root
} }
const renderFeed = () => { const renderFeed = (text: string) => {
const xmlDoc = getFeedRoot() const xmlDoc = text?.length ? textToDOM(text) : getFeedRoot()
if (!xmlDoc) if (!xmlDoc)
// Not an RSS feed // Not an RSS feed
return return
@ -153,9 +156,9 @@ const renderFeed = () => {
const extractFeedUrl = () => { const extractFeedUrl = () => {
const links = Array.from(document.getElementsByTagName('link')) const links = Array.from(document.getElementsByTagName('link'))
.filter((link) => .filter((link) =>
link.getAttribute('rel') === 'alternate' && link.getAttribute('rel') === 'alternate' &&
link.getAttribute('type') === 'application/rss+xml' link.getAttribute('type')?.startsWith('application/rss+xml')
) )
if (!links.length) if (!links.length)
return return
@ -171,10 +174,20 @@ const extractFeedUrl = () => {
return link.length ? link : null return link.length ? link : null
} }
browser.runtime.onMessage.addListener(async (message: {type: Object}) => { browser.runtime.onMessage.addListener(
if (message.type === 'renderFeed') async (
return renderFeed() message: {
if (message.type === 'extractFeedUrl') type: Object,
return extractFeedUrl() url: string,
}) document: string,
}
) => {
if (message.type === 'renderFeed')
return renderFeed(message.document)
if (message.type === 'extractFeedUrl')
return extractFeedUrl()
console.warn(`Received unknown message type: ${message.type}`)
}
)

View file

@ -1,7 +1,7 @@
{ {
"name": "RSS Viewer", "name": "RSS Viewer",
"description": "An easy way to render RSS feeds directly in your browser", "description": "An easy way to render RSS feeds directly in your browser",
"version": "0.1.1", "version": "0.1.2",
"manifest_version": 2, "manifest_version": 2,
"browser_action": { "browser_action": {
"default_title": "Feed Viewer", "default_title": "Feed Viewer",
@ -26,6 +26,11 @@
"viewer/index.html" "viewer/index.html"
], ],
"permissions": [ "permissions": [
"activeTab", "storage", "<all_urls>", "webNavigation" "activeTab",
"storage",
"<all_urls>",
"webNavigation",
"webRequest",
"webRequestBlocking"
] ]
} }

View file

@ -64,6 +64,10 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.feed {
font-family: -apple-system, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Open Sans", "Droid Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
}
header { header {
padding: 0.5em; padding: 0.5em;
box-shadow: 1px 1px 1px 1px #b7b7b7; box-shadow: 1px 1px 1px 1px #b7b7b7;
@ -153,9 +157,15 @@ main {
} }
.content { .content {
display: flex;
flex-direction: column;
padding: 1em; padding: 1em;
font-size: 1.25em; font-size: 1.25em;
font-family: sans-serif;
.description {
max-width: 45em;
text-align: justify;
}
} }
} }
} }