fix: initial load #3

Merged
mcataford merged 5 commits from fix/initial-load into main 2021-07-17 03:01:06 +00:00
9 changed files with 89 additions and 28 deletions

3
.gitignore vendored
View file

@ -115,3 +115,6 @@ dist
# TernJS port file # TernJS port file
.tern-port .tern-port
# Local Netlify folder
.netlify

View file

@ -0,0 +1,24 @@
const https = require('https')
async function httpGet(url) {
return new Promise((resolve) => {
https.get(url, (response) => {
response.on('data', (d) => resolve(d))
})
})
}
const handler = async (event) => {
try {
const url = event.queryStringParameters.url
const proxiedResponse = await httpGet(url)
return {
statusCode: 200,
body: String(proxiedResponse),
}
} catch (error) {
return { statusCode: 500, body: error.toString() }
}
}
module.exports = { handler }

View file

@ -6,7 +6,7 @@
"scripts": { "scripts": {
"start": "parcel src/index.html --https --host localhost.localdomain", "start": "parcel src/index.html --https --host localhost.localdomain",
"lint": "eslint src", "lint": "eslint src",
"lint:fix": "eslint src --fix", "lint:fix": "eslint src netlify --fix",
"types": "tsc --noEmit", "types": "tsc --noEmit",
"build": "parcel build src/index.html", "build": "parcel build src/index.html",
"build:bundlesize": "parcel build src/index.html --reporter @parcel/reporter-bundle-analyzer" "build:bundlesize": "parcel build src/index.html --reporter @parcel/reporter-bundle-analyzer"

View file

@ -15,7 +15,7 @@ export default function App(): ReactNode {
useEffect(() => { useEffect(() => {
if (state.loaded) return if (state.loaded) return
setFeedUrls(restoreSettings().feedUrls) setFeedUrls(restoreSettings()?.feedUrls ?? [])
}, [state.loaded, setFeedUrls]) }, [state.loaded, setFeedUrls])
useEffect(() => { useEffect(() => {

View file

@ -8,7 +8,7 @@ import Button from '@material-ui/core/Button'
import CheckCircleOutlineIcon from '@material-ui/icons/CheckCircleOutline' import CheckCircleOutlineIcon from '@material-ui/icons/CheckCircleOutline'
import ErrorOutlineIcon from '@material-ui/icons/ErrorOutline' import ErrorOutlineIcon from '@material-ui/icons/ErrorOutline'
import { persistSettings } from './utils/persistence' import { storeSettings } from './utils/persistence'
interface Props { interface Props {
feedUrls: string[] feedUrls: string[]
@ -73,7 +73,7 @@ export default function SettingsPanel(props: Props): ReactNode {
const validUrls = feedUrlsForm.filter(isValidUrl) const validUrls = feedUrlsForm.filter(isValidUrl)
setFeedUrls(validUrls) setFeedUrls(validUrls)
setFeedUrlsForm(validUrls) setFeedUrlsForm(validUrls)
persistSettings({ feedUrls: validUrls }) storeSettings({ feedUrls: validUrls })
}} }}
> >
Save Save

View file

@ -1,5 +1,8 @@
<html> <html>
<head></head> <head>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<style> <style>
body { background-color: #f5f5f5 } body { background-color: #f5f5f5 }
</style> </style>

View file

@ -10,3 +10,12 @@ export interface State {
feedUrls: string[] feedUrls: string[]
activePanel: string activePanel: string
} }
export interface RSSData {
items: Item[]
lastPushed: Date
}
export interface Settings {
feedUrls: string[]
}

View file

@ -1,10 +1,9 @@
import axios from 'axios' import axios from 'axios'
import { parseFeed } from 'htmlparser2' import { parseFeed } from 'htmlparser2'
import md5 from 'crypto-js/md5'
import type { Item } from '../types' import type { Item } from '../types'
import { persistResults, restoreResults } from './persistence' import { restoreRssData, storeRssData } from './persistence'
function processFeedXML(feed) { function processFeedXML(feed) {
return feed.items.reduce((items, feedItem) => { return feed.items.reduce((items, feedItem) => {
@ -18,6 +17,12 @@ function processFeedXML(feed) {
}, []) }, [])
} }
function getRefetchThreshold() {
const refetchThreshold = new Date()
refetchThreshold.setMinutes(refetchThreshold.getMinutes() - 10)
return refetchThreshold
}
/* /*
* Fetches RSS feeds from the given url list. If feed data exists in * Fetches RSS feeds from the given url list. If feed data exists in
* localStorage, it is used as a basis for the final list and any new * localStorage, it is used as a basis for the final list and any new
@ -32,17 +37,16 @@ export default async function fetchFeeds(
): Item[] { ): Item[] {
const feed = await Promise.all( const feed = await Promise.all(
feedUrls.map(async (url: string) => { feedUrls.map(async (url: string) => {
const urlHash = md5(url) const storedFeedData = restoreRssData(url)
const storedFeedData = restoreResults(urlHash)
const { items, lastPush } = storedFeedData || { items: [] } const items = storedFeedData?.items || []
const lastPush = storedFeedData?.lastPush
// TODO: Constantize if (!forceRefetch && lastPush > getRefetchThreshold()) return items
if (!forceRefetch && lastPush > Date.now() - 10 * 60 * 1000)
return items
const response = await axios.get(url)
const response = await axios.get('/.netlify/functions/rss-proxy', {
params: { url },
})
const availableFeedItems = [...items] const availableFeedItems = [...items]
try { try {
@ -58,9 +62,9 @@ export default async function fetchFeeds(
}) })
} catch (e) { } catch (e) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.error(e) console.error(e.response)
} }
persistResults(urlHash, availableFeedItems) storeRssData(url, availableFeedItems)
return availableFeedItems return availableFeedItems
}), }),

View file

@ -1,18 +1,36 @@
export function persistSettings(settings) { import md5 from 'crypto-js/md5'
window.localStorage.setItem('settings', JSON.stringify(settings))
import type { RSSData, Settings } from '../types'
const SETTINGS_KEY = 'settings'
const RSS_DATA_KEY_PREFIX = 'savedItems_'
function storeToLocal(key: string, data): void {
window.localStorage.setItem(key, JSON.stringify(data))
} }
export function restoreSettings() { function restoreFromLocal(key: string) {
return JSON.parse(window.localStorage.getItem('settings') ?? {}) const restored = window.localStorage.getItem(key)
try {
return JSON.parse(restored)
} catch (e) {
return null
}
}
export function storeSettings(settings: Settings): void {
return storeToLocal(SETTINGS_KEY, settings)
} }
export function persistResults(h: string, items: Item[]) { export function restoreSettings(): Settings {
window.localStorage.setItem( return restoreFromLocal(SETTINGS_KEY)
`savedItems_${h}`,
JSON.stringify({ items, lastPush: Date.now() }),
)
} }
export function restoreResults(key): Item[] { export function storeRssData(url: string, items: Item[]): void {
return JSON.parse(window.localStorage.getItem(`savedItems_${key}`)) const key = RSS_DATA_KEY_PREFIX + md5(url)
storeToLocal(key, { items, lastPush: Date.now() })
}
export function restoreRssData(url: string): RSSData {
const key = RSS_DATA_KEY_PREFIX + md5(url)
return restoreFromLocal(key)
} }