favicon-stealer 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getDomain = void 0;
4
+ const getDomain = (url) => {
5
+ try {
6
+ // Add https:// protocol if not present
7
+ const urlWithProtocol = url.startsWith('http') ? url : `https://${url}`;
8
+ const domain = new URL(urlWithProtocol).hostname;
9
+ // Remove 'www.' prefix if exists
10
+ return domain.replace(/^www\./, '');
11
+ }
12
+ catch (error) {
13
+ // Return original input if URL parsing fails
14
+ return url;
15
+ }
16
+ };
17
+ exports.getDomain = getDomain;
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ "use client";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ const utils_1 = require("@/lib/utils");
38
+ const react_1 = __importStar(require("react"));
39
+ const Favicon = ({ url, size = 32, className = "", timeout = 1000, // 1 second
40
+ }) => {
41
+ const domain = (0, utils_1.getDomain)(url);
42
+ const [imgSrc, setImgSrc] = (0, react_1.useState)(`https://${domain}/logo.svg`);
43
+ const [fallbackIndex, setFallbackIndex] = (0, react_1.useState)(0);
44
+ const [isLoading, setIsLoading] = (0, react_1.useState)(true);
45
+ const [hasError, setHasError] = (0, react_1.useState)(false);
46
+ const fallbackSources = [
47
+ `https://${domain}/logo.svg`,
48
+ `https://${domain}/logo.png`,
49
+ `https://${domain}/apple-touch-icon.png`,
50
+ `https://${domain}/apple-touch-icon-precomposed.png`,
51
+ `https://www.google.com/s2/favicons?domain=${domain}&sz=64`,
52
+ `https://icons.duckduckgo.com/ip3/${domain}.ico`,
53
+ `https://${domain}/favicon.ico`,
54
+ ];
55
+ (0, react_1.useEffect)(() => {
56
+ let timeoutId;
57
+ if (isLoading) {
58
+ timeoutId = setTimeout(() => {
59
+ handleError();
60
+ }, timeout);
61
+ }
62
+ return () => {
63
+ if (timeoutId) {
64
+ clearTimeout(timeoutId);
65
+ }
66
+ };
67
+ }, [imgSrc, isLoading]);
68
+ const handleError = () => {
69
+ const nextIndex = fallbackIndex + 1;
70
+ if (nextIndex < fallbackSources.length) {
71
+ setFallbackIndex(nextIndex);
72
+ setImgSrc(fallbackSources[nextIndex]);
73
+ setIsLoading(true);
74
+ }
75
+ else {
76
+ setHasError(true);
77
+ setIsLoading(false);
78
+ }
79
+ };
80
+ const handleLoad = () => {
81
+ setIsLoading(false);
82
+ setHasError(false);
83
+ };
84
+ return (react_1.default.createElement("div", { className: `relative inline-block ${className}`, style: { width: size, height: size } },
85
+ isLoading && (react_1.default.createElement("div", { className: "absolute inset-0 animate-pulse" },
86
+ react_1.default.createElement("div", { className: "w-full h-full rounded-md bg-gray-200/60" }))),
87
+ react_1.default.createElement("img", { src: imgSrc, alt: `${domain} logo`, width: size, height: size, onError: handleError, onLoad: handleLoad, className: `inline-block transition-opacity duration-300 ${isLoading ? "opacity-0" : "opacity-100"}`, style: {
88
+ objectFit: "contain",
89
+ display: hasError ? "none" : "inline-block",
90
+ } }),
91
+ hasError && (react_1.default.createElement("div", { className: "w-full h-full flex items-center justify-center bg-gray-100 rounded-md", style: { fontSize: `${size * 0.5}px` } }, domain.charAt(0).toUpperCase()))));
92
+ };
93
+ exports.default = Favicon;
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.Favicon = void 0;
7
+ var Favicon_1 = require("./Favicon");
8
+ Object.defineProperty(exports, "Favicon", { enumerable: true, get: function () { return __importDefault(Favicon_1).default; } });
@@ -0,0 +1 @@
1
+ export declare const getDomain: (url: string) => string;
@@ -0,0 +1,9 @@
1
+ import React from "react";
2
+ interface IProps {
3
+ url: string;
4
+ size?: number;
5
+ className?: string;
6
+ timeout?: number;
7
+ }
8
+ declare const Favicon: ({ url, size, className, timeout, }: IProps) => React.JSX.Element;
9
+ export default Favicon;
@@ -0,0 +1 @@
1
+ export { default as Favicon } from './Favicon';
@@ -0,0 +1,12 @@
1
+ export const getDomain = (url: string) => {
2
+ try {
3
+ // Add https:// protocol if not present
4
+ const urlWithProtocol = url.startsWith('http') ? url : `https://${url}`;
5
+ const domain = new URL(urlWithProtocol).hostname;
6
+ // Remove 'www.' prefix if exists
7
+ return domain.replace(/^www\./, '');
8
+ } catch (error) {
9
+ // Return original input if URL parsing fails
10
+ return url;
11
+ }
12
+ };
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "favicon-stealer",
3
+ "version": "1.0.0",
4
+ "main": "index.js",
5
+ "scripts": {
6
+ "test": "echo \"Error: no test specified\" && exit 1",
7
+ "build": "tsc",
8
+ "prepublishOnly": "npm run build"
9
+ },
10
+ "keywords": [],
11
+ "author": "",
12
+ "license": "ISC",
13
+ "description": "",
14
+ "dependencies": {
15
+ "react": "^19.0.0",
16
+ "react-dom": "^19.0.0"
17
+ },
18
+ "devDependencies": {
19
+ "@types/react": "^19.0.7",
20
+ "@types/react-dom": "^19.0.3",
21
+ "typescript": "^5.7.3"
22
+ }
23
+ }
@@ -0,0 +1,109 @@
1
+ "use client";
2
+
3
+ import { getDomain } from "@/lib/utils";
4
+ import React, { useEffect, useState } from "react";
5
+
6
+ interface IProps {
7
+ url: string;
8
+ size?: number;
9
+ className?: string;
10
+ timeout?: number;
11
+ }
12
+
13
+ const Favicon = ({
14
+ url,
15
+ size = 32,
16
+ className = "",
17
+ timeout = 1000, // 1 second
18
+ }: IProps) => {
19
+ const domain = getDomain(url);
20
+ const [imgSrc, setImgSrc] = useState(`https://${domain}/logo.svg`);
21
+ const [fallbackIndex, setFallbackIndex] = useState(0);
22
+ const [isLoading, setIsLoading] = useState(true);
23
+ const [hasError, setHasError] = useState(false);
24
+
25
+ const fallbackSources = [
26
+ `https://${domain}/logo.svg`,
27
+ `https://${domain}/logo.png`,
28
+ `https://${domain}/apple-touch-icon.png`,
29
+ `https://${domain}/apple-touch-icon-precomposed.png`,
30
+ `https://www.google.com/s2/favicons?domain=${domain}&sz=64`,
31
+ `https://icons.duckduckgo.com/ip3/${domain}.ico`,
32
+ `https://${domain}/favicon.ico`,
33
+ ];
34
+
35
+ useEffect(() => {
36
+ let timeoutId: any;
37
+
38
+ if (isLoading) {
39
+ timeoutId = setTimeout(() => {
40
+ handleError();
41
+ }, timeout);
42
+ }
43
+
44
+ return () => {
45
+ if (timeoutId) {
46
+ clearTimeout(timeoutId);
47
+ }
48
+ };
49
+ }, [imgSrc, isLoading]);
50
+
51
+ const handleError = () => {
52
+ const nextIndex = fallbackIndex + 1;
53
+ if (nextIndex < fallbackSources.length) {
54
+ setFallbackIndex(nextIndex);
55
+ setImgSrc(fallbackSources[nextIndex]);
56
+ setIsLoading(true);
57
+ } else {
58
+ setHasError(true);
59
+ setIsLoading(false);
60
+ }
61
+ };
62
+
63
+ const handleLoad = () => {
64
+ setIsLoading(false);
65
+ setHasError(false);
66
+ };
67
+
68
+ return (
69
+ <div
70
+ className={`relative inline-block ${className}`}
71
+ style={{ width: size, height: size }}
72
+ >
73
+ {/* placeholder */}
74
+ {isLoading && (
75
+ <div className="absolute inset-0 animate-pulse">
76
+ <div className="w-full h-full rounded-md bg-gray-200/60" />
77
+ </div>
78
+ )}
79
+
80
+ <img
81
+ src={imgSrc}
82
+ alt={`${domain} logo`}
83
+ width={size}
84
+ height={size}
85
+ onError={handleError}
86
+ onLoad={handleLoad}
87
+ className={`inline-block transition-opacity duration-300 ${
88
+ isLoading ? "opacity-0" : "opacity-100"
89
+ }`}
90
+ style={{
91
+ objectFit: "contain",
92
+ display: hasError ? "none" : "inline-block",
93
+ }}
94
+ />
95
+
96
+ {/* Fallback: Display first letter of domain when all image sources fail */}
97
+ {hasError && (
98
+ <div
99
+ className="w-full h-full flex items-center justify-center bg-gray-100 rounded-md"
100
+ style={{ fontSize: `${size * 0.5}px` }}
101
+ >
102
+ {domain.charAt(0).toUpperCase()}
103
+ </div>
104
+ )}
105
+ </div>
106
+ );
107
+ };
108
+
109
+ export default Favicon;
package/src/index.tsx ADDED
@@ -0,0 +1 @@
1
+ export { default as Favicon } from './Favicon';
package/tsconfig.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "outDir": "dist",
4
+ "module": "commonjs",
5
+ "target": "es2015",
6
+ "jsx": "react",
7
+ "declaration": true,
8
+ "declarationDir": "dist/types",
9
+ "strict": true,
10
+ "moduleResolution": "node",
11
+ "baseUrl": ".",
12
+ "paths": {
13
+ "@/*": ["./*"]
14
+ },
15
+ "esModuleInterop": true
16
+ },
17
+ "include": [
18
+ "src",
19
+ "lib"
20
+ ]
21
+ }