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
.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": {
"start": "parcel src/index.html --https --host localhost.localdomain",
"lint": "eslint src",
"lint:fix": "eslint src --fix",
"lint:fix": "eslint src netlify --fix",
"types": "tsc --noEmit",
"build": "parcel build src/index.html",
"build:bundlesize": "parcel build src/index.html --reporter @parcel/reporter-bundle-analyzer"

View file

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

View file

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

View file

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

View file

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

View file

@ -1,18 +1,36 @@
export function persistSettings(settings) {
window.localStorage.setItem('settings', JSON.stringify(settings))
import md5 from 'crypto-js/md5'
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() {
return JSON.parse(window.localStorage.getItem('settings') ?? {})
function restoreFromLocal(key: string) {
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[]) {
window.localStorage.setItem(
`savedItems_${h}`,
JSON.stringify({ items, lastPush: Date.now() }),
)
export function restoreSettings(): Settings {
return restoreFromLocal(SETTINGS_KEY)
}
export function restoreResults(key): Item[] {
return JSON.parse(window.localStorage.getItem(`savedItems_${key}`))
export function storeRssData(url: string, items: Item[]): void {
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)
}