Skip to content

Commit

Permalink
Fix: HTTP strategy for absolute icon paths (#8)
Browse files Browse the repository at this point in the history
  • Loading branch information
dennypradipta authored Jun 4, 2024
1 parent 7b21e58 commit fbbbbc5
Show file tree
Hide file tree
Showing 5 changed files with 37 additions and 41 deletions.
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Favicon Fetcher

`favicon-fetcher` is a utility to fetch a website's favicon by using multiple strategies (the favicon.ico method, the DuckDuckGo API method, and the Google API method).
`favicon-fetcher` is a utility to fetch a website's favicon by using multiple strategies. By default, it will fetch the `href` attribute of the first `<link rel="icon">` element in the target's HTML file.

## How to Use

Expand All @@ -21,7 +21,7 @@ console.log(result1) // returns a binary or a URL

const options = {
strategies: [EStrategies.duckduckgo, EStrategies.default], // use the DuckDuckGo API and default method
output: 'url', // can be 'url' or 'buffer'
output: 'url' // can be 'url' or 'buffer'
}

const result2 = await getFavicon('https://www.google.com', options) // use some strategies
Expand All @@ -30,10 +30,10 @@ console.log(result2) // returns a binary or URL from either DuckDuckGo API or de

## Options

| Options | Type | Description | Default |
| ---------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- |
| strategies | `string[]` | Define the strategies that will be used to fetch the favicon. Each strategy will be run sequentially. Currently available strategies are: `default`, `http`, `duckduckgo`, `google` | `['default', 'http', 'duckduckgo', 'google']` |
| output | `string` | Define the output format of the fetched favicon. Can be either `url` or `buffer`. | `'url'` |
| Options | Type | Description | Default |
| ---------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- |
| strategies | `string[]` | Define the strategies that will be used to fetch the favicon. Each strategy will be run sequentially. Currently available strategies are: `http`, `duckduckgo`, `google`, and `default`. Strategies defined will be run sequentially. | `['http', 'duckduckgo', 'google', 'default']` |
| output | `string` | Define the output format of the fetched favicon. Can be either `url` or `buffer`. URL will return the favicon URL, buffer will return the image of the favicon | `'url'` |

## Contributing

Expand Down
6 changes: 3 additions & 3 deletions src/get-favicon.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import fetch from 'node-fetch'
import d from 'debug'
import { handleHttpStrategy, handleOtherStrategies } from './handlers'
import d from 'debug'

// Initialize the debug instance
const debug = d('favicon')
Expand Down Expand Up @@ -58,13 +58,13 @@ export async function getFavicon(

// Define available strategies for fetching the favicon
const availableStrategies: IStrategy[] = [
{ name: 'default', url: `${url}/favicon.ico` },
{ name: 'http', url },
{ name: 'duckduckgo', url: `https://icons.duckduckgo.com/ip3/${url}.ico` },
{
name: 'google',
url: `https://s2.googleusercontent.com/s2/favicons?domain=${url}`
}
},
{ name: 'default', url: `${url}/favicon.ico` }
]

// Filter strategies based on options provided
Expand Down
12 changes: 4 additions & 8 deletions src/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,10 @@ export async function handleHttpStrategy(
?.getAttribute('href')
if (!iconPath) return null

let iconURL: string
try {
const isAbsoluteURL = new URL(iconPath)
if (isAbsoluteURL) {
iconURL = isAbsoluteURL.href
}
} catch (e) {
iconURL = `${baseUrl}${iconPath}`
let iconURL: string = `${baseUrl}${iconPath.replace(baseUrl, '')}`
const isAbsoluteURL = new URL(iconPath, baseUrl)
if (isAbsoluteURL) {
iconURL = new URL(iconPath, baseUrl).href
}

if (output === 'buffer') {
Expand Down
32 changes: 16 additions & 16 deletions tests/get-favicon.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,20 @@ describe('Get favicon function', () => {
const result = await getFavicon('https://github.com')

expect(result).not.toBeNull()
expect(result).toBe('https://github.com/favicon.ico')
expect(result).toBe('https://github.githubassets.com/favicons/favicon.svg')
})

it('should get the favicon correctly without options with buffer output', async () => {
const result = await getFavicon('https://github.com', {
output: 'buffer',
output: 'buffer'
})

expect(result).not.toBeNull()
})

it('should get the favicon correctly using the default strategy', async () => {
const result = await getFavicon('https://github.com', {
strategies: ['default' as EStrategies],
strategies: ['default' as EStrategies]
})

expect(result).not.toBeNull()
Expand All @@ -37,15 +37,15 @@ describe('Get favicon function', () => {
it('should get the favicon correctly using the default strategy with buffer output', async () => {
const result = await getFavicon('https://github.com', {
strategies: ['default' as EStrategies],
output: 'buffer',
output: 'buffer'
})

expect(result).not.toBeNull()
})

it('should get the favicon correctly using the http strategy', async () => {
const result = await getFavicon('https://github.com', {
strategies: ['http' as EStrategies],
strategies: ['http' as EStrategies]
})

expect(result).not.toBeNull()
Expand All @@ -55,55 +55,55 @@ describe('Get favicon function', () => {
it('should get the favicon correctly using the http strategy with buffer output', async () => {
const result = await getFavicon('https://github.com', {
strategies: ['http' as EStrategies],
output: 'buffer',
output: 'buffer'
})

expect(result).not.toBeNull()
})

it('should get the favicon correctly using the duckduckgo strategy', async () => {
const result = await getFavicon('https://github.com', {
strategies: ['duckduckgo' as EStrategies],
strategies: ['duckduckgo' as EStrategies]
})

expect(result).not.toBeNull()
expect(result).toBe(
'https://icons.duckduckgo.com/ip3/https://github.com.ico',
'https://icons.duckduckgo.com/ip3/https://github.com.ico'
)
})

it('should get the favicon correctly using the duckduckgo strategy with buffer output', async () => {
const result = await getFavicon('https://github.com', {
strategies: ['duckduckgo' as EStrategies],
output: 'buffer',
output: 'buffer'
})

expect(result).not.toBeNull()
})

it('should get the favicon correctly using the google strategy', async () => {
const result = await getFavicon('https://github.com', {
strategies: ['google' as EStrategies],
strategies: ['google' as EStrategies]
})

expect(result).not.toBeNull()
expect(result).toBe(
'https://s2.googleusercontent.com/s2/favicons?domain=https://github.com',
'https://s2.googleusercontent.com/s2/favicons?domain=https://github.com'
)
})

it('should get the favicon correctly using the google strategy with buffer output', async () => {
const result = await getFavicon('https://github.com', {
strategies: ['google' as EStrategies],
output: 'buffer',
output: 'buffer'
})

expect(result).not.toBeNull()
})

it('should not get the favicon correctly using unsupported strategy', async () => {
const result = await getFavicon('https://github.com', {
strategies: ['bing' as EStrategies],
strategies: ['bing' as EStrategies]
})

expect(result).toBeNull()
Expand All @@ -112,7 +112,7 @@ describe('Get favicon function', () => {
it('should not get the favicon correctly using unsupported strategy with buffer output', async () => {
const result = await getFavicon('https://github.com', {
strategies: ['bing' as EStrategies],
output: 'buffer',
output: 'buffer'
})

expect(result).toBeNull()
Expand All @@ -131,7 +131,7 @@ describe('Get favicon function', () => {
it('should not get the favicon correctly because of bad URL with buffer output', async () => {
try {
await getFavicon('nicelydone', {
output: 'buffer',
output: 'buffer'
})
// If the function doesn't throw an error, fail the test
expect(true).toBe(false)
Expand All @@ -145,7 +145,7 @@ describe('Get favicon function', () => {

const result = await getFavicon('https://github.com', {
strategies: ['http' as EStrategies],
output: 'buffer',
output: 'buffer'
})

// Check that result is null because of fetch error
Expand Down
16 changes: 8 additions & 8 deletions tests/handlers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ describe('handleHttpStrategy', () => {
const result = await handleHttpStrategy(
response,
'http://example.com',
'url',
'url'
)
expect(result).toBeNull()
})
Expand All @@ -25,31 +25,31 @@ describe('handleHttpStrategy', () => {
const result = await handleHttpStrategy(
response,
'http://example.com',
'url',
'url'
)
expect(result).toBeNull()
})

it('should return icon URL for relative path', async () => {
const response = new Response(
'<html><head><link rel="icon" href="/favicon.ico"/></head><body></body></html>',
'<html><head><link rel="icon" href="/favicon.ico"/></head><body></body></html>'
)
const result = await handleHttpStrategy(
response,
'http://example.com',
'url',
'url'
)
expect(result).toBe('http://example.com/favicon.ico')
})

it('should return icon URL for absolute path', async () => {
const response = new Response(
'<html><head><link rel="icon" href="http://example.com/favicon.ico"/></head><body></body></html>',
'<html><head><link rel="icon" href="http://example.com/favicon.ico"/></head><body></body></html>'
)
const result = await handleHttpStrategy(
response,
'http://example.com',
'url',
'url'
)
expect(result).toBe('http://example.com/favicon.ico')
})
Expand All @@ -62,7 +62,7 @@ describe('handleOtherStrategies', () => {
const result = await handleOtherStrategies(
response,
'http://example.com',
'buffer',
'buffer'
)

expect(result).toEqual(Buffer.from(arrayBuffer))
Expand All @@ -73,7 +73,7 @@ describe('handleOtherStrategies', () => {
const result = await handleOtherStrategies(
response,
'http://example.com',
'url',
'url'
)

expect(result).toBe('http://example.com')
Expand Down

0 comments on commit fbbbbc5

Please sign in to comment.