@realfavicongenerator/check-favicon 0.5.0 → 0.5.2
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/dist/desktop/ico.js +73 -43
- package/dist/desktop/ico.test.d.ts +1 -0
- package/dist/desktop/ico.test.js +263 -0
- package/dist/types.d.ts +65 -64
- package/dist/types.js +65 -64
- package/fixtures/simple-ico.ico +0 -0
- package/package.json +2 -2
- package/src/desktop/ico.test.ts +280 -0
- package/src/desktop/ico.ts +77 -47
- package/src/types.ts +1 -0
package/dist/desktop/ico.js
CHANGED
|
@@ -36,21 +36,16 @@ const checkIcoFavicon = (url, head, fetcher) => __awaiter(void 0, void 0, void 0
|
|
|
36
36
|
];
|
|
37
37
|
let iconUrl = null;
|
|
38
38
|
let images;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
status: types_1.CheckerStatus.Error,
|
|
42
|
-
id: types_1.MessageId.noIcoFavicon,
|
|
43
|
-
text: 'There is no ICO favicon'
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
else if (icos.length > 1) {
|
|
39
|
+
let isDeclared = false;
|
|
40
|
+
if (icos.length > 1) {
|
|
47
41
|
messages.push({
|
|
48
42
|
status: types_1.CheckerStatus.Error,
|
|
49
43
|
id: types_1.MessageId.multipleIcoFavicons,
|
|
50
44
|
text: `There are ${icos.length} ICO favicons`
|
|
51
45
|
});
|
|
52
46
|
}
|
|
53
|
-
else {
|
|
47
|
+
else if (icos.length === 1) {
|
|
48
|
+
isDeclared = true;
|
|
54
49
|
messages.push({
|
|
55
50
|
status: types_1.CheckerStatus.Ok,
|
|
56
51
|
id: types_1.MessageId.icoFaviconDeclared,
|
|
@@ -66,15 +61,35 @@ const checkIcoFavicon = (url, head, fetcher) => __awaiter(void 0, void 0, void 0
|
|
|
66
61
|
}
|
|
67
62
|
else {
|
|
68
63
|
iconUrl = (0, helper_1.mergeUrlAndPath)(url, href);
|
|
69
|
-
|
|
70
|
-
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
// No declared ICO favicon, try the implicit /favicon.ico convention
|
|
68
|
+
iconUrl = (0, helper_1.mergeUrlAndPath)(url, '/favicon.ico');
|
|
69
|
+
}
|
|
70
|
+
// If we have an iconUrl (either from declaration or implicit), try to fetch it
|
|
71
|
+
if (iconUrl) {
|
|
72
|
+
const iconResponse = yield fetcher(iconUrl, 'image/x-icon');
|
|
73
|
+
if (iconResponse.status === 404) {
|
|
74
|
+
if (isDeclared) {
|
|
71
75
|
messages.push({
|
|
72
76
|
status: types_1.CheckerStatus.Error,
|
|
73
77
|
id: types_1.MessageId.icoFavicon404,
|
|
74
78
|
text: `ICO favicon not found at ${iconUrl}`
|
|
75
79
|
});
|
|
76
80
|
}
|
|
77
|
-
else
|
|
81
|
+
else {
|
|
82
|
+
// Implicit favicon not found, report no ICO favicon
|
|
83
|
+
messages.push({
|
|
84
|
+
status: types_1.CheckerStatus.Error,
|
|
85
|
+
id: types_1.MessageId.noIcoFavicon,
|
|
86
|
+
text: 'There is no ICO favicon'
|
|
87
|
+
});
|
|
88
|
+
iconUrl = null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
else if (iconResponse.status >= 300 || !iconResponse.readableStream) {
|
|
92
|
+
if (isDeclared) {
|
|
78
93
|
messages.push({
|
|
79
94
|
status: types_1.CheckerStatus.Error,
|
|
80
95
|
id: types_1.MessageId.icoFaviconCannotGet,
|
|
@@ -82,42 +97,57 @@ const checkIcoFavicon = (url, head, fetcher) => __awaiter(void 0, void 0, void 0
|
|
|
82
97
|
});
|
|
83
98
|
}
|
|
84
99
|
else {
|
|
100
|
+
// Implicit favicon cannot be fetched, report no ICO favicon
|
|
101
|
+
messages.push({
|
|
102
|
+
status: types_1.CheckerStatus.Error,
|
|
103
|
+
id: types_1.MessageId.noIcoFavicon,
|
|
104
|
+
text: 'There is no ICO favicon'
|
|
105
|
+
});
|
|
106
|
+
iconUrl = null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
if (!isDeclared) {
|
|
111
|
+
messages.push({
|
|
112
|
+
status: types_1.CheckerStatus.Ok,
|
|
113
|
+
id: types_1.MessageId.icoFaviconImplicitInRoot,
|
|
114
|
+
text: 'An implicit ICO favicon is found at /favicon.ico'
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
messages.push({
|
|
118
|
+
status: types_1.CheckerStatus.Ok,
|
|
119
|
+
id: types_1.MessageId.icoFaviconDownloadable,
|
|
120
|
+
text: 'ICO favicon found'
|
|
121
|
+
});
|
|
122
|
+
const iconBuffer = yield (0, helper_1.readableStreamToBuffer)(iconResponse.readableStream);
|
|
123
|
+
images = (0, decode_ico_1.default)(iconBuffer);
|
|
124
|
+
const imageSizes = images.map(image => `${image.width}x${image.height}`);
|
|
125
|
+
const expectedSizes = exports.IcoFaviconSizes.map(size => `${size}x${size}`);
|
|
126
|
+
const extraSizes = imageSizes.filter(size => !expectedSizes.includes(size));
|
|
127
|
+
if (extraSizes.length > 0) {
|
|
128
|
+
messages.push({
|
|
129
|
+
status: types_1.CheckerStatus.Warning,
|
|
130
|
+
id: types_1.MessageId.icoFaviconExtraSizes,
|
|
131
|
+
text: `Extra sizes found in ICO favicon: ${extraSizes.join(', ')}`
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
const missingSizes = expectedSizes.filter(size => !imageSizes.includes(size));
|
|
135
|
+
if (missingSizes.length > 0) {
|
|
136
|
+
messages.push({
|
|
137
|
+
status: types_1.CheckerStatus.Warning,
|
|
138
|
+
id: types_1.MessageId.icoFaviconMissingSizes,
|
|
139
|
+
text: `Missing sizes in ICO favicon: ${missingSizes.join(', ')}`
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
if (extraSizes.length === 0 && missingSizes.length === 0) {
|
|
85
143
|
messages.push({
|
|
86
144
|
status: types_1.CheckerStatus.Ok,
|
|
87
|
-
id: types_1.MessageId.
|
|
88
|
-
text:
|
|
145
|
+
id: types_1.MessageId.icoFaviconExpectedSizes,
|
|
146
|
+
text: `The ICO favicon has the expected sizes (${imageSizes.join(', ')})`
|
|
89
147
|
});
|
|
90
|
-
const iconBuffer = yield (0, helper_1.readableStreamToBuffer)(iconResponse.readableStream);
|
|
91
|
-
images = yield (0, decode_ico_1.default)(iconBuffer);
|
|
92
|
-
const imageSizes = images.map(image => `${image.width}x${image.height}`);
|
|
93
|
-
const expectedSizes = exports.IcoFaviconSizes.map(size => `${size}x${size}`);
|
|
94
|
-
const extraSizes = imageSizes.filter(size => !expectedSizes.includes(size));
|
|
95
|
-
if (extraSizes.length > 0) {
|
|
96
|
-
messages.push({
|
|
97
|
-
status: types_1.CheckerStatus.Warning,
|
|
98
|
-
id: types_1.MessageId.icoFaviconExtraSizes,
|
|
99
|
-
text: `Extra sizes found in ICO favicon: ${extraSizes.join(', ')}`
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
const missingSizes = expectedSizes.filter(size => !imageSizes.includes(size));
|
|
103
|
-
if (missingSizes.length > 0) {
|
|
104
|
-
messages.push({
|
|
105
|
-
status: types_1.CheckerStatus.Warning,
|
|
106
|
-
id: types_1.MessageId.icoFaviconMissingSizes,
|
|
107
|
-
text: `Missing sizes in ICO favicon: ${missingSizes.join(', ')}`
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
if (extraSizes.length === 0 && missingSizes.length === 0) {
|
|
111
|
-
messages.push({
|
|
112
|
-
status: types_1.CheckerStatus.Ok,
|
|
113
|
-
id: types_1.MessageId.icoFaviconExpectedSizes,
|
|
114
|
-
text: `The ICO favicon has the expected sizes (${imageSizes.join(', ')})`
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
148
|
}
|
|
118
149
|
}
|
|
119
150
|
}
|
|
120
|
-
let content = null;
|
|
121
151
|
const theIcon = {
|
|
122
152
|
content: null,
|
|
123
153
|
url: iconUrl,
|
|
@@ -127,7 +157,7 @@ const checkIcoFavicon = (url, head, fetcher) => __awaiter(void 0, void 0, void 0
|
|
|
127
157
|
if (images) {
|
|
128
158
|
const image = images[0];
|
|
129
159
|
const mimeType = (image.type === "bmp") ? "image/bmp" : "image/png";
|
|
130
|
-
theIcon.content =
|
|
160
|
+
theIcon.content = (0, helper_1.bufferToDataUrl)(Buffer.from(image.data.buffer, image.data.byteOffset, image.data.byteLength), mimeType);
|
|
131
161
|
theIcon.width = image.width;
|
|
132
162
|
theIcon.height = image.height;
|
|
133
163
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,263 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
const node_html_parser_1 = require("node-html-parser");
|
|
13
|
+
const ico_1 = require("./ico");
|
|
14
|
+
const types_1 = require("../types");
|
|
15
|
+
const helper_1 = require("../helper");
|
|
16
|
+
const test_helper_1 = require("../test-helper");
|
|
17
|
+
const runIcoTest = (headFragment_1, output_1, ...args_1) => __awaiter(void 0, [headFragment_1, output_1, ...args_1], void 0, function* (headFragment, output, fetchDatabase = {}, checkContent = true) {
|
|
18
|
+
const root = headFragment ? (0, node_html_parser_1.parse)(headFragment) : null;
|
|
19
|
+
const result = yield (0, ico_1.checkIcoFavicon)('https://example.com/', root, (0, test_helper_1.testFetcher)(fetchDatabase));
|
|
20
|
+
const filteredMessages = result.messages.map(m => ({ status: m.status, id: m.id }));
|
|
21
|
+
expect(filteredMessages).toEqual(output.messages);
|
|
22
|
+
// Check icon properties - icon is always returned by checkIcoFavicon
|
|
23
|
+
const resultIcon = result.icon;
|
|
24
|
+
const outputIcon = output.icon;
|
|
25
|
+
expect(resultIcon.url).toEqual(outputIcon.url);
|
|
26
|
+
expect(resultIcon.width).toEqual(outputIcon.width);
|
|
27
|
+
expect(resultIcon.height).toEqual(outputIcon.height);
|
|
28
|
+
// For content, just check if it's null or not null unless exact match is needed
|
|
29
|
+
if (checkContent && outputIcon.content === null) {
|
|
30
|
+
expect(resultIcon.content).toBeNull();
|
|
31
|
+
}
|
|
32
|
+
else if (checkContent && outputIcon.content !== null) {
|
|
33
|
+
expect(resultIcon.content).not.toBeNull();
|
|
34
|
+
expect(resultIcon.content).toMatch(/^data:image\/(png|bmp);base64,/);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
test('checkIcoFavicon - noHead', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
38
|
+
yield runIcoTest(null, {
|
|
39
|
+
messages: [{
|
|
40
|
+
status: types_1.CheckerStatus.Error,
|
|
41
|
+
id: types_1.MessageId.noHead,
|
|
42
|
+
}],
|
|
43
|
+
icon: {
|
|
44
|
+
content: null,
|
|
45
|
+
url: null,
|
|
46
|
+
width: null,
|
|
47
|
+
height: null,
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}));
|
|
51
|
+
test('checkIcoFavicon - noIcoFavicon', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
52
|
+
yield runIcoTest(`<title>Some text</title>`, {
|
|
53
|
+
messages: [{
|
|
54
|
+
status: types_1.CheckerStatus.Error,
|
|
55
|
+
id: types_1.MessageId.noIcoFavicon,
|
|
56
|
+
}],
|
|
57
|
+
icon: {
|
|
58
|
+
content: null,
|
|
59
|
+
url: null,
|
|
60
|
+
width: null,
|
|
61
|
+
height: null,
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}));
|
|
65
|
+
test('checkIcoFavicon - implicit /favicon.ico when not declared', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
66
|
+
const testIconPath = './fixtures/simple-ico.ico';
|
|
67
|
+
yield runIcoTest(`<title>Some text</title>`, {
|
|
68
|
+
messages: [{
|
|
69
|
+
status: types_1.CheckerStatus.Ok,
|
|
70
|
+
id: types_1.MessageId.icoFaviconImplicitInRoot,
|
|
71
|
+
}, {
|
|
72
|
+
status: types_1.CheckerStatus.Ok,
|
|
73
|
+
id: types_1.MessageId.icoFaviconDownloadable,
|
|
74
|
+
}, {
|
|
75
|
+
status: types_1.CheckerStatus.Ok,
|
|
76
|
+
id: types_1.MessageId.icoFaviconExpectedSizes,
|
|
77
|
+
}],
|
|
78
|
+
icon: {
|
|
79
|
+
content: "data:image/png;base64,placeholder", // Will be checked for format only
|
|
80
|
+
url: 'https://example.com/favicon.ico',
|
|
81
|
+
width: 48,
|
|
82
|
+
height: 48,
|
|
83
|
+
}
|
|
84
|
+
}, {
|
|
85
|
+
'https://example.com/favicon.ico': {
|
|
86
|
+
status: 200,
|
|
87
|
+
contentType: 'image/x-icon',
|
|
88
|
+
readableStream: yield (0, helper_1.filePathToReadableStream)(testIconPath)
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}));
|
|
92
|
+
test('checkIcoFavicon - multipleIcoFavicons with shortcut icon', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
93
|
+
yield runIcoTest(`
|
|
94
|
+
<link rel="shortcut icon" href="/favicon1.ico" />
|
|
95
|
+
<link rel="shortcut icon" href="/favicon2.ico" />
|
|
96
|
+
`, {
|
|
97
|
+
messages: [{
|
|
98
|
+
status: types_1.CheckerStatus.Error,
|
|
99
|
+
id: types_1.MessageId.multipleIcoFavicons,
|
|
100
|
+
}],
|
|
101
|
+
icon: {
|
|
102
|
+
content: null,
|
|
103
|
+
url: null,
|
|
104
|
+
width: null,
|
|
105
|
+
height: null,
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
}));
|
|
109
|
+
test('checkIcoFavicon - multipleIcoFavicons with type="image/x-icon"', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
110
|
+
yield runIcoTest(`
|
|
111
|
+
<link rel="icon" type="image/x-icon" href="/favicon1.ico" />
|
|
112
|
+
<link rel="icon" type="image/x-icon" href="/favicon2.ico" />
|
|
113
|
+
`, {
|
|
114
|
+
messages: [{
|
|
115
|
+
status: types_1.CheckerStatus.Error,
|
|
116
|
+
id: types_1.MessageId.multipleIcoFavicons,
|
|
117
|
+
}],
|
|
118
|
+
icon: {
|
|
119
|
+
content: null,
|
|
120
|
+
url: null,
|
|
121
|
+
width: null,
|
|
122
|
+
height: null,
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
}));
|
|
126
|
+
test('checkIcoFavicon - icoFaviconDeclared & noIcoFaviconHref', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
127
|
+
yield runIcoTest(`<link rel="shortcut icon" />`, {
|
|
128
|
+
messages: [{
|
|
129
|
+
status: types_1.CheckerStatus.Ok,
|
|
130
|
+
id: types_1.MessageId.icoFaviconDeclared,
|
|
131
|
+
}, {
|
|
132
|
+
status: types_1.CheckerStatus.Error,
|
|
133
|
+
id: types_1.MessageId.noIcoFaviconHref,
|
|
134
|
+
}],
|
|
135
|
+
icon: {
|
|
136
|
+
content: null,
|
|
137
|
+
url: null,
|
|
138
|
+
width: null,
|
|
139
|
+
height: null,
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
}));
|
|
143
|
+
test('checkIcoFavicon - icoFaviconDeclared & icoFavicon404', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
144
|
+
yield runIcoTest(`<link rel="shortcut icon" href="/favicon.ico" />`, {
|
|
145
|
+
messages: [{
|
|
146
|
+
status: types_1.CheckerStatus.Ok,
|
|
147
|
+
id: types_1.MessageId.icoFaviconDeclared,
|
|
148
|
+
}, {
|
|
149
|
+
status: types_1.CheckerStatus.Error,
|
|
150
|
+
id: types_1.MessageId.icoFavicon404,
|
|
151
|
+
}],
|
|
152
|
+
icon: {
|
|
153
|
+
content: null,
|
|
154
|
+
url: 'https://example.com/favicon.ico',
|
|
155
|
+
width: null,
|
|
156
|
+
height: null,
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
}));
|
|
160
|
+
test('checkIcoFavicon - icoFaviconDeclared & icoFaviconCannotGet', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
161
|
+
yield runIcoTest(`<link rel="shortcut icon" href="/favicon.ico" />`, {
|
|
162
|
+
messages: [{
|
|
163
|
+
status: types_1.CheckerStatus.Ok,
|
|
164
|
+
id: types_1.MessageId.icoFaviconDeclared,
|
|
165
|
+
}, {
|
|
166
|
+
status: types_1.CheckerStatus.Error,
|
|
167
|
+
id: types_1.MessageId.icoFaviconCannotGet,
|
|
168
|
+
}],
|
|
169
|
+
icon: {
|
|
170
|
+
content: null,
|
|
171
|
+
url: 'https://example.com/favicon.ico',
|
|
172
|
+
width: null,
|
|
173
|
+
height: null,
|
|
174
|
+
}
|
|
175
|
+
}, {
|
|
176
|
+
'https://example.com/favicon.ico': {
|
|
177
|
+
status: 403,
|
|
178
|
+
contentType: 'image/x-icon'
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
}));
|
|
182
|
+
test('checkIcoFavicon - icoFaviconDeclared & icoFaviconDownloadable & icoFaviconExpectedSizes', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
183
|
+
const testIconPath = './fixtures/simple-ico.ico';
|
|
184
|
+
yield runIcoTest(`<link rel="shortcut icon" href="/favicon.ico" />`, {
|
|
185
|
+
messages: [{
|
|
186
|
+
status: types_1.CheckerStatus.Ok,
|
|
187
|
+
id: types_1.MessageId.icoFaviconDeclared,
|
|
188
|
+
}, {
|
|
189
|
+
status: types_1.CheckerStatus.Ok,
|
|
190
|
+
id: types_1.MessageId.icoFaviconDownloadable,
|
|
191
|
+
}, {
|
|
192
|
+
status: types_1.CheckerStatus.Ok,
|
|
193
|
+
id: types_1.MessageId.icoFaviconExpectedSizes,
|
|
194
|
+
}],
|
|
195
|
+
icon: {
|
|
196
|
+
content: "data:image/png;base64,placeholder", // Will be checked for format only
|
|
197
|
+
url: 'https://example.com/favicon.ico',
|
|
198
|
+
width: 48,
|
|
199
|
+
height: 48,
|
|
200
|
+
}
|
|
201
|
+
}, {
|
|
202
|
+
'https://example.com/favicon.ico': {
|
|
203
|
+
status: 200,
|
|
204
|
+
contentType: 'image/x-icon',
|
|
205
|
+
readableStream: yield (0, helper_1.filePathToReadableStream)(testIconPath)
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
}));
|
|
209
|
+
test('checkIcoFavicon - using type="image/x-icon"', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
210
|
+
const testIconPath = './fixtures/simple-ico.ico';
|
|
211
|
+
yield runIcoTest(`<link rel="icon" type="image/x-icon" href="/favicon.ico" />`, {
|
|
212
|
+
messages: [{
|
|
213
|
+
status: types_1.CheckerStatus.Ok,
|
|
214
|
+
id: types_1.MessageId.icoFaviconDeclared,
|
|
215
|
+
}, {
|
|
216
|
+
status: types_1.CheckerStatus.Ok,
|
|
217
|
+
id: types_1.MessageId.icoFaviconDownloadable,
|
|
218
|
+
}, {
|
|
219
|
+
status: types_1.CheckerStatus.Ok,
|
|
220
|
+
id: types_1.MessageId.icoFaviconExpectedSizes,
|
|
221
|
+
}],
|
|
222
|
+
icon: {
|
|
223
|
+
content: "data:image/png;base64,placeholder", // Will be checked for format only
|
|
224
|
+
url: 'https://example.com/favicon.ico',
|
|
225
|
+
width: 48,
|
|
226
|
+
height: 48,
|
|
227
|
+
}
|
|
228
|
+
}, {
|
|
229
|
+
'https://example.com/favicon.ico': {
|
|
230
|
+
status: 200,
|
|
231
|
+
contentType: 'image/x-icon',
|
|
232
|
+
readableStream: yield (0, helper_1.filePathToReadableStream)(testIconPath)
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
}));
|
|
236
|
+
// For https://github.com/RealFaviconGenerator/core/issues/2
|
|
237
|
+
test('checkIcoFavicon - Protocol-relative URL', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
238
|
+
const testIconPath = './fixtures/simple-ico.ico';
|
|
239
|
+
yield runIcoTest(`<link rel="shortcut icon" href="//example.com/favicon.ico" />`, {
|
|
240
|
+
messages: [{
|
|
241
|
+
status: types_1.CheckerStatus.Ok,
|
|
242
|
+
id: types_1.MessageId.icoFaviconDeclared,
|
|
243
|
+
}, {
|
|
244
|
+
status: types_1.CheckerStatus.Ok,
|
|
245
|
+
id: types_1.MessageId.icoFaviconDownloadable,
|
|
246
|
+
}, {
|
|
247
|
+
status: types_1.CheckerStatus.Ok,
|
|
248
|
+
id: types_1.MessageId.icoFaviconExpectedSizes,
|
|
249
|
+
}],
|
|
250
|
+
icon: {
|
|
251
|
+
content: "data:image/png;base64,placeholder", // Will be checked for format only
|
|
252
|
+
url: 'https://example.com/favicon.ico',
|
|
253
|
+
width: 48,
|
|
254
|
+
height: 48,
|
|
255
|
+
}
|
|
256
|
+
}, {
|
|
257
|
+
'https://example.com/favicon.ico': {
|
|
258
|
+
status: 200,
|
|
259
|
+
contentType: 'image/x-icon',
|
|
260
|
+
readableStream: yield (0, helper_1.filePathToReadableStream)(testIconPath)
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
}));
|
package/dist/types.d.ts
CHANGED
|
@@ -17,70 +17,71 @@ export declare enum MessageId {
|
|
|
17
17
|
noIcoFavicon = 10,
|
|
18
18
|
multipleIcoFavicons = 11,
|
|
19
19
|
icoFaviconDeclared = 12,
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
20
|
+
icoFaviconImplicitInRoot = 13,
|
|
21
|
+
noIcoFaviconHref = 14,
|
|
22
|
+
icoFavicon404 = 15,
|
|
23
|
+
icoFaviconCannotGet = 16,
|
|
24
|
+
icoFaviconDownloadable = 17,
|
|
25
|
+
icoFaviconExtraSizes = 18,
|
|
26
|
+
icoFaviconMissingSizes = 19,
|
|
27
|
+
icoFaviconExpectedSizes = 20,
|
|
28
|
+
noDesktopPngFavicon = 21,
|
|
29
|
+
no96x96DesktopPngFavicon = 22,
|
|
30
|
+
desktopPngFaviconDeclared = 23,
|
|
31
|
+
noDesktopPngFaviconHref = 24,
|
|
32
|
+
desktopPngFaviconCannotGet = 25,
|
|
33
|
+
desktopPngFaviconDownloadable = 26,
|
|
34
|
+
desktopPngFavicon404 = 27,
|
|
35
|
+
desktopPngFaviconWrongSize = 28,
|
|
36
|
+
desktopPngFaviconRightSize = 29,
|
|
37
|
+
noTouchWebAppTitle = 30,
|
|
38
|
+
multipleTouchWebAppTitles = 31,
|
|
39
|
+
emptyTouchWebAppTitle = 32,
|
|
40
|
+
touchWebAppTitleDeclared = 33,
|
|
41
|
+
noTouchIcon = 34,
|
|
42
|
+
duplicatedTouchIconSizes = 35,
|
|
43
|
+
touchIconWithSize = 36,
|
|
44
|
+
touchIconDeclared = 37,
|
|
45
|
+
noTouchIconHref = 38,
|
|
46
|
+
touchIcon404 = 39,
|
|
47
|
+
touchIconCannotGet = 40,
|
|
48
|
+
touchIconDownloadable = 41,
|
|
49
|
+
touchIconSquare = 42,
|
|
50
|
+
touchIcon180x180 = 43,
|
|
51
|
+
touchIconNotSquare = 44,
|
|
52
|
+
touchIconWrongSize = 45,
|
|
53
|
+
noManifest = 46,
|
|
54
|
+
noManifestHref = 47,
|
|
55
|
+
manifest404 = 48,
|
|
56
|
+
manifestCannotGet = 49,
|
|
57
|
+
manifestInvalidJson = 50,
|
|
58
|
+
manifestName = 51,
|
|
59
|
+
noManifestName = 52,
|
|
60
|
+
manifestShortName = 53,
|
|
61
|
+
noManifestShortName = 54,
|
|
62
|
+
manifestBackgroundColor = 55,
|
|
63
|
+
noManifestBackgroundColor = 56,
|
|
64
|
+
manifestThemeColor = 57,
|
|
65
|
+
noManifestThemeColor = 58,
|
|
66
|
+
noManifestIcons = 59,
|
|
67
|
+
noManifestIcon = 60,
|
|
68
|
+
manifestIconDeclared = 61,
|
|
69
|
+
manifestIconCannotGet = 62,
|
|
70
|
+
manifestIconDownloadable = 63,
|
|
71
|
+
manifestIcon404 = 64,
|
|
72
|
+
manifestIconNoHref = 65,
|
|
73
|
+
manifestIconNotSquare = 66,
|
|
74
|
+
manifestIconRightSize = 67,
|
|
75
|
+
manifestIconSquare = 68,
|
|
76
|
+
manifestIconWrongSize = 69,
|
|
77
|
+
googleNoRobotsFile = 70,
|
|
78
|
+
googleRobotsFileFound = 71,
|
|
79
|
+
googleIcoBlockedByRobots = 72,
|
|
80
|
+
googleIcoAllowedByRobots = 73,
|
|
81
|
+
googleSvgIconBlockedByRobots = 74,
|
|
82
|
+
googleSvgIconAllowedByRobots = 75,
|
|
83
|
+
googlePngIconBlockedByRobots = 76,
|
|
84
|
+
googlePngIconAllowedByRobots = 77
|
|
84
85
|
}
|
|
85
86
|
export type CheckerMessage = {
|
|
86
87
|
status: CheckerStatus;
|
package/dist/types.js
CHANGED
|
@@ -22,68 +22,69 @@ var MessageId;
|
|
|
22
22
|
MessageId[MessageId["noIcoFavicon"] = 10] = "noIcoFavicon";
|
|
23
23
|
MessageId[MessageId["multipleIcoFavicons"] = 11] = "multipleIcoFavicons";
|
|
24
24
|
MessageId[MessageId["icoFaviconDeclared"] = 12] = "icoFaviconDeclared";
|
|
25
|
-
MessageId[MessageId["
|
|
26
|
-
MessageId[MessageId["
|
|
27
|
-
MessageId[MessageId["
|
|
28
|
-
MessageId[MessageId["
|
|
29
|
-
MessageId[MessageId["
|
|
30
|
-
MessageId[MessageId["
|
|
31
|
-
MessageId[MessageId["
|
|
32
|
-
MessageId[MessageId["
|
|
33
|
-
MessageId[MessageId["
|
|
34
|
-
MessageId[MessageId["
|
|
35
|
-
MessageId[MessageId["
|
|
36
|
-
MessageId[MessageId["
|
|
37
|
-
MessageId[MessageId["
|
|
38
|
-
MessageId[MessageId["
|
|
39
|
-
MessageId[MessageId["
|
|
40
|
-
MessageId[MessageId["
|
|
41
|
-
MessageId[MessageId["
|
|
42
|
-
MessageId[MessageId["
|
|
43
|
-
MessageId[MessageId["
|
|
44
|
-
MessageId[MessageId["
|
|
45
|
-
MessageId[MessageId["
|
|
46
|
-
MessageId[MessageId["
|
|
47
|
-
MessageId[MessageId["
|
|
48
|
-
MessageId[MessageId["
|
|
49
|
-
MessageId[MessageId["
|
|
50
|
-
MessageId[MessageId["
|
|
51
|
-
MessageId[MessageId["
|
|
52
|
-
MessageId[MessageId["
|
|
53
|
-
MessageId[MessageId["
|
|
54
|
-
MessageId[MessageId["
|
|
55
|
-
MessageId[MessageId["
|
|
56
|
-
MessageId[MessageId["
|
|
57
|
-
MessageId[MessageId["
|
|
58
|
-
MessageId[MessageId["
|
|
59
|
-
MessageId[MessageId["
|
|
60
|
-
MessageId[MessageId["
|
|
61
|
-
MessageId[MessageId["
|
|
62
|
-
MessageId[MessageId["
|
|
63
|
-
MessageId[MessageId["
|
|
64
|
-
MessageId[MessageId["
|
|
65
|
-
MessageId[MessageId["
|
|
66
|
-
MessageId[MessageId["
|
|
67
|
-
MessageId[MessageId["
|
|
68
|
-
MessageId[MessageId["
|
|
69
|
-
MessageId[MessageId["
|
|
70
|
-
MessageId[MessageId["
|
|
71
|
-
MessageId[MessageId["
|
|
72
|
-
MessageId[MessageId["
|
|
73
|
-
MessageId[MessageId["
|
|
74
|
-
MessageId[MessageId["
|
|
75
|
-
MessageId[MessageId["
|
|
76
|
-
MessageId[MessageId["
|
|
77
|
-
MessageId[MessageId["
|
|
78
|
-
MessageId[MessageId["
|
|
79
|
-
MessageId[MessageId["
|
|
80
|
-
MessageId[MessageId["
|
|
81
|
-
MessageId[MessageId["
|
|
82
|
-
MessageId[MessageId["
|
|
83
|
-
MessageId[MessageId["
|
|
84
|
-
MessageId[MessageId["
|
|
85
|
-
MessageId[MessageId["
|
|
86
|
-
MessageId[MessageId["
|
|
87
|
-
MessageId[MessageId["
|
|
88
|
-
MessageId[MessageId["
|
|
25
|
+
MessageId[MessageId["icoFaviconImplicitInRoot"] = 13] = "icoFaviconImplicitInRoot";
|
|
26
|
+
MessageId[MessageId["noIcoFaviconHref"] = 14] = "noIcoFaviconHref";
|
|
27
|
+
MessageId[MessageId["icoFavicon404"] = 15] = "icoFavicon404";
|
|
28
|
+
MessageId[MessageId["icoFaviconCannotGet"] = 16] = "icoFaviconCannotGet";
|
|
29
|
+
MessageId[MessageId["icoFaviconDownloadable"] = 17] = "icoFaviconDownloadable";
|
|
30
|
+
MessageId[MessageId["icoFaviconExtraSizes"] = 18] = "icoFaviconExtraSizes";
|
|
31
|
+
MessageId[MessageId["icoFaviconMissingSizes"] = 19] = "icoFaviconMissingSizes";
|
|
32
|
+
MessageId[MessageId["icoFaviconExpectedSizes"] = 20] = "icoFaviconExpectedSizes";
|
|
33
|
+
MessageId[MessageId["noDesktopPngFavicon"] = 21] = "noDesktopPngFavicon";
|
|
34
|
+
MessageId[MessageId["no96x96DesktopPngFavicon"] = 22] = "no96x96DesktopPngFavicon";
|
|
35
|
+
MessageId[MessageId["desktopPngFaviconDeclared"] = 23] = "desktopPngFaviconDeclared";
|
|
36
|
+
MessageId[MessageId["noDesktopPngFaviconHref"] = 24] = "noDesktopPngFaviconHref";
|
|
37
|
+
MessageId[MessageId["desktopPngFaviconCannotGet"] = 25] = "desktopPngFaviconCannotGet";
|
|
38
|
+
MessageId[MessageId["desktopPngFaviconDownloadable"] = 26] = "desktopPngFaviconDownloadable";
|
|
39
|
+
MessageId[MessageId["desktopPngFavicon404"] = 27] = "desktopPngFavicon404";
|
|
40
|
+
MessageId[MessageId["desktopPngFaviconWrongSize"] = 28] = "desktopPngFaviconWrongSize";
|
|
41
|
+
MessageId[MessageId["desktopPngFaviconRightSize"] = 29] = "desktopPngFaviconRightSize";
|
|
42
|
+
MessageId[MessageId["noTouchWebAppTitle"] = 30] = "noTouchWebAppTitle";
|
|
43
|
+
MessageId[MessageId["multipleTouchWebAppTitles"] = 31] = "multipleTouchWebAppTitles";
|
|
44
|
+
MessageId[MessageId["emptyTouchWebAppTitle"] = 32] = "emptyTouchWebAppTitle";
|
|
45
|
+
MessageId[MessageId["touchWebAppTitleDeclared"] = 33] = "touchWebAppTitleDeclared";
|
|
46
|
+
MessageId[MessageId["noTouchIcon"] = 34] = "noTouchIcon";
|
|
47
|
+
MessageId[MessageId["duplicatedTouchIconSizes"] = 35] = "duplicatedTouchIconSizes";
|
|
48
|
+
MessageId[MessageId["touchIconWithSize"] = 36] = "touchIconWithSize";
|
|
49
|
+
MessageId[MessageId["touchIconDeclared"] = 37] = "touchIconDeclared";
|
|
50
|
+
MessageId[MessageId["noTouchIconHref"] = 38] = "noTouchIconHref";
|
|
51
|
+
MessageId[MessageId["touchIcon404"] = 39] = "touchIcon404";
|
|
52
|
+
MessageId[MessageId["touchIconCannotGet"] = 40] = "touchIconCannotGet";
|
|
53
|
+
MessageId[MessageId["touchIconDownloadable"] = 41] = "touchIconDownloadable";
|
|
54
|
+
MessageId[MessageId["touchIconSquare"] = 42] = "touchIconSquare";
|
|
55
|
+
MessageId[MessageId["touchIcon180x180"] = 43] = "touchIcon180x180";
|
|
56
|
+
MessageId[MessageId["touchIconNotSquare"] = 44] = "touchIconNotSquare";
|
|
57
|
+
MessageId[MessageId["touchIconWrongSize"] = 45] = "touchIconWrongSize";
|
|
58
|
+
MessageId[MessageId["noManifest"] = 46] = "noManifest";
|
|
59
|
+
MessageId[MessageId["noManifestHref"] = 47] = "noManifestHref";
|
|
60
|
+
MessageId[MessageId["manifest404"] = 48] = "manifest404";
|
|
61
|
+
MessageId[MessageId["manifestCannotGet"] = 49] = "manifestCannotGet";
|
|
62
|
+
MessageId[MessageId["manifestInvalidJson"] = 50] = "manifestInvalidJson";
|
|
63
|
+
MessageId[MessageId["manifestName"] = 51] = "manifestName";
|
|
64
|
+
MessageId[MessageId["noManifestName"] = 52] = "noManifestName";
|
|
65
|
+
MessageId[MessageId["manifestShortName"] = 53] = "manifestShortName";
|
|
66
|
+
MessageId[MessageId["noManifestShortName"] = 54] = "noManifestShortName";
|
|
67
|
+
MessageId[MessageId["manifestBackgroundColor"] = 55] = "manifestBackgroundColor";
|
|
68
|
+
MessageId[MessageId["noManifestBackgroundColor"] = 56] = "noManifestBackgroundColor";
|
|
69
|
+
MessageId[MessageId["manifestThemeColor"] = 57] = "manifestThemeColor";
|
|
70
|
+
MessageId[MessageId["noManifestThemeColor"] = 58] = "noManifestThemeColor";
|
|
71
|
+
MessageId[MessageId["noManifestIcons"] = 59] = "noManifestIcons";
|
|
72
|
+
MessageId[MessageId["noManifestIcon"] = 60] = "noManifestIcon";
|
|
73
|
+
MessageId[MessageId["manifestIconDeclared"] = 61] = "manifestIconDeclared";
|
|
74
|
+
MessageId[MessageId["manifestIconCannotGet"] = 62] = "manifestIconCannotGet";
|
|
75
|
+
MessageId[MessageId["manifestIconDownloadable"] = 63] = "manifestIconDownloadable";
|
|
76
|
+
MessageId[MessageId["manifestIcon404"] = 64] = "manifestIcon404";
|
|
77
|
+
MessageId[MessageId["manifestIconNoHref"] = 65] = "manifestIconNoHref";
|
|
78
|
+
MessageId[MessageId["manifestIconNotSquare"] = 66] = "manifestIconNotSquare";
|
|
79
|
+
MessageId[MessageId["manifestIconRightSize"] = 67] = "manifestIconRightSize";
|
|
80
|
+
MessageId[MessageId["manifestIconSquare"] = 68] = "manifestIconSquare";
|
|
81
|
+
MessageId[MessageId["manifestIconWrongSize"] = 69] = "manifestIconWrongSize";
|
|
82
|
+
MessageId[MessageId["googleNoRobotsFile"] = 70] = "googleNoRobotsFile";
|
|
83
|
+
MessageId[MessageId["googleRobotsFileFound"] = 71] = "googleRobotsFileFound";
|
|
84
|
+
MessageId[MessageId["googleIcoBlockedByRobots"] = 72] = "googleIcoBlockedByRobots";
|
|
85
|
+
MessageId[MessageId["googleIcoAllowedByRobots"] = 73] = "googleIcoAllowedByRobots";
|
|
86
|
+
MessageId[MessageId["googleSvgIconBlockedByRobots"] = 74] = "googleSvgIconBlockedByRobots";
|
|
87
|
+
MessageId[MessageId["googleSvgIconAllowedByRobots"] = 75] = "googleSvgIconAllowedByRobots";
|
|
88
|
+
MessageId[MessageId["googlePngIconBlockedByRobots"] = 76] = "googlePngIconBlockedByRobots";
|
|
89
|
+
MessageId[MessageId["googlePngIconAllowedByRobots"] = 77] = "googlePngIconAllowedByRobots";
|
|
89
90
|
})(MessageId || (exports.MessageId = MessageId = {}));
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@realfavicongenerator/check-favicon",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"description": "Check the favicon of a website",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -40,5 +40,5 @@
|
|
|
40
40
|
"robots-parser": "^3.0.1",
|
|
41
41
|
"sharp": "^0.32.6"
|
|
42
42
|
},
|
|
43
|
-
"gitHead": "
|
|
43
|
+
"gitHead": "25d06206eedd021db005448c6b2cf015f6cfc3cf"
|
|
44
44
|
}
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import { parse } from 'node-html-parser'
|
|
2
|
+
import { checkIcoFavicon } from "./ico";
|
|
3
|
+
import { CheckerMessage, CheckerStatus, DesktopSingleReport, FetchResponse, MessageId } from '../types';
|
|
4
|
+
import { filePathToReadableStream } from '../helper';
|
|
5
|
+
import { testFetcher } from '../test-helper';
|
|
6
|
+
|
|
7
|
+
type TestOutput = {
|
|
8
|
+
messages: Pick<CheckerMessage, 'id' | 'status'>[];
|
|
9
|
+
icon: DesktopSingleReport['icon'];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const runIcoTest = async (
|
|
13
|
+
headFragment: string | null,
|
|
14
|
+
output: TestOutput,
|
|
15
|
+
fetchDatabase: { [url: string]: FetchResponse } = {},
|
|
16
|
+
checkContent = true
|
|
17
|
+
) => {
|
|
18
|
+
const root = headFragment ? parse(headFragment) : null;
|
|
19
|
+
const result = await checkIcoFavicon('https://example.com/', root, testFetcher(fetchDatabase));
|
|
20
|
+
const filteredMessages = result.messages.map(m => ({ status: m.status, id: m.id }));
|
|
21
|
+
expect(filteredMessages).toEqual(output.messages);
|
|
22
|
+
|
|
23
|
+
// Check icon properties - icon is always returned by checkIcoFavicon
|
|
24
|
+
const resultIcon = result.icon!;
|
|
25
|
+
const outputIcon = output.icon!;
|
|
26
|
+
|
|
27
|
+
expect(resultIcon.url).toEqual(outputIcon.url);
|
|
28
|
+
expect(resultIcon.width).toEqual(outputIcon.width);
|
|
29
|
+
expect(resultIcon.height).toEqual(outputIcon.height);
|
|
30
|
+
|
|
31
|
+
// For content, just check if it's null or not null unless exact match is needed
|
|
32
|
+
if (checkContent && outputIcon.content === null) {
|
|
33
|
+
expect(resultIcon.content).toBeNull();
|
|
34
|
+
} else if (checkContent && outputIcon.content !== null) {
|
|
35
|
+
expect(resultIcon.content).not.toBeNull();
|
|
36
|
+
expect(resultIcon.content).toMatch(/^data:image\/(png|bmp);base64,/);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
test('checkIcoFavicon - noHead', async () => {
|
|
41
|
+
await runIcoTest(null, {
|
|
42
|
+
messages: [{
|
|
43
|
+
status: CheckerStatus.Error,
|
|
44
|
+
id: MessageId.noHead,
|
|
45
|
+
}],
|
|
46
|
+
icon: {
|
|
47
|
+
content: null,
|
|
48
|
+
url: null,
|
|
49
|
+
width: null,
|
|
50
|
+
height: null,
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
test('checkIcoFavicon - noIcoFavicon', async () => {
|
|
56
|
+
await runIcoTest(`<title>Some text</title>`, {
|
|
57
|
+
messages: [{
|
|
58
|
+
status: CheckerStatus.Error,
|
|
59
|
+
id: MessageId.noIcoFavicon,
|
|
60
|
+
}],
|
|
61
|
+
icon: {
|
|
62
|
+
content: null,
|
|
63
|
+
url: null,
|
|
64
|
+
width: null,
|
|
65
|
+
height: null,
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
test('checkIcoFavicon - implicit /favicon.ico when not declared', async () => {
|
|
71
|
+
const testIconPath = './fixtures/simple-ico.ico';
|
|
72
|
+
|
|
73
|
+
await runIcoTest(`<title>Some text</title>`, {
|
|
74
|
+
messages: [{
|
|
75
|
+
status: CheckerStatus.Ok,
|
|
76
|
+
id: MessageId.icoFaviconImplicitInRoot,
|
|
77
|
+
},{
|
|
78
|
+
status: CheckerStatus.Ok,
|
|
79
|
+
id: MessageId.icoFaviconDownloadable,
|
|
80
|
+
}, {
|
|
81
|
+
status: CheckerStatus.Ok,
|
|
82
|
+
id: MessageId.icoFaviconExpectedSizes,
|
|
83
|
+
}],
|
|
84
|
+
icon: {
|
|
85
|
+
content: "data:image/png;base64,placeholder", // Will be checked for format only
|
|
86
|
+
url: 'https://example.com/favicon.ico',
|
|
87
|
+
width: 48,
|
|
88
|
+
height: 48,
|
|
89
|
+
}
|
|
90
|
+
}, {
|
|
91
|
+
'https://example.com/favicon.ico': {
|
|
92
|
+
status: 200,
|
|
93
|
+
contentType: 'image/x-icon',
|
|
94
|
+
readableStream: await filePathToReadableStream(testIconPath)
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
test('checkIcoFavicon - multipleIcoFavicons with shortcut icon', async () => {
|
|
100
|
+
await runIcoTest(`
|
|
101
|
+
<link rel="shortcut icon" href="/favicon1.ico" />
|
|
102
|
+
<link rel="shortcut icon" href="/favicon2.ico" />
|
|
103
|
+
`, {
|
|
104
|
+
messages: [{
|
|
105
|
+
status: CheckerStatus.Error,
|
|
106
|
+
id: MessageId.multipleIcoFavicons,
|
|
107
|
+
}],
|
|
108
|
+
icon: {
|
|
109
|
+
content: null,
|
|
110
|
+
url: null,
|
|
111
|
+
width: null,
|
|
112
|
+
height: null,
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
test('checkIcoFavicon - multipleIcoFavicons with type="image/x-icon"', async () => {
|
|
118
|
+
await runIcoTest(`
|
|
119
|
+
<link rel="icon" type="image/x-icon" href="/favicon1.ico" />
|
|
120
|
+
<link rel="icon" type="image/x-icon" href="/favicon2.ico" />
|
|
121
|
+
`, {
|
|
122
|
+
messages: [{
|
|
123
|
+
status: CheckerStatus.Error,
|
|
124
|
+
id: MessageId.multipleIcoFavicons,
|
|
125
|
+
}],
|
|
126
|
+
icon: {
|
|
127
|
+
content: null,
|
|
128
|
+
url: null,
|
|
129
|
+
width: null,
|
|
130
|
+
height: null,
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
test('checkIcoFavicon - icoFaviconDeclared & noIcoFaviconHref', async () => {
|
|
136
|
+
await runIcoTest(`<link rel="shortcut icon" />`, {
|
|
137
|
+
messages: [{
|
|
138
|
+
status: CheckerStatus.Ok,
|
|
139
|
+
id: MessageId.icoFaviconDeclared,
|
|
140
|
+
}, {
|
|
141
|
+
status: CheckerStatus.Error,
|
|
142
|
+
id: MessageId.noIcoFaviconHref,
|
|
143
|
+
}],
|
|
144
|
+
icon: {
|
|
145
|
+
content: null,
|
|
146
|
+
url: null,
|
|
147
|
+
width: null,
|
|
148
|
+
height: null,
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
test('checkIcoFavicon - icoFaviconDeclared & icoFavicon404', async () => {
|
|
154
|
+
await runIcoTest(`<link rel="shortcut icon" href="/favicon.ico" />`, {
|
|
155
|
+
messages: [{
|
|
156
|
+
status: CheckerStatus.Ok,
|
|
157
|
+
id: MessageId.icoFaviconDeclared,
|
|
158
|
+
}, {
|
|
159
|
+
status: CheckerStatus.Error,
|
|
160
|
+
id: MessageId.icoFavicon404,
|
|
161
|
+
}],
|
|
162
|
+
icon: {
|
|
163
|
+
content: null,
|
|
164
|
+
url: 'https://example.com/favicon.ico',
|
|
165
|
+
width: null,
|
|
166
|
+
height: null,
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
test('checkIcoFavicon - icoFaviconDeclared & icoFaviconCannotGet', async () => {
|
|
172
|
+
await runIcoTest(`<link rel="shortcut icon" href="/favicon.ico" />`, {
|
|
173
|
+
messages: [{
|
|
174
|
+
status: CheckerStatus.Ok,
|
|
175
|
+
id: MessageId.icoFaviconDeclared,
|
|
176
|
+
}, {
|
|
177
|
+
status: CheckerStatus.Error,
|
|
178
|
+
id: MessageId.icoFaviconCannotGet,
|
|
179
|
+
}],
|
|
180
|
+
icon: {
|
|
181
|
+
content: null,
|
|
182
|
+
url: 'https://example.com/favicon.ico',
|
|
183
|
+
width: null,
|
|
184
|
+
height: null,
|
|
185
|
+
}
|
|
186
|
+
}, {
|
|
187
|
+
'https://example.com/favicon.ico': {
|
|
188
|
+
status: 403,
|
|
189
|
+
contentType: 'image/x-icon'
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
test('checkIcoFavicon - icoFaviconDeclared & icoFaviconDownloadable & icoFaviconExpectedSizes', async () => {
|
|
195
|
+
const testIconPath = './fixtures/simple-ico.ico';
|
|
196
|
+
|
|
197
|
+
await runIcoTest(`<link rel="shortcut icon" href="/favicon.ico" />`, {
|
|
198
|
+
messages: [{
|
|
199
|
+
status: CheckerStatus.Ok,
|
|
200
|
+
id: MessageId.icoFaviconDeclared,
|
|
201
|
+
}, {
|
|
202
|
+
status: CheckerStatus.Ok,
|
|
203
|
+
id: MessageId.icoFaviconDownloadable,
|
|
204
|
+
}, {
|
|
205
|
+
status: CheckerStatus.Ok,
|
|
206
|
+
id: MessageId.icoFaviconExpectedSizes,
|
|
207
|
+
}],
|
|
208
|
+
icon: {
|
|
209
|
+
content: "data:image/png;base64,placeholder", // Will be checked for format only
|
|
210
|
+
url: 'https://example.com/favicon.ico',
|
|
211
|
+
width: 48,
|
|
212
|
+
height: 48,
|
|
213
|
+
}
|
|
214
|
+
}, {
|
|
215
|
+
'https://example.com/favicon.ico': {
|
|
216
|
+
status: 200,
|
|
217
|
+
contentType: 'image/x-icon',
|
|
218
|
+
readableStream: await filePathToReadableStream(testIconPath)
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
test('checkIcoFavicon - using type="image/x-icon"', async () => {
|
|
224
|
+
const testIconPath = './fixtures/simple-ico.ico';
|
|
225
|
+
|
|
226
|
+
await runIcoTest(`<link rel="icon" type="image/x-icon" href="/favicon.ico" />`, {
|
|
227
|
+
messages: [{
|
|
228
|
+
status: CheckerStatus.Ok,
|
|
229
|
+
id: MessageId.icoFaviconDeclared,
|
|
230
|
+
}, {
|
|
231
|
+
status: CheckerStatus.Ok,
|
|
232
|
+
id: MessageId.icoFaviconDownloadable,
|
|
233
|
+
}, {
|
|
234
|
+
status: CheckerStatus.Ok,
|
|
235
|
+
id: MessageId.icoFaviconExpectedSizes,
|
|
236
|
+
}],
|
|
237
|
+
icon: {
|
|
238
|
+
content: "data:image/png;base64,placeholder", // Will be checked for format only
|
|
239
|
+
url: 'https://example.com/favicon.ico',
|
|
240
|
+
width: 48,
|
|
241
|
+
height: 48,
|
|
242
|
+
}
|
|
243
|
+
}, {
|
|
244
|
+
'https://example.com/favicon.ico': {
|
|
245
|
+
status: 200,
|
|
246
|
+
contentType: 'image/x-icon',
|
|
247
|
+
readableStream: await filePathToReadableStream(testIconPath)
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
// For https://github.com/RealFaviconGenerator/core/issues/2
|
|
253
|
+
test('checkIcoFavicon - Protocol-relative URL', async () => {
|
|
254
|
+
const testIconPath = './fixtures/simple-ico.ico';
|
|
255
|
+
|
|
256
|
+
await runIcoTest(`<link rel="shortcut icon" href="//example.com/favicon.ico" />`, {
|
|
257
|
+
messages: [{
|
|
258
|
+
status: CheckerStatus.Ok,
|
|
259
|
+
id: MessageId.icoFaviconDeclared,
|
|
260
|
+
}, {
|
|
261
|
+
status: CheckerStatus.Ok,
|
|
262
|
+
id: MessageId.icoFaviconDownloadable,
|
|
263
|
+
}, {
|
|
264
|
+
status: CheckerStatus.Ok,
|
|
265
|
+
id: MessageId.icoFaviconExpectedSizes,
|
|
266
|
+
}],
|
|
267
|
+
icon: {
|
|
268
|
+
content: "data:image/png;base64,placeholder", // Will be checked for format only
|
|
269
|
+
url: 'https://example.com/favicon.ico',
|
|
270
|
+
width: 48,
|
|
271
|
+
height: 48,
|
|
272
|
+
}
|
|
273
|
+
}, {
|
|
274
|
+
'https://example.com/favicon.ico': {
|
|
275
|
+
status: 200,
|
|
276
|
+
contentType: 'image/x-icon',
|
|
277
|
+
readableStream: await filePathToReadableStream(testIconPath)
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
})
|
package/src/desktop/ico.ts
CHANGED
|
@@ -28,20 +28,16 @@ export const checkIcoFavicon = async (url: string, head: HTMLElement | null, fet
|
|
|
28
28
|
|
|
29
29
|
let iconUrl: string | null = null;
|
|
30
30
|
let images;
|
|
31
|
+
let isDeclared = false;
|
|
31
32
|
|
|
32
|
-
if (icos.length
|
|
33
|
-
messages.push({
|
|
34
|
-
status: CheckerStatus.Error,
|
|
35
|
-
id: MessageId.noIcoFavicon,
|
|
36
|
-
text: 'There is no ICO favicon'
|
|
37
|
-
});
|
|
38
|
-
} else if (icos.length > 1) {
|
|
33
|
+
if (icos.length > 1) {
|
|
39
34
|
messages.push({
|
|
40
35
|
status: CheckerStatus.Error,
|
|
41
36
|
id: MessageId.multipleIcoFavicons,
|
|
42
37
|
text: `There are ${icos.length} ICO favicons`
|
|
43
38
|
});
|
|
44
|
-
} else {
|
|
39
|
+
} else if (icos.length === 1) {
|
|
40
|
+
isDeclared = true;
|
|
45
41
|
messages.push({
|
|
46
42
|
status: CheckerStatus.Ok,
|
|
47
43
|
id: MessageId.icoFaviconDeclared,
|
|
@@ -57,63 +53,97 @@ export const checkIcoFavicon = async (url: string, head: HTMLElement | null, fet
|
|
|
57
53
|
});
|
|
58
54
|
} else {
|
|
59
55
|
iconUrl = mergeUrlAndPath(url, href);
|
|
60
|
-
|
|
61
|
-
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
// No declared ICO favicon, try the implicit /favicon.ico convention
|
|
59
|
+
iconUrl = mergeUrlAndPath(url, '/favicon.ico');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// If we have an iconUrl (either from declaration or implicit), try to fetch it
|
|
63
|
+
if (iconUrl) {
|
|
64
|
+
const iconResponse = await fetcher(iconUrl, 'image/x-icon');
|
|
65
|
+
if (iconResponse.status === 404) {
|
|
66
|
+
if (isDeclared) {
|
|
62
67
|
messages.push({
|
|
63
68
|
status: CheckerStatus.Error,
|
|
64
69
|
id: MessageId.icoFavicon404,
|
|
65
70
|
text: `ICO favicon not found at ${iconUrl}`
|
|
66
71
|
});
|
|
67
|
-
} else
|
|
72
|
+
} else {
|
|
73
|
+
// Implicit favicon not found, report no ICO favicon
|
|
74
|
+
messages.push({
|
|
75
|
+
status: CheckerStatus.Error,
|
|
76
|
+
id: MessageId.noIcoFavicon,
|
|
77
|
+
text: 'There is no ICO favicon'
|
|
78
|
+
});
|
|
79
|
+
iconUrl = null;
|
|
80
|
+
}
|
|
81
|
+
} else if (iconResponse.status >= 300 || !iconResponse.readableStream) {
|
|
82
|
+
if (isDeclared) {
|
|
68
83
|
messages.push({
|
|
69
84
|
status: CheckerStatus.Error,
|
|
70
85
|
id: MessageId.icoFaviconCannotGet,
|
|
71
86
|
text: `Error fetching ICO favicon at ${iconUrl} (status ${iconResponse.status})`
|
|
72
87
|
});
|
|
73
88
|
} else {
|
|
89
|
+
// Implicit favicon cannot be fetched, report no ICO favicon
|
|
90
|
+
messages.push({
|
|
91
|
+
status: CheckerStatus.Error,
|
|
92
|
+
id: MessageId.noIcoFavicon,
|
|
93
|
+
text: 'There is no ICO favicon'
|
|
94
|
+
});
|
|
95
|
+
iconUrl = null;
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
if (!isDeclared) {
|
|
74
99
|
messages.push({
|
|
75
100
|
status: CheckerStatus.Ok,
|
|
76
|
-
id: MessageId.
|
|
77
|
-
text: 'ICO favicon found'
|
|
101
|
+
id: MessageId.icoFaviconImplicitInRoot,
|
|
102
|
+
text: 'An implicit ICO favicon is found at /favicon.ico'
|
|
78
103
|
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
messages.push({
|
|
107
|
+
status: CheckerStatus.Ok,
|
|
108
|
+
id: MessageId.icoFaviconDownloadable,
|
|
109
|
+
text: 'ICO favicon found'
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const iconBuffer = await readableStreamToBuffer(iconResponse.readableStream);
|
|
113
|
+
images = decodeIco(iconBuffer);
|
|
114
|
+
|
|
115
|
+
const imageSizes = images.map(image => `${image.width}x${image.height}`);
|
|
116
|
+
|
|
117
|
+
const expectedSizes = IcoFaviconSizes.map(size => `${size}x${size}`);
|
|
118
|
+
|
|
119
|
+
const extraSizes = imageSizes.filter(size => !expectedSizes.includes(size));
|
|
120
|
+
if (extraSizes.length > 0) {
|
|
121
|
+
messages.push({
|
|
122
|
+
status: CheckerStatus.Warning,
|
|
123
|
+
id: MessageId.icoFaviconExtraSizes,
|
|
124
|
+
text: `Extra sizes found in ICO favicon: ${extraSizes.join(', ')}`
|
|
125
|
+
});
|
|
126
|
+
}
|
|
79
127
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
})
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const missingSizes = expectedSizes.filter(size => !imageSizes.includes(size));
|
|
97
|
-
if (missingSizes.length > 0) {
|
|
98
|
-
messages.push({
|
|
99
|
-
status: CheckerStatus.Warning,
|
|
100
|
-
id: MessageId.icoFaviconMissingSizes,
|
|
101
|
-
text: `Missing sizes in ICO favicon: ${missingSizes.join(', ')}`
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (extraSizes.length === 0 && missingSizes.length === 0) {
|
|
106
|
-
messages.push({
|
|
107
|
-
status: CheckerStatus.Ok,
|
|
108
|
-
id: MessageId.icoFaviconExpectedSizes,
|
|
109
|
-
text: `The ICO favicon has the expected sizes (${imageSizes.join(', ')})`
|
|
110
|
-
});
|
|
111
|
-
}
|
|
128
|
+
const missingSizes = expectedSizes.filter(size => !imageSizes.includes(size));
|
|
129
|
+
if (missingSizes.length > 0) {
|
|
130
|
+
messages.push({
|
|
131
|
+
status: CheckerStatus.Warning,
|
|
132
|
+
id: MessageId.icoFaviconMissingSizes,
|
|
133
|
+
text: `Missing sizes in ICO favicon: ${missingSizes.join(', ')}`
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (extraSizes.length === 0 && missingSizes.length === 0) {
|
|
138
|
+
messages.push({
|
|
139
|
+
status: CheckerStatus.Ok,
|
|
140
|
+
id: MessageId.icoFaviconExpectedSizes,
|
|
141
|
+
text: `The ICO favicon has the expected sizes (${imageSizes.join(', ')})`
|
|
142
|
+
});
|
|
112
143
|
}
|
|
113
144
|
}
|
|
114
145
|
}
|
|
115
146
|
|
|
116
|
-
let content: string | null = null;
|
|
117
147
|
const theIcon: CheckedIcon = {
|
|
118
148
|
content: null,
|
|
119
149
|
url: iconUrl,
|
|
@@ -123,7 +153,7 @@ export const checkIcoFavicon = async (url: string, head: HTMLElement | null, fet
|
|
|
123
153
|
if (images) {
|
|
124
154
|
const image = images[0];
|
|
125
155
|
const mimeType = (image.type === "bmp") ? "image/bmp" : "image/png";
|
|
126
|
-
theIcon.content =
|
|
156
|
+
theIcon.content = bufferToDataUrl(Buffer.from(image.data.buffer, image.data.byteOffset, image.data.byteLength), mimeType);
|
|
127
157
|
theIcon.width = image.width;
|
|
128
158
|
theIcon.height = image.height;
|
|
129
159
|
}
|