@realfavicongenerator/check-favicon 0.4.7 → 0.4.8
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.
- package/LICENSE +21 -0
- package/dist/desktop/desktop.d.ts +4 -4
- package/dist/desktop/desktop.js +40 -14
- package/dist/desktop/desktop.test.js +149 -64
- package/dist/desktop/ico.d.ts +2 -2
- package/dist/desktop/ico.js +26 -4
- package/dist/google.d.ts +8 -0
- package/dist/google.js +107 -0
- package/dist/google.test.js +71 -0
- package/dist/helper.d.ts +8 -1
- package/dist/helper.js +26 -7
- package/dist/helper.test.js +18 -3
- package/dist/touch-icon.js +1 -1
- package/dist/types.d.ts +29 -1
- package/dist/types.js +8 -0
- package/dist/web-app-manifest.js +1 -1
- package/package.json +6 -4
- package/src/desktop/desktop.test.ts +152 -64
- package/src/desktop/desktop.ts +46 -19
- package/src/desktop/ico.ts +31 -7
- package/src/google.test.ts +82 -0
- package/src/google.ts +103 -0
- package/src/helper.test.ts +19 -4
- package/src/helper.ts +35 -7
- package/src/touch-icon.ts +3 -3
- package/src/types.ts +33 -1
- package/src/web-app-manifest.ts +3 -3
- package/dist/web-manifest.d.ts +0 -4
- package/dist/web-manifest.js +0 -262
- package/dist/web-manifest.test.js +0 -172
- /package/dist/{web-manifest.test.d.ts → google.test.d.ts} +0 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 RealFaviconGenerator
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DesktopFaviconReport, DesktopSingleReport, Fetcher } from "../types";
|
|
2
2
|
import { HTMLElement } from 'node-html-parser';
|
|
3
3
|
export declare const PngFaviconFileSize = 96;
|
|
4
|
-
export declare const checkSvgFavicon: (baseUrl: string, head: HTMLElement | null, fetcher?: Fetcher) => Promise<
|
|
5
|
-
export declare const checkSvgFaviconFile: (baseUrl: string, url: string, fetcher: Fetcher) => Promise<
|
|
6
|
-
export declare const checkPngFavicon: (baseUrl: string, head: HTMLElement | null, fetcher?: Fetcher) => Promise<
|
|
4
|
+
export declare const checkSvgFavicon: (baseUrl: string, head: HTMLElement | null, fetcher?: Fetcher) => Promise<DesktopSingleReport>;
|
|
5
|
+
export declare const checkSvgFaviconFile: (baseUrl: string, url: string, fetcher: Fetcher) => Promise<DesktopSingleReport>;
|
|
6
|
+
export declare const checkPngFavicon: (baseUrl: string, head: HTMLElement | null, fetcher?: Fetcher) => Promise<DesktopSingleReport>;
|
|
7
7
|
export declare const checkDesktopFavicon: (baseUrl: string, head: HTMLElement | null, fetcher?: Fetcher) => Promise<DesktopFaviconReport>;
|
package/dist/desktop/desktop.js
CHANGED
|
@@ -26,7 +26,10 @@ const checkSvgFavicon = (baseUrl_1, head_1, ...args_1) => __awaiter(void 0, [bas
|
|
|
26
26
|
id: types_1.MessageId.noHead,
|
|
27
27
|
text: 'No <head> element'
|
|
28
28
|
});
|
|
29
|
-
return
|
|
29
|
+
return {
|
|
30
|
+
messages,
|
|
31
|
+
icon: { content: null, url: null, width: null, height: null }
|
|
32
|
+
};
|
|
30
33
|
}
|
|
31
34
|
const svgs = head === null || head === void 0 ? void 0 : head.querySelectorAll("link[rel='icon'][type='image/svg+xml']");
|
|
32
35
|
if (svgs.length === 0) {
|
|
@@ -58,17 +61,26 @@ const checkSvgFavicon = (baseUrl_1, head_1, ...args_1) => __awaiter(void 0, [bas
|
|
|
58
61
|
});
|
|
59
62
|
}
|
|
60
63
|
else {
|
|
61
|
-
const
|
|
62
|
-
return
|
|
64
|
+
const iconReport = yield (0, exports.checkSvgFaviconFile)(baseUrl, href, fetcher);
|
|
65
|
+
return {
|
|
66
|
+
messages: [...messages, ...iconReport.messages],
|
|
67
|
+
icon: iconReport.icon
|
|
68
|
+
};
|
|
63
69
|
}
|
|
64
70
|
}
|
|
65
|
-
return
|
|
71
|
+
return {
|
|
72
|
+
messages,
|
|
73
|
+
icon: { content: null, url: null, width: null, height: null }
|
|
74
|
+
};
|
|
66
75
|
});
|
|
67
76
|
exports.checkSvgFavicon = checkSvgFavicon;
|
|
68
77
|
const checkSvgFaviconFile = (baseUrl, url, fetcher) => __awaiter(void 0, void 0, void 0, function* () {
|
|
69
78
|
const messages = [];
|
|
70
79
|
const svgUrl = (0, helper_1.mergeUrlAndPath)(baseUrl, url);
|
|
71
80
|
const res = yield fetcher(svgUrl, 'image/svg+xml');
|
|
81
|
+
let content;
|
|
82
|
+
let width = null;
|
|
83
|
+
let height = null;
|
|
72
84
|
if (res.status === 404) {
|
|
73
85
|
messages.push({
|
|
74
86
|
status: types_1.CheckerStatus.Error,
|
|
@@ -89,24 +101,33 @@ const checkSvgFaviconFile = (baseUrl, url, fetcher) => __awaiter(void 0, void 0,
|
|
|
89
101
|
id: types_1.MessageId.svgFaviconDownloadable,
|
|
90
102
|
text: `The SVG favicon is accessible at \`${url}\``
|
|
91
103
|
});
|
|
92
|
-
|
|
104
|
+
content = yield (0, helper_1.readableStreamToString)(res.readableStream);
|
|
93
105
|
const meta = yield (0, sharp_1.default)(Buffer.from(content)).metadata();
|
|
94
|
-
|
|
106
|
+
width = meta.width || null;
|
|
107
|
+
height = meta.height || null;
|
|
108
|
+
if (width && height && width !== height) {
|
|
95
109
|
messages.push({
|
|
96
110
|
status: types_1.CheckerStatus.Error,
|
|
97
111
|
id: types_1.MessageId.svgFaviconNotSquare,
|
|
98
|
-
text: `The SVG is not square (${
|
|
112
|
+
text: `The SVG is not square (${width}x${height})`
|
|
99
113
|
});
|
|
100
114
|
}
|
|
101
115
|
else {
|
|
102
116
|
messages.push({
|
|
103
117
|
status: types_1.CheckerStatus.Ok,
|
|
104
118
|
id: types_1.MessageId.svgFaviconSquare,
|
|
105
|
-
text: `The SVG is square (${
|
|
119
|
+
text: `The SVG is square (${width}x${height})`
|
|
106
120
|
});
|
|
107
121
|
}
|
|
108
122
|
}
|
|
109
|
-
return
|
|
123
|
+
return {
|
|
124
|
+
messages,
|
|
125
|
+
icon: {
|
|
126
|
+
content: content ? yield (0, helper_1.bufferToDataUrl)(Buffer.from(content), 'image/svg+xml') : null,
|
|
127
|
+
url: svgUrl,
|
|
128
|
+
width, height
|
|
129
|
+
}
|
|
130
|
+
};
|
|
110
131
|
});
|
|
111
132
|
exports.checkSvgFaviconFile = checkSvgFaviconFile;
|
|
112
133
|
const checkPngFavicon = (baseUrl_2, head_2, ...args_2) => __awaiter(void 0, [baseUrl_2, head_2, ...args_2], void 0, function* (baseUrl, head, fetcher = helper_1.fetchFetcher) {
|
|
@@ -117,7 +138,7 @@ const checkPngFavicon = (baseUrl_2, head_2, ...args_2) => __awaiter(void 0, [bas
|
|
|
117
138
|
id: types_1.MessageId.noHead,
|
|
118
139
|
text: 'No <head> element'
|
|
119
140
|
});
|
|
120
|
-
return { messages, icon: null };
|
|
141
|
+
return { messages, icon: { content: null, url: null, width: null, height: null } };
|
|
121
142
|
}
|
|
122
143
|
const icons = head === null || head === void 0 ? void 0 : head.querySelectorAll("link[rel='icon'][type='image/png']");
|
|
123
144
|
if (icons.length === 0) {
|
|
@@ -198,16 +219,21 @@ const checkPngFavicon = (baseUrl_2, head_2, ...args_2) => __awaiter(void 0, [bas
|
|
|
198
219
|
}
|
|
199
220
|
}
|
|
200
221
|
}
|
|
201
|
-
return { messages, icon: null };
|
|
222
|
+
return { messages, icon: { content: null, url: null, width: null, height: null } };
|
|
202
223
|
});
|
|
203
224
|
exports.checkPngFavicon = checkPngFavicon;
|
|
204
225
|
const checkDesktopFavicon = (baseUrl_3, head_3, ...args_3) => __awaiter(void 0, [baseUrl_3, head_3, ...args_3], void 0, function* (baseUrl, head, fetcher = helper_1.fetchFetcher) {
|
|
205
|
-
const
|
|
226
|
+
const svgReport = yield (0, exports.checkSvgFavicon)(baseUrl, head, fetcher);
|
|
206
227
|
const pngReport = yield (0, exports.checkPngFavicon)(baseUrl, head, fetcher);
|
|
207
228
|
const icoReport = yield (0, ico_1.checkIcoFavicon)(baseUrl, head, fetcher);
|
|
208
229
|
return {
|
|
209
|
-
messages: [...
|
|
210
|
-
icon: pngReport.icon
|
|
230
|
+
messages: [...svgReport.messages, ...pngReport.messages, ...icoReport.messages],
|
|
231
|
+
icon: pngReport.icon ? pngReport.icon.content : null,
|
|
232
|
+
icons: {
|
|
233
|
+
png: pngReport.icon,
|
|
234
|
+
ico: icoReport.icon,
|
|
235
|
+
svg: svgReport.icon
|
|
236
|
+
}
|
|
211
237
|
};
|
|
212
238
|
});
|
|
213
239
|
exports.checkDesktopFavicon = checkDesktopFavicon;
|
|
@@ -17,56 +17,107 @@ const test_helper_1 = require("../test-helper");
|
|
|
17
17
|
const runSvgTest = (headFragment_1, output_1, ...args_1) => __awaiter(void 0, [headFragment_1, output_1, ...args_1], void 0, function* (headFragment, output, fetchDatabase = {}) {
|
|
18
18
|
const root = headFragment ? (0, node_html_parser_1.parse)(headFragment) : null;
|
|
19
19
|
const result = yield (0, desktop_1.checkSvgFavicon)('https://example.com/', root, (0, test_helper_1.testFetcher)(fetchDatabase));
|
|
20
|
-
const filteredMessages = result.map(m => ({ status: m.status, id: m.id }));
|
|
21
|
-
expect(filteredMessages).toEqual(output);
|
|
20
|
+
const filteredMessages = result.messages.map(m => ({ status: m.status, id: m.id }));
|
|
21
|
+
expect(filteredMessages).toEqual(output.messages);
|
|
22
22
|
});
|
|
23
23
|
test('checkSvgFavicon - noHead', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
24
|
-
yield runSvgTest(null,
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
yield runSvgTest(null, {
|
|
25
|
+
messages: [{
|
|
26
|
+
status: types_1.CheckerStatus.Error,
|
|
27
|
+
id: types_1.MessageId.noHead,
|
|
28
|
+
}],
|
|
29
|
+
icons: {
|
|
30
|
+
png: null,
|
|
31
|
+
ico: null,
|
|
32
|
+
svg: null,
|
|
33
|
+
}
|
|
34
|
+
});
|
|
28
35
|
}));
|
|
29
36
|
test('checkSvgFavicon - noSvgFavicon', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
30
|
-
yield runSvgTest(`<title>
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
37
|
+
yield runSvgTest(`<title>Some text</title>`, {
|
|
38
|
+
messages: [{
|
|
39
|
+
status: types_1.CheckerStatus.Error,
|
|
40
|
+
id: types_1.MessageId.noSvgFavicon,
|
|
41
|
+
}],
|
|
42
|
+
icons: {
|
|
43
|
+
png: null,
|
|
44
|
+
ico: null,
|
|
45
|
+
svg: null,
|
|
46
|
+
}
|
|
47
|
+
});
|
|
34
48
|
}));
|
|
35
49
|
test('checkSvgFavicon - multipleSvgFavicons', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
36
50
|
yield runSvgTest(`
|
|
37
51
|
<link rel="icon" type="image/svg+xml" href="/the-icon.svg" />
|
|
38
52
|
<link rel="icon" type="image/svg+xml" href="/another-icon.svg" />
|
|
39
|
-
`,
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
53
|
+
`, {
|
|
54
|
+
messages: [{
|
|
55
|
+
status: types_1.CheckerStatus.Error,
|
|
56
|
+
id: types_1.MessageId.multipleSvgFavicons,
|
|
57
|
+
}], icons: {
|
|
58
|
+
png: null,
|
|
59
|
+
ico: null,
|
|
60
|
+
svg: null,
|
|
61
|
+
}
|
|
62
|
+
});
|
|
43
63
|
}));
|
|
44
64
|
test('checkSvgFavicon - svgFaviconDeclared & noSvgFaviconHref', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
45
|
-
yield runSvgTest(`<link rel="icon" type="image/svg+xml" />`,
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
65
|
+
yield runSvgTest(`<link rel="icon" type="image/svg+xml" />`, {
|
|
66
|
+
messages: [{
|
|
67
|
+
status: types_1.CheckerStatus.Ok,
|
|
68
|
+
id: types_1.MessageId.svgFaviconDeclared,
|
|
69
|
+
}, {
|
|
70
|
+
status: types_1.CheckerStatus.Error,
|
|
71
|
+
id: types_1.MessageId.noSvgFaviconHref,
|
|
72
|
+
}],
|
|
73
|
+
icons: {
|
|
74
|
+
png: null,
|
|
75
|
+
ico: null,
|
|
76
|
+
svg: null,
|
|
77
|
+
}
|
|
78
|
+
});
|
|
52
79
|
}));
|
|
53
80
|
test('checkSvgFavicon - svgFaviconDeclared & svgFavicon404', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
54
|
-
yield runSvgTest(`<link rel="icon" type="image/svg+xml" href="/the-icon.svg" />`,
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
81
|
+
yield runSvgTest(`<link rel="icon" type="image/svg+xml" href="/the-icon.svg" />`, {
|
|
82
|
+
messages: [{
|
|
83
|
+
status: types_1.CheckerStatus.Ok,
|
|
84
|
+
id: types_1.MessageId.svgFaviconDeclared,
|
|
85
|
+
}, {
|
|
86
|
+
status: types_1.CheckerStatus.Error,
|
|
87
|
+
id: types_1.MessageId.svgFavicon404,
|
|
88
|
+
}],
|
|
89
|
+
icons: {
|
|
90
|
+
png: null,
|
|
91
|
+
ico: null,
|
|
92
|
+
svg: {
|
|
93
|
+
content: null,
|
|
94
|
+
url: 'https://example.com/the-icon.svg',
|
|
95
|
+
width: null,
|
|
96
|
+
height: null,
|
|
97
|
+
},
|
|
98
|
+
}
|
|
99
|
+
});
|
|
61
100
|
}));
|
|
62
101
|
test('checkSvgFavicon - svgFaviconDeclared & svgFaviconCannotGet', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
63
|
-
yield runSvgTest(`<link rel="icon" type="image/svg+xml" href="/the-icon.svg" />`,
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
102
|
+
yield runSvgTest(`<link rel="icon" type="image/svg+xml" href="/the-icon.svg" />`, {
|
|
103
|
+
messages: [{
|
|
104
|
+
status: types_1.CheckerStatus.Ok,
|
|
105
|
+
id: types_1.MessageId.svgFaviconDeclared,
|
|
106
|
+
}, {
|
|
107
|
+
status: types_1.CheckerStatus.Error,
|
|
108
|
+
id: types_1.MessageId.svgFaviconCannotGet,
|
|
109
|
+
}],
|
|
110
|
+
icons: {
|
|
111
|
+
png: null,
|
|
112
|
+
ico: null,
|
|
113
|
+
svg: {
|
|
114
|
+
content: null,
|
|
115
|
+
url: 'https://example.com/the-icon.svg',
|
|
116
|
+
width: null,
|
|
117
|
+
height: null,
|
|
118
|
+
},
|
|
119
|
+
}
|
|
120
|
+
}, {
|
|
70
121
|
'https://example.com/the-icon.svg': {
|
|
71
122
|
status: 403,
|
|
72
123
|
contentType: 'image/svg+xml'
|
|
@@ -75,13 +126,24 @@ test('checkSvgFavicon - svgFaviconDeclared & svgFaviconCannotGet', () => __await
|
|
|
75
126
|
}));
|
|
76
127
|
// For https://github.com/RealFaviconGenerator/core/issues/2
|
|
77
128
|
test('checkSvgFavicon - Protocol-relative URL', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
78
|
-
yield runSvgTest(`<link rel="icon" type="image/svg+xml" href="//example.com/the-icon.svg" />`,
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
129
|
+
yield runSvgTest(`<link rel="icon" type="image/svg+xml" href="//example.com/the-icon.svg" />`, {
|
|
130
|
+
messages: [{
|
|
131
|
+
status: types_1.CheckerStatus.Ok,
|
|
132
|
+
id: types_1.MessageId.svgFaviconDeclared,
|
|
133
|
+
}, {
|
|
134
|
+
status: types_1.CheckerStatus.Error,
|
|
135
|
+
id: types_1.MessageId.svgFaviconCannotGet,
|
|
136
|
+
}], icons: {
|
|
137
|
+
png: null,
|
|
138
|
+
ico: null,
|
|
139
|
+
svg: {
|
|
140
|
+
content: null,
|
|
141
|
+
url: 'https://example.com/the-icon.svg',
|
|
142
|
+
width: null,
|
|
143
|
+
height: null,
|
|
144
|
+
},
|
|
145
|
+
}
|
|
146
|
+
}, {
|
|
85
147
|
'https://example.com/the-icon.svg': {
|
|
86
148
|
status: 403,
|
|
87
149
|
contentType: 'image/svg+xml'
|
|
@@ -91,18 +153,30 @@ test('checkSvgFavicon - Protocol-relative URL', () => __awaiter(void 0, void 0,
|
|
|
91
153
|
test('checkSvgFavicon - svgFaviconDeclared & svgFaviconDownloadable & svgFaviconSquare', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
92
154
|
const testIconPath = './fixtures/happy-face.svg';
|
|
93
155
|
const serpIcon = yield (0, helper_1.filePathToString)(testIconPath);
|
|
94
|
-
yield runSvgTest(`<link rel="icon" type="image/svg+xml" href="/the-icon.svg" />`,
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
156
|
+
yield runSvgTest(`<link rel="icon" type="image/svg+xml" href="/the-icon.svg" />`, {
|
|
157
|
+
messages: [
|
|
158
|
+
{
|
|
159
|
+
status: types_1.CheckerStatus.Ok,
|
|
160
|
+
id: types_1.MessageId.svgFaviconDeclared,
|
|
161
|
+
}, {
|
|
162
|
+
status: types_1.CheckerStatus.Ok,
|
|
163
|
+
id: types_1.MessageId.svgFaviconDownloadable,
|
|
164
|
+
}, {
|
|
165
|
+
status: types_1.CheckerStatus.Ok,
|
|
166
|
+
id: types_1.MessageId.svgFaviconSquare,
|
|
167
|
+
}
|
|
168
|
+
],
|
|
169
|
+
icons: {
|
|
170
|
+
png: null,
|
|
171
|
+
ico: null,
|
|
172
|
+
svg: {
|
|
173
|
+
content: yield (0, helper_1.filePathToDataUrl)(testIconPath),
|
|
174
|
+
url: 'https://example.com/the-icon.svg',
|
|
175
|
+
width: 36,
|
|
176
|
+
height: 36,
|
|
177
|
+
},
|
|
104
178
|
}
|
|
105
|
-
|
|
179
|
+
}, {
|
|
106
180
|
'https://example.com/the-icon.svg': {
|
|
107
181
|
status: 200,
|
|
108
182
|
contentType: 'image/svg+xml',
|
|
@@ -114,7 +188,7 @@ const runPngTest = (headFragment_2, output_2, ...args_2) => __awaiter(void 0, [h
|
|
|
114
188
|
const root = headFragment ? (0, node_html_parser_1.parse)(headFragment) : null;
|
|
115
189
|
const result = yield (0, desktop_1.checkPngFavicon)('https://example.com/', root, (0, test_helper_1.testFetcher)(fetchDatabase));
|
|
116
190
|
const filteredMessages = result.messages.map(m => ({ status: m.status, id: m.id }));
|
|
117
|
-
expect(filteredMessages).toEqual(output);
|
|
191
|
+
expect(filteredMessages).toEqual(output.messages);
|
|
118
192
|
});
|
|
119
193
|
const testIcon16 = './fixtures/16x16.png';
|
|
120
194
|
const testIcon32 = './fixtures/32x32.png';
|
|
@@ -126,16 +200,27 @@ test('checkSvgFavicon - Three PNG icons with different sizes', () => __awaiter(v
|
|
|
126
200
|
<link rel="icon" type="image/png" sizes="32x32" href="/favicon/favicon-32x32.png">
|
|
127
201
|
<link rel="icon" type="image/png" sizes="48x48" href="/favicon/favicon-48x48.png">
|
|
128
202
|
<link rel="icon" type="image/png" sizes="96x96" href="/favicon/favicon-96x96.png">
|
|
129
|
-
`, [{
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
203
|
+
`, { messages: [{
|
|
204
|
+
status: types_1.CheckerStatus.Ok,
|
|
205
|
+
id: types_1.MessageId.desktopPngFaviconDeclared,
|
|
206
|
+
}, {
|
|
207
|
+
status: types_1.CheckerStatus.Ok,
|
|
208
|
+
id: types_1.MessageId.desktopPngFaviconDownloadable,
|
|
209
|
+
}, {
|
|
210
|
+
status: types_1.CheckerStatus.Ok,
|
|
211
|
+
id: types_1.MessageId.desktopPngFaviconRightSize,
|
|
212
|
+
}],
|
|
213
|
+
icons: {
|
|
214
|
+
png: {
|
|
215
|
+
content: yield (0, helper_1.filePathToDataUrl)(testIcon96),
|
|
216
|
+
url: 'https://example.com/favicon/favicon-96x96.png',
|
|
217
|
+
width: 96,
|
|
218
|
+
height: 96,
|
|
219
|
+
},
|
|
220
|
+
ico: null,
|
|
221
|
+
svg: null,
|
|
222
|
+
}
|
|
223
|
+
}, {
|
|
139
224
|
'https://example.com/favicon/favicon-16x16.png': {
|
|
140
225
|
status: 200,
|
|
141
226
|
contentType: 'image/png',
|
package/dist/desktop/ico.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DesktopSingleReport, Fetcher } from "../types";
|
|
2
2
|
import { HTMLElement } from 'node-html-parser';
|
|
3
3
|
export declare const IcoFaviconSizes: number[];
|
|
4
|
-
export declare const checkIcoFavicon: (url: string, head: HTMLElement | null, fetcher: Fetcher) => Promise<
|
|
4
|
+
export declare const checkIcoFavicon: (url: string, head: HTMLElement | null, fetcher: Fetcher) => Promise<DesktopSingleReport>;
|
package/dist/desktop/ico.js
CHANGED
|
@@ -25,10 +25,15 @@ const checkIcoFavicon = (url, head, fetcher) => __awaiter(void 0, void 0, void 0
|
|
|
25
25
|
id: types_1.MessageId.noHead,
|
|
26
26
|
text: 'No <head> element'
|
|
27
27
|
});
|
|
28
|
-
return
|
|
28
|
+
return {
|
|
29
|
+
messages,
|
|
30
|
+
icon: { content: null, url: null, width: null, height: null }
|
|
31
|
+
};
|
|
29
32
|
}
|
|
30
33
|
const icos = head.querySelectorAll('link[rel="shortcut icon"]') ||
|
|
31
34
|
head.querySelectorAll('link[rel="icon"][type="image/x-icon"]');
|
|
35
|
+
let iconUrl = null;
|
|
36
|
+
let images;
|
|
32
37
|
if (icos.length === 0) {
|
|
33
38
|
messages.push({
|
|
34
39
|
status: types_1.CheckerStatus.Error,
|
|
@@ -58,7 +63,7 @@ const checkIcoFavicon = (url, head, fetcher) => __awaiter(void 0, void 0, void 0
|
|
|
58
63
|
});
|
|
59
64
|
}
|
|
60
65
|
else {
|
|
61
|
-
|
|
66
|
+
iconUrl = (0, helper_1.mergeUrlAndPath)(url, href);
|
|
62
67
|
const iconResponse = yield fetcher(iconUrl, 'image/x-icon');
|
|
63
68
|
if (iconResponse.status === 404) {
|
|
64
69
|
messages.push({
|
|
@@ -81,7 +86,7 @@ const checkIcoFavicon = (url, head, fetcher) => __awaiter(void 0, void 0, void 0
|
|
|
81
86
|
text: 'ICO favicon found'
|
|
82
87
|
});
|
|
83
88
|
const iconBuffer = yield (0, helper_1.readableStreamToBuffer)(iconResponse.readableStream);
|
|
84
|
-
|
|
89
|
+
images = yield (0, decode_ico_1.default)(iconBuffer);
|
|
85
90
|
const imageSizes = images.map(image => `${image.width}x${image.height}`);
|
|
86
91
|
const expectedSizes = exports.IcoFaviconSizes.map(size => `${size}x${size}`);
|
|
87
92
|
const extraSizes = imageSizes.filter(size => !expectedSizes.includes(size));
|
|
@@ -110,6 +115,23 @@ const checkIcoFavicon = (url, head, fetcher) => __awaiter(void 0, void 0, void 0
|
|
|
110
115
|
}
|
|
111
116
|
}
|
|
112
117
|
}
|
|
113
|
-
|
|
118
|
+
let content = null;
|
|
119
|
+
const theIcon = {
|
|
120
|
+
content: null,
|
|
121
|
+
url: iconUrl,
|
|
122
|
+
width: null,
|
|
123
|
+
height: null
|
|
124
|
+
};
|
|
125
|
+
if (images) {
|
|
126
|
+
const image = images[0];
|
|
127
|
+
const mimeType = (image.type === "bmp") ? "image/bmp" : "image/png";
|
|
128
|
+
theIcon.content = yield (0, helper_1.bufferToDataUrl)(Buffer.from(image.data), mimeType);
|
|
129
|
+
theIcon.width = image.width;
|
|
130
|
+
theIcon.height = image.height;
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
messages,
|
|
134
|
+
icon: theIcon,
|
|
135
|
+
};
|
|
114
136
|
});
|
|
115
137
|
exports.checkIcoFavicon = checkIcoFavicon;
|
package/dist/google.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { CheckerMessage, DesktopFaviconReport, Fetcher, GoogleReport } from "./types";
|
|
2
|
+
import { HTMLElement } from "node-html-parser";
|
|
3
|
+
export declare const GoogleBot = "Googlebot";
|
|
4
|
+
export declare const GoogleImageBot = "Googlebot-Image";
|
|
5
|
+
export declare const getRobotsFileUrl: (baseUrl: string) => string;
|
|
6
|
+
export declare const checkRobotsFile: (baseUrl: string, iconUrls: string[], fetcher?: Fetcher) => Promise<CheckerMessage[]>;
|
|
7
|
+
export declare const checkGoogleFaviconFromDesktopReport: (baseUrl: string, desktopReport: DesktopFaviconReport, fetcher?: Fetcher) => Promise<GoogleReport>;
|
|
8
|
+
export declare const checkGoogleFavicon: (baseUrl: string, head: HTMLElement | null, fetcher?: Fetcher) => Promise<GoogleReport>;
|
package/dist/google.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.checkGoogleFavicon = exports.checkGoogleFaviconFromDesktopReport = exports.checkRobotsFile = exports.getRobotsFileUrl = exports.GoogleImageBot = exports.GoogleBot = void 0;
|
|
16
|
+
const robots_parser_1 = __importDefault(require("robots-parser"));
|
|
17
|
+
const desktop_1 = require("./desktop/desktop");
|
|
18
|
+
const helper_1 = require("./helper");
|
|
19
|
+
const types_1 = require("./types");
|
|
20
|
+
exports.GoogleBot = 'Googlebot';
|
|
21
|
+
exports.GoogleImageBot = 'Googlebot-Image';
|
|
22
|
+
const getRobotsFileUrl = (baseUrl) => {
|
|
23
|
+
try {
|
|
24
|
+
const url = new URL(baseUrl);
|
|
25
|
+
url.pathname = '/robots.txt';
|
|
26
|
+
return url.toString();
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
throw new Error(`Invalid URL ${baseUrl}`);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
exports.getRobotsFileUrl = getRobotsFileUrl;
|
|
33
|
+
const checkRobotsFile = (baseUrl_1, iconUrls_1, ...args_1) => __awaiter(void 0, [baseUrl_1, iconUrls_1, ...args_1], void 0, function* (baseUrl, iconUrls, fetcher = helper_1.fetchFetcher) {
|
|
34
|
+
const robotsUrl = (0, exports.getRobotsFileUrl)(baseUrl);
|
|
35
|
+
const robotsResponse = yield fetcher(robotsUrl);
|
|
36
|
+
const messages = [];
|
|
37
|
+
if (robotsResponse.status === 200) {
|
|
38
|
+
messages.push({
|
|
39
|
+
status: types_1.CheckerStatus.Ok,
|
|
40
|
+
text: `robots.txt file found at ${robotsUrl}`,
|
|
41
|
+
id: types_1.MessageId.googleRobotsFileFound
|
|
42
|
+
});
|
|
43
|
+
const robotsFile = robotsResponse.readableStream ? yield (0, helper_1.readableStreamToString)(robotsResponse.readableStream) : '';
|
|
44
|
+
const robots = (0, robots_parser_1.default)(robotsUrl, robotsFile);
|
|
45
|
+
iconUrls.forEach(url => {
|
|
46
|
+
if (url) {
|
|
47
|
+
if (robots.isAllowed(url, exports.GoogleImageBot)) {
|
|
48
|
+
messages.push({
|
|
49
|
+
status: types_1.CheckerStatus.Ok,
|
|
50
|
+
text: `Access to \`${url}\` is allowed for \`${exports.GoogleImageBot}\``,
|
|
51
|
+
id: types_1.MessageId.googlePngIconAllowedByRobots
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
const line = robots.getMatchingLineNumber(url, exports.GoogleImageBot);
|
|
56
|
+
messages.push({
|
|
57
|
+
status: types_1.CheckerStatus.Error,
|
|
58
|
+
text: `Access to \`${url}\` is blocked for \`${exports.GoogleImageBot}\` (\`${robotsUrl}\`, line ${line})`,
|
|
59
|
+
id: types_1.MessageId.googlePngIconBlockedByRobots
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
messages.push({
|
|
67
|
+
status: types_1.CheckerStatus.Ok,
|
|
68
|
+
text: `No \`robots.txt\` file found at \`${robotsUrl}\`. Also this is not a recommanded setup, at least Google is not restricted from accessing favicon assets.`,
|
|
69
|
+
id: types_1.MessageId.googleNoRobotsFile
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return messages;
|
|
73
|
+
});
|
|
74
|
+
exports.checkRobotsFile = checkRobotsFile;
|
|
75
|
+
const checkGoogleFaviconFromDesktopReport = (baseUrl_2, desktopReport_1, ...args_2) => __awaiter(void 0, [baseUrl_2, desktopReport_1, ...args_2], void 0, function* (baseUrl, desktopReport, fetcher = helper_1.fetchFetcher) {
|
|
76
|
+
const allIcons = [
|
|
77
|
+
desktopReport.icons.png,
|
|
78
|
+
desktopReport.icons.ico,
|
|
79
|
+
desktopReport.icons.svg
|
|
80
|
+
].filter((i) => !!i);
|
|
81
|
+
const allIconUrls = allIcons.map(i => i.url).filter((i) => !!i);
|
|
82
|
+
const robotsMessages = yield (0, exports.checkRobotsFile)(baseUrl, allIconUrls, fetcher);
|
|
83
|
+
const messages = [...desktopReport.messages, ...robotsMessages];
|
|
84
|
+
let finalIcon = null;
|
|
85
|
+
let icons = [];
|
|
86
|
+
let maxWidth = 0;
|
|
87
|
+
allIcons.forEach(icon => {
|
|
88
|
+
if (icon.content && icon.width && icon.height && icon.url) {
|
|
89
|
+
icons.push(icon);
|
|
90
|
+
if (icon.width > maxWidth) {
|
|
91
|
+
finalIcon = icon.content;
|
|
92
|
+
maxWidth = icon.width;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
return {
|
|
97
|
+
messages,
|
|
98
|
+
icon: finalIcon,
|
|
99
|
+
icons
|
|
100
|
+
};
|
|
101
|
+
});
|
|
102
|
+
exports.checkGoogleFaviconFromDesktopReport = checkGoogleFaviconFromDesktopReport;
|
|
103
|
+
const checkGoogleFavicon = (baseUrl_3, head_1, ...args_3) => __awaiter(void 0, [baseUrl_3, head_1, ...args_3], void 0, function* (baseUrl, head, fetcher = helper_1.fetchFetcher) {
|
|
104
|
+
const desktopReport = yield (0, desktop_1.checkDesktopFavicon)(baseUrl, head, fetcher);
|
|
105
|
+
return (0, exports.checkGoogleFaviconFromDesktopReport)(baseUrl, desktopReport, fetcher);
|
|
106
|
+
});
|
|
107
|
+
exports.checkGoogleFavicon = checkGoogleFavicon;
|