@realfavicongenerator/check-favicon 0.4.6 → 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/dist/desktop/desktop.d.ts +4 -4
- package/dist/desktop/desktop.js +40 -14
- package/dist/desktop/desktop.test.js +157 -57
- 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 +32 -9
- package/dist/helper.test.js +22 -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 +5 -4
- package/src/desktop/desktop.test.ts +162 -58
- 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 +24 -4
- package/src/helper.ts +40 -9
- 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/dist/helper.js
CHANGED
|
@@ -12,7 +12,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
12
12
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
13
|
};
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.fetchFetcher = exports.bufferToDataUrl = exports.parseSizesAttribute = exports.mergeUrlAndPath = exports.checkIcon = exports.pathToMimeType = exports.readableStreamToBuffer = exports.readableStreamToString = exports.stringToReadableStream = exports.filePathToString = exports.filePathToReadableStream = void 0;
|
|
15
|
+
exports.fetchFetcher = exports.filePathToDataUrl = exports.bufferToDataUrl = exports.parseSizesAttribute = exports.mergeUrlAndPath = exports.checkIcon = exports.pathToMimeType = exports.readableStreamToBuffer = exports.readableStreamToString = exports.stringToReadableStream = exports.filePathToString = exports.filePathToReadableStream = void 0;
|
|
16
16
|
const promises_1 = __importDefault(require("fs/promises"));
|
|
17
17
|
const sharp_1 = __importDefault(require("sharp"));
|
|
18
18
|
const filePathToReadableStream = (path) => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -35,9 +35,11 @@ const filePathToString = (path) => __awaiter(void 0, void 0, void 0, function* (
|
|
|
35
35
|
});
|
|
36
36
|
exports.filePathToString = filePathToString;
|
|
37
37
|
const stringToReadableStream = (str) => {
|
|
38
|
+
const encoder = new TextEncoder();
|
|
39
|
+
const uint8Array = encoder.encode(str);
|
|
38
40
|
return new ReadableStream({
|
|
39
41
|
start(controller) {
|
|
40
|
-
controller.enqueue(
|
|
42
|
+
controller.enqueue(uint8Array);
|
|
41
43
|
controller.close();
|
|
42
44
|
}
|
|
43
45
|
});
|
|
@@ -114,13 +116,13 @@ const checkIcon = (iconUrl, processor, fetcher, mimeType, expectedWidthHeight) =
|
|
|
114
116
|
}
|
|
115
117
|
else if (res.readableStream) {
|
|
116
118
|
processor.downloadable();
|
|
117
|
-
const
|
|
118
|
-
const meta = yield (0, sharp_1.default)(
|
|
119
|
+
const rawContent = yield (0, exports.readableStreamToBuffer)(res.readableStream);
|
|
120
|
+
const meta = yield (0, sharp_1.default)(rawContent).metadata();
|
|
119
121
|
const contentType = res.contentType || (0, exports.pathToMimeType)(iconUrl);
|
|
122
|
+
const content = yield (0, exports.bufferToDataUrl)(rawContent, contentType);
|
|
120
123
|
if (meta.width && meta.height) {
|
|
121
124
|
if (meta.width !== meta.height) {
|
|
122
125
|
processor.notSquare(meta.width, meta.height);
|
|
123
|
-
return null;
|
|
124
126
|
}
|
|
125
127
|
else {
|
|
126
128
|
processor.square(meta.width);
|
|
@@ -134,9 +136,19 @@ const checkIcon = (iconUrl, processor, fetcher, mimeType, expectedWidthHeight) =
|
|
|
134
136
|
}
|
|
135
137
|
}
|
|
136
138
|
}
|
|
137
|
-
return
|
|
139
|
+
return {
|
|
140
|
+
content,
|
|
141
|
+
url: iconUrl,
|
|
142
|
+
width: meta.width || null,
|
|
143
|
+
height: meta.height || null
|
|
144
|
+
};
|
|
138
145
|
}
|
|
139
|
-
return
|
|
146
|
+
return {
|
|
147
|
+
content: null,
|
|
148
|
+
url: iconUrl,
|
|
149
|
+
width: null,
|
|
150
|
+
height: null
|
|
151
|
+
};
|
|
140
152
|
});
|
|
141
153
|
exports.checkIcon = checkIcon;
|
|
142
154
|
const mergeUrlAndPath = (baseUrl, absoluteOrRelativePath) => {
|
|
@@ -145,8 +157,12 @@ const mergeUrlAndPath = (baseUrl, absoluteOrRelativePath) => {
|
|
|
145
157
|
return absoluteOrRelativePath;
|
|
146
158
|
}
|
|
147
159
|
const url = new URL(baseUrl);
|
|
148
|
-
//
|
|
149
|
-
if (absoluteOrRelativePath.startsWith('
|
|
160
|
+
// Protocol-relative URL
|
|
161
|
+
if (absoluteOrRelativePath.startsWith('//')) {
|
|
162
|
+
return `${url.protocol}${absoluteOrRelativePath}`;
|
|
163
|
+
}
|
|
164
|
+
else if (absoluteOrRelativePath.startsWith('/')) {
|
|
165
|
+
// If the path starts with a slash, replace the pathname
|
|
150
166
|
return `${url.origin}${absoluteOrRelativePath}`;
|
|
151
167
|
}
|
|
152
168
|
else {
|
|
@@ -173,6 +189,13 @@ const bufferToDataUrl = (buffer, mimeType) => {
|
|
|
173
189
|
return `data:${mimeType};base64,${buffer.toString('base64')}`;
|
|
174
190
|
};
|
|
175
191
|
exports.bufferToDataUrl = bufferToDataUrl;
|
|
192
|
+
const filePathToDataUrl = (filePath) => __awaiter(void 0, void 0, void 0, function* () {
|
|
193
|
+
const readStream = yield (0, exports.filePathToReadableStream)(filePath);
|
|
194
|
+
const rawContent = yield (0, exports.readableStreamToBuffer)(readStream);
|
|
195
|
+
const contentType = (0, exports.pathToMimeType)(filePath);
|
|
196
|
+
return (0, exports.bufferToDataUrl)(rawContent, contentType);
|
|
197
|
+
});
|
|
198
|
+
exports.filePathToDataUrl = filePathToDataUrl;
|
|
176
199
|
const fetchFetcher = (url, contentType) => __awaiter(void 0, void 0, void 0, function* () {
|
|
177
200
|
const res = yield fetch(url, {
|
|
178
201
|
headers: {
|
package/dist/helper.test.js
CHANGED
|
@@ -34,7 +34,12 @@ test('checkIcon - noHref', () => __awaiter(void 0, void 0, void 0, function* ()
|
|
|
34
34
|
}));
|
|
35
35
|
test('checkIcon - icon404', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
36
36
|
const processor = getTestProcessor();
|
|
37
|
-
expect(yield (0, helper_1.checkIcon)('/does-not-exist.png', processor.processor, (0, test_helper_1.testFetcher)({}), 'image/png')).
|
|
37
|
+
expect(yield (0, helper_1.checkIcon)('/does-not-exist.png', processor.processor, (0, test_helper_1.testFetcher)({}), 'image/png')).toEqual({
|
|
38
|
+
content: null,
|
|
39
|
+
url: '/does-not-exist.png',
|
|
40
|
+
width: null,
|
|
41
|
+
height: null
|
|
42
|
+
});
|
|
38
43
|
expect(processor.messages).toEqual(['icon404']);
|
|
39
44
|
}));
|
|
40
45
|
test('checkIcon - icon404', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -44,7 +49,12 @@ test('checkIcon - icon404', () => __awaiter(void 0, void 0, void 0, function* ()
|
|
|
44
49
|
contentType: 'image/png',
|
|
45
50
|
status: 500
|
|
46
51
|
}
|
|
47
|
-
}), 'image/png')).
|
|
52
|
+
}), 'image/png')).toEqual({
|
|
53
|
+
content: null,
|
|
54
|
+
url: '/bad-icon.png',
|
|
55
|
+
width: null,
|
|
56
|
+
height: null
|
|
57
|
+
});
|
|
48
58
|
expect(processor.messages).toEqual(['cannotGet 500']);
|
|
49
59
|
}));
|
|
50
60
|
test('checkIcon - downloadable & square', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -99,7 +109,12 @@ test('checkIcon - downloadable & notSquare', () => __awaiter(void 0, void 0, voi
|
|
|
99
109
|
contentType: 'image/png',
|
|
100
110
|
readableStream: yield (0, helper_1.filePathToReadableStream)(nonSquareIcon)
|
|
101
111
|
}
|
|
102
|
-
}), 'image/png', 500)).
|
|
112
|
+
}), 'image/png', 500)).toEqual({
|
|
113
|
+
content: yield (0, helper_1.filePathToDataUrl)(nonSquareIcon),
|
|
114
|
+
url: '/non-square-icon.png',
|
|
115
|
+
width: 240,
|
|
116
|
+
height: 180
|
|
117
|
+
});
|
|
103
118
|
expect(processor.messages).toEqual([
|
|
104
119
|
'downloadable',
|
|
105
120
|
'notSquare 240x180'
|
|
@@ -112,6 +127,10 @@ test('mergeUrlAndPath', () => {
|
|
|
112
127
|
expect((0, helper_1.mergeUrlAndPath)('https://example.com/sub-page', '/some-path')).toBe('https://example.com/some-path');
|
|
113
128
|
expect((0, helper_1.mergeUrlAndPath)('https://example.com/sub-page', 'some/path')).toBe('https://example.com/sub-page/some/path');
|
|
114
129
|
expect((0, helper_1.mergeUrlAndPath)('https://example.com', 'https://elsewhere.com/some-path')).toBe('https://elsewhere.com/some-path');
|
|
130
|
+
// Protocol-relative URL
|
|
131
|
+
// For https://github.com/RealFaviconGenerator/core/issues/2
|
|
132
|
+
expect((0, helper_1.mergeUrlAndPath)('https://example.com', '//elsewhere.com/some-path')).toBe('https://elsewhere.com/some-path');
|
|
133
|
+
expect((0, helper_1.mergeUrlAndPath)('http://example.com', '//elsewhere.com/some-other/path')).toBe('http://elsewhere.com/some-other/path');
|
|
115
134
|
});
|
|
116
135
|
test('parseSizesAttribute', () => {
|
|
117
136
|
expect((0, helper_1.parseSizesAttribute)(null)).toEqual(null);
|
package/dist/touch-icon.js
CHANGED
|
@@ -159,7 +159,7 @@ const checkTouchIconIcon = (baseUrl_2, head_2, ...args_2) => __awaiter(void 0, [
|
|
|
159
159
|
}
|
|
160
160
|
};
|
|
161
161
|
touchIcon = yield (0, helper_1.checkIcon)(touchIconUrl, processor, fetcher, undefined);
|
|
162
|
-
return { messages, touchIcon };
|
|
162
|
+
return { messages, touchIcon: touchIcon ? touchIcon.content : null };
|
|
163
163
|
});
|
|
164
164
|
exports.checkTouchIconIcon = checkTouchIconIcon;
|
|
165
165
|
const getDuplicatedSizes = (sizes) => {
|
package/dist/types.d.ts
CHANGED
|
@@ -72,7 +72,15 @@ export declare enum MessageId {
|
|
|
72
72
|
manifestIconNotSquare = 65,
|
|
73
73
|
manifestIconRightSize = 66,
|
|
74
74
|
manifestIconSquare = 67,
|
|
75
|
-
manifestIconWrongSize = 68
|
|
75
|
+
manifestIconWrongSize = 68,
|
|
76
|
+
googleNoRobotsFile = 69,
|
|
77
|
+
googleRobotsFileFound = 70,
|
|
78
|
+
googleIcoBlockedByRobots = 71,
|
|
79
|
+
googleIcoAllowedByRobots = 72,
|
|
80
|
+
googleSvgIconBlockedByRobots = 73,
|
|
81
|
+
googleSvgIconAllowedByRobots = 74,
|
|
82
|
+
googlePngIconBlockedByRobots = 75,
|
|
83
|
+
googlePngIconAllowedByRobots = 76
|
|
76
84
|
}
|
|
77
85
|
export type CheckerMessage = {
|
|
78
86
|
status: CheckerStatus;
|
|
@@ -85,9 +93,24 @@ export type FetchResponse = {
|
|
|
85
93
|
readableStream?: ReadableStream | null;
|
|
86
94
|
};
|
|
87
95
|
export type Fetcher = (url: string, contentType?: string) => Promise<FetchResponse>;
|
|
96
|
+
export type CheckedIcon = {
|
|
97
|
+
content: string | null;
|
|
98
|
+
url: string | null;
|
|
99
|
+
width: number | null;
|
|
100
|
+
height: number | null;
|
|
101
|
+
};
|
|
102
|
+
export type DesktopSingleReport = {
|
|
103
|
+
messages: CheckerMessage[];
|
|
104
|
+
icon: CheckedIcon | null;
|
|
105
|
+
};
|
|
88
106
|
export type DesktopFaviconReport = {
|
|
89
107
|
messages: CheckerMessage[];
|
|
90
108
|
icon: string | null;
|
|
109
|
+
icons: {
|
|
110
|
+
png: CheckedIcon | null;
|
|
111
|
+
ico: CheckedIcon | null;
|
|
112
|
+
svg: CheckedIcon | null;
|
|
113
|
+
};
|
|
91
114
|
};
|
|
92
115
|
export type TouchIconTitleReport = {
|
|
93
116
|
messages: CheckerMessage[];
|
|
@@ -111,3 +134,8 @@ export type FaviconReport = {
|
|
|
111
134
|
webAppManifest: WebAppManifestReport;
|
|
112
135
|
};
|
|
113
136
|
export type TouchIconReport = TouchIconIconReport & TouchIconTitleReport;
|
|
137
|
+
export type GoogleReport = {
|
|
138
|
+
messages: CheckerMessage[];
|
|
139
|
+
icon: string | null;
|
|
140
|
+
icons: CheckedIcon[];
|
|
141
|
+
};
|
package/dist/types.js
CHANGED
|
@@ -78,4 +78,12 @@ var MessageId;
|
|
|
78
78
|
MessageId[MessageId["manifestIconRightSize"] = 66] = "manifestIconRightSize";
|
|
79
79
|
MessageId[MessageId["manifestIconSquare"] = 67] = "manifestIconSquare";
|
|
80
80
|
MessageId[MessageId["manifestIconWrongSize"] = 68] = "manifestIconWrongSize";
|
|
81
|
+
MessageId[MessageId["googleNoRobotsFile"] = 69] = "googleNoRobotsFile";
|
|
82
|
+
MessageId[MessageId["googleRobotsFileFound"] = 70] = "googleRobotsFileFound";
|
|
83
|
+
MessageId[MessageId["googleIcoBlockedByRobots"] = 71] = "googleIcoBlockedByRobots";
|
|
84
|
+
MessageId[MessageId["googleIcoAllowedByRobots"] = 72] = "googleIcoAllowedByRobots";
|
|
85
|
+
MessageId[MessageId["googleSvgIconBlockedByRobots"] = 73] = "googleSvgIconBlockedByRobots";
|
|
86
|
+
MessageId[MessageId["googleSvgIconAllowedByRobots"] = 74] = "googleSvgIconAllowedByRobots";
|
|
87
|
+
MessageId[MessageId["googlePngIconBlockedByRobots"] = 75] = "googlePngIconBlockedByRobots";
|
|
88
|
+
MessageId[MessageId["googlePngIconAllowedByRobots"] = 76] = "googlePngIconAllowedByRobots";
|
|
81
89
|
})(MessageId || (exports.MessageId = MessageId = {}));
|
package/dist/web-app-manifest.js
CHANGED
|
@@ -257,6 +257,6 @@ const checkWebAppManifestFile = (manifest, baseUrl, fetcher) => __awaiter(void 0
|
|
|
257
257
|
}
|
|
258
258
|
finally { if (e_1) throw e_1.error; }
|
|
259
259
|
}
|
|
260
|
-
return { messages, name, shortName, backgroundColor, themeColor, icon };
|
|
260
|
+
return { messages, name, shortName, backgroundColor, themeColor, icon: icon ? icon.content : null };
|
|
261
261
|
});
|
|
262
262
|
exports.checkWebAppManifestFile = checkWebAppManifestFile;
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@realfavicongenerator/check-favicon",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.8",
|
|
4
4
|
"description": "Check the favicon of a website",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"test": "jest",
|
|
9
|
-
"test:watch": "jest --watch",
|
|
8
|
+
"test": "jest src",
|
|
9
|
+
"test:watch": "jest src --watch",
|
|
10
10
|
"build": "tsc",
|
|
11
11
|
"prepublishOnly": "npm run build"
|
|
12
12
|
},
|
|
@@ -37,7 +37,8 @@
|
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"decode-ico": "^0.4.1",
|
|
39
39
|
"node-html-parser": "^6.1.13",
|
|
40
|
+
"robots-parser": "^3.0.1",
|
|
40
41
|
"sharp": "^0.32.6"
|
|
41
42
|
},
|
|
42
|
-
"gitHead": "
|
|
43
|
+
"gitHead": "723c35120dced8dac83b7938c1cf76844d27a535"
|
|
43
44
|
}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { parse } from 'node-html-parser'
|
|
2
2
|
import { checkPngFavicon, checkSvgFavicon } from "./desktop";
|
|
3
|
-
import { CheckerMessage, CheckerStatus, FetchResponse, MessageId } from '../types';
|
|
4
|
-
import { filePathToReadableStream, filePathToString, stringToReadableStream } from '../helper';
|
|
3
|
+
import { CheckerMessage, CheckerStatus, DesktopFaviconReport, FetchResponse, MessageId } from '../types';
|
|
4
|
+
import { filePathToDataUrl, filePathToReadableStream, filePathToString, stringToReadableStream } from '../helper';
|
|
5
5
|
import { testFetcher } from '../test-helper';
|
|
6
6
|
|
|
7
|
-
type TestOutput =
|
|
7
|
+
type TestOutput = {
|
|
8
|
+
messages: Pick<CheckerMessage, 'id' | 'status'>[];
|
|
9
|
+
icons: DesktopFaviconReport['icons'];
|
|
10
|
+
}
|
|
8
11
|
|
|
9
12
|
const runSvgTest = async (
|
|
10
13
|
headFragment: string | null,
|
|
@@ -13,62 +16,140 @@ const runSvgTest = async (
|
|
|
13
16
|
) => {
|
|
14
17
|
const root = headFragment ? parse(headFragment) : null;
|
|
15
18
|
const result = await checkSvgFavicon('https://example.com/', root, testFetcher(fetchDatabase));
|
|
16
|
-
const filteredMessages = result.map(m => ({ status: m.status, id: m.id }));
|
|
17
|
-
expect(filteredMessages).toEqual(output);
|
|
19
|
+
const filteredMessages = result.messages.map(m => ({ status: m.status, id: m.id }));
|
|
20
|
+
expect(filteredMessages).toEqual(output.messages);
|
|
18
21
|
}
|
|
19
22
|
|
|
20
23
|
test('checkSvgFavicon - noHead', async () => {
|
|
21
|
-
await runSvgTest(null,
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
await runSvgTest(null, {
|
|
25
|
+
messages: [{
|
|
26
|
+
status: CheckerStatus.Error,
|
|
27
|
+
id: MessageId.noHead,
|
|
28
|
+
}],
|
|
29
|
+
icons: {
|
|
30
|
+
png: null,
|
|
31
|
+
ico: null,
|
|
32
|
+
svg: null,
|
|
33
|
+
}
|
|
34
|
+
});
|
|
25
35
|
})
|
|
26
36
|
|
|
27
37
|
test('checkSvgFavicon - noSvgFavicon', async () => {
|
|
28
|
-
await runSvgTest(`<title>
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
38
|
+
await runSvgTest(`<title>Some text</title>`, {
|
|
39
|
+
messages: [{
|
|
40
|
+
status: CheckerStatus.Error,
|
|
41
|
+
id: MessageId.noSvgFavicon,
|
|
42
|
+
}],
|
|
43
|
+
icons: {
|
|
44
|
+
png: null,
|
|
45
|
+
ico: null,
|
|
46
|
+
svg: null,
|
|
47
|
+
}
|
|
48
|
+
});
|
|
32
49
|
})
|
|
33
50
|
|
|
34
51
|
test('checkSvgFavicon - multipleSvgFavicons', async () => {
|
|
35
52
|
await runSvgTest(`
|
|
36
53
|
<link rel="icon" type="image/svg+xml" href="/the-icon.svg" />
|
|
37
54
|
<link rel="icon" type="image/svg+xml" href="/another-icon.svg" />
|
|
38
|
-
`,
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
55
|
+
`, {
|
|
56
|
+
messages: [{
|
|
57
|
+
status: CheckerStatus.Error,
|
|
58
|
+
id: MessageId.multipleSvgFavicons,
|
|
59
|
+
}], icons: {
|
|
60
|
+
png: null,
|
|
61
|
+
ico: null,
|
|
62
|
+
svg: null,
|
|
63
|
+
}
|
|
64
|
+
});
|
|
42
65
|
})
|
|
43
66
|
|
|
44
67
|
test('checkSvgFavicon - svgFaviconDeclared & noSvgFaviconHref', async () => {
|
|
45
|
-
await runSvgTest(`<link rel="icon" type="image/svg+xml" />`,
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
68
|
+
await runSvgTest(`<link rel="icon" type="image/svg+xml" />`, {
|
|
69
|
+
messages: [{
|
|
70
|
+
status: CheckerStatus.Ok,
|
|
71
|
+
id: MessageId.svgFaviconDeclared,
|
|
72
|
+
}, {
|
|
73
|
+
status: CheckerStatus.Error,
|
|
74
|
+
id: MessageId.noSvgFaviconHref,
|
|
75
|
+
}],
|
|
76
|
+
icons: {
|
|
77
|
+
png: null,
|
|
78
|
+
ico: null,
|
|
79
|
+
svg: null,
|
|
80
|
+
}
|
|
81
|
+
});
|
|
52
82
|
})
|
|
53
83
|
|
|
54
84
|
test('checkSvgFavicon - svgFaviconDeclared & svgFavicon404', async () => {
|
|
55
|
-
await runSvgTest(`<link rel="icon" type="image/svg+xml" href="/the-icon.svg" />`,
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
85
|
+
await runSvgTest(`<link rel="icon" type="image/svg+xml" href="/the-icon.svg" />`, {
|
|
86
|
+
messages: [{
|
|
87
|
+
status: CheckerStatus.Ok,
|
|
88
|
+
id: MessageId.svgFaviconDeclared,
|
|
89
|
+
}, {
|
|
90
|
+
status: CheckerStatus.Error,
|
|
91
|
+
id: MessageId.svgFavicon404,
|
|
92
|
+
}],
|
|
93
|
+
icons: {
|
|
94
|
+
png: null,
|
|
95
|
+
ico: null,
|
|
96
|
+
svg: {
|
|
97
|
+
content: null,
|
|
98
|
+
url: 'https://example.com/the-icon.svg',
|
|
99
|
+
width: null,
|
|
100
|
+
height: null,
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
});
|
|
62
104
|
})
|
|
63
105
|
|
|
64
106
|
test('checkSvgFavicon - svgFaviconDeclared & svgFaviconCannotGet', async () => {
|
|
65
|
-
await runSvgTest(`<link rel="icon" type="image/svg+xml" href="/the-icon.svg" />`,
|
|
66
|
-
|
|
67
|
-
|
|
107
|
+
await runSvgTest(`<link rel="icon" type="image/svg+xml" href="/the-icon.svg" />`, {
|
|
108
|
+
messages: [{
|
|
109
|
+
status: CheckerStatus.Ok,
|
|
110
|
+
id: MessageId.svgFaviconDeclared,
|
|
111
|
+
}, {
|
|
112
|
+
status: CheckerStatus.Error,
|
|
113
|
+
id: MessageId.svgFaviconCannotGet,
|
|
114
|
+
}],
|
|
115
|
+
icons: {
|
|
116
|
+
png: null,
|
|
117
|
+
ico: null,
|
|
118
|
+
svg: {
|
|
119
|
+
content: null,
|
|
120
|
+
url: 'https://example.com/the-icon.svg',
|
|
121
|
+
width: null,
|
|
122
|
+
height: null,
|
|
123
|
+
},
|
|
124
|
+
}
|
|
125
|
+
}, {
|
|
126
|
+
'https://example.com/the-icon.svg': {
|
|
127
|
+
status: 403,
|
|
128
|
+
contentType: 'image/svg+xml'
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
// For https://github.com/RealFaviconGenerator/core/issues/2
|
|
134
|
+
test('checkSvgFavicon - Protocol-relative URL', async () => {
|
|
135
|
+
await runSvgTest(`<link rel="icon" type="image/svg+xml" href="//example.com/the-icon.svg" />`, {
|
|
136
|
+
messages: [{
|
|
137
|
+
status: CheckerStatus.Ok,
|
|
138
|
+
id: MessageId.svgFaviconDeclared,
|
|
139
|
+
}, {
|
|
140
|
+
status: CheckerStatus.Error,
|
|
141
|
+
id: MessageId.svgFaviconCannotGet,
|
|
142
|
+
}], icons: {
|
|
143
|
+
png: null,
|
|
144
|
+
ico: null,
|
|
145
|
+
svg: {
|
|
146
|
+
content: null,
|
|
147
|
+
url: 'https://example.com/the-icon.svg',
|
|
148
|
+
width: null,
|
|
149
|
+
height: null,
|
|
150
|
+
},
|
|
151
|
+
}
|
|
68
152
|
}, {
|
|
69
|
-
status: CheckerStatus.Error,
|
|
70
|
-
id: MessageId.svgFaviconCannotGet,
|
|
71
|
-
}], {
|
|
72
153
|
'https://example.com/the-icon.svg': {
|
|
73
154
|
status: 403,
|
|
74
155
|
contentType: 'image/svg+xml'
|
|
@@ -81,17 +162,29 @@ test('checkSvgFavicon - svgFaviconDeclared & svgFaviconDownloadable & svgFavicon
|
|
|
81
162
|
|
|
82
163
|
const serpIcon = await filePathToString(testIconPath);
|
|
83
164
|
|
|
84
|
-
await runSvgTest(`<link rel="icon" type="image/svg+xml" href="/the-icon.svg" />`,
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
165
|
+
await runSvgTest(`<link rel="icon" type="image/svg+xml" href="/the-icon.svg" />`, {
|
|
166
|
+
messages: [
|
|
167
|
+
{
|
|
168
|
+
status: CheckerStatus.Ok,
|
|
169
|
+
id: MessageId.svgFaviconDeclared,
|
|
170
|
+
}, {
|
|
171
|
+
status: CheckerStatus.Ok,
|
|
172
|
+
id: MessageId.svgFaviconDownloadable,
|
|
173
|
+
}, {
|
|
174
|
+
status: CheckerStatus.Ok,
|
|
175
|
+
id: MessageId.svgFaviconSquare,
|
|
176
|
+
}],
|
|
177
|
+
icons: {
|
|
178
|
+
png: null,
|
|
179
|
+
ico: null,
|
|
180
|
+
svg: {
|
|
181
|
+
content: await filePathToDataUrl(testIconPath),
|
|
182
|
+
url: 'https://example.com/the-icon.svg',
|
|
183
|
+
width: 36,
|
|
184
|
+
height: 36,
|
|
185
|
+
},
|
|
186
|
+
}
|
|
91
187
|
}, {
|
|
92
|
-
status: CheckerStatus.Ok,
|
|
93
|
-
id: MessageId.svgFaviconSquare,
|
|
94
|
-
}], {
|
|
95
188
|
'https://example.com/the-icon.svg': {
|
|
96
189
|
status: 200,
|
|
97
190
|
contentType: 'image/svg+xml',
|
|
@@ -108,7 +201,7 @@ const runPngTest = async (
|
|
|
108
201
|
const root = headFragment ? parse(headFragment) : null;
|
|
109
202
|
const result = await checkPngFavicon('https://example.com/', root, testFetcher(fetchDatabase));
|
|
110
203
|
const filteredMessages = result.messages.map(m => ({ status: m.status, id: m.id }));
|
|
111
|
-
expect(filteredMessages).toEqual(output);
|
|
204
|
+
expect(filteredMessages).toEqual(output.messages);
|
|
112
205
|
}
|
|
113
206
|
|
|
114
207
|
const testIcon16 = './fixtures/16x16.png';
|
|
@@ -122,16 +215,27 @@ test('checkSvgFavicon - Three PNG icons with different sizes', async () => {
|
|
|
122
215
|
<link rel="icon" type="image/png" sizes="32x32" href="/favicon/favicon-32x32.png">
|
|
123
216
|
<link rel="icon" type="image/png" sizes="48x48" href="/favicon/favicon-48x48.png">
|
|
124
217
|
<link rel="icon" type="image/png" sizes="96x96" href="/favicon/favicon-96x96.png">
|
|
125
|
-
`, [{
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
218
|
+
`, { messages: [{
|
|
219
|
+
status: CheckerStatus.Ok,
|
|
220
|
+
id: MessageId.desktopPngFaviconDeclared,
|
|
221
|
+
}, {
|
|
222
|
+
status: CheckerStatus.Ok,
|
|
223
|
+
id: MessageId.desktopPngFaviconDownloadable,
|
|
224
|
+
}, {
|
|
225
|
+
status: CheckerStatus.Ok,
|
|
226
|
+
id: MessageId.desktopPngFaviconRightSize,
|
|
227
|
+
}],
|
|
228
|
+
icons: {
|
|
229
|
+
png: {
|
|
230
|
+
content: await filePathToDataUrl(testIcon96),
|
|
231
|
+
url: 'https://example.com/favicon/favicon-96x96.png',
|
|
232
|
+
width: 96,
|
|
233
|
+
height: 96,
|
|
234
|
+
},
|
|
235
|
+
ico: null,
|
|
236
|
+
svg: null,
|
|
237
|
+
}
|
|
238
|
+
},
|
|
135
239
|
{
|
|
136
240
|
'https://example.com/favicon/favicon-16x16.png': {
|
|
137
241
|
status: 200,
|
package/src/desktop/desktop.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { CheckerMessage, CheckerStatus, DesktopFaviconReport, Fetcher, MessageId } from "../types";
|
|
1
|
+
import { CheckerMessage, CheckerStatus, DesktopFaviconReport, DesktopSingleReport, Fetcher, MessageId } from "../types";
|
|
2
2
|
import { HTMLElement } from 'node-html-parser'
|
|
3
3
|
import sharp from 'sharp'
|
|
4
|
-
import { CheckIconProcessor, checkIcon, fetchFetcher, mergeUrlAndPath, readableStreamToString } from "../helper";
|
|
4
|
+
import { CheckIconProcessor, bufferToDataUrl, checkIcon, fetchFetcher, mergeUrlAndPath, readableStreamToString } from "../helper";
|
|
5
5
|
import { checkIcoFavicon } from "./ico";
|
|
6
6
|
|
|
7
7
|
export const PngFaviconFileSize = 96;
|
|
8
8
|
|
|
9
|
-
export const checkSvgFavicon = async (baseUrl: string, head: HTMLElement | null, fetcher: Fetcher = fetchFetcher): Promise<
|
|
9
|
+
export const checkSvgFavicon = async (baseUrl: string, head: HTMLElement | null, fetcher: Fetcher = fetchFetcher): Promise<DesktopSingleReport> => {
|
|
10
10
|
const messages: CheckerMessage[] = [];
|
|
11
11
|
|
|
12
12
|
if (!head) {
|
|
@@ -16,7 +16,10 @@ export const checkSvgFavicon = async (baseUrl: string, head: HTMLElement | null,
|
|
|
16
16
|
text: 'No <head> element'
|
|
17
17
|
});
|
|
18
18
|
|
|
19
|
-
return
|
|
19
|
+
return {
|
|
20
|
+
messages,
|
|
21
|
+
icon: { content: null, url: null, width: null, height: null }
|
|
22
|
+
};
|
|
20
23
|
}
|
|
21
24
|
|
|
22
25
|
const svgs = head?.querySelectorAll("link[rel='icon'][type='image/svg+xml']");
|
|
@@ -47,20 +50,29 @@ export const checkSvgFavicon = async (baseUrl: string, head: HTMLElement | null,
|
|
|
47
50
|
text: 'The SVG markup has no href attribute'
|
|
48
51
|
});
|
|
49
52
|
} else {
|
|
50
|
-
const
|
|
51
|
-
return
|
|
53
|
+
const iconReport = await checkSvgFaviconFile(baseUrl, href, fetcher)
|
|
54
|
+
return {
|
|
55
|
+
messages: [ ...messages, ...iconReport.messages ],
|
|
56
|
+
icon: iconReport.icon
|
|
57
|
+
};
|
|
52
58
|
}
|
|
53
59
|
}
|
|
54
60
|
|
|
55
|
-
return
|
|
61
|
+
return {
|
|
62
|
+
messages,
|
|
63
|
+
icon: { content: null, url: null, width: null, height: null }
|
|
64
|
+
};
|
|
56
65
|
}
|
|
57
66
|
|
|
58
|
-
export const checkSvgFaviconFile = async (baseUrl: string, url: string, fetcher: Fetcher): Promise<
|
|
67
|
+
export const checkSvgFaviconFile = async (baseUrl: string, url: string, fetcher: Fetcher): Promise<DesktopSingleReport> => {
|
|
59
68
|
const messages: CheckerMessage[] = [];
|
|
60
69
|
|
|
61
70
|
const svgUrl = mergeUrlAndPath(baseUrl, url);
|
|
62
71
|
|
|
63
72
|
const res = await fetcher(svgUrl, 'image/svg+xml');
|
|
73
|
+
let content;
|
|
74
|
+
let width: number | null = null;
|
|
75
|
+
let height: number | null = null;
|
|
64
76
|
if (res.status === 404) {
|
|
65
77
|
messages.push({
|
|
66
78
|
status: CheckerStatus.Error,
|
|
@@ -80,28 +92,37 @@ export const checkSvgFaviconFile = async (baseUrl: string, url: string, fetcher:
|
|
|
80
92
|
text: `The SVG favicon is accessible at \`${url}\``
|
|
81
93
|
});
|
|
82
94
|
|
|
83
|
-
|
|
95
|
+
content = await readableStreamToString(res.readableStream);
|
|
84
96
|
const meta = await sharp(Buffer.from(content)).metadata();
|
|
97
|
+
width = meta.width || null;
|
|
98
|
+
height = meta.height || null;
|
|
85
99
|
|
|
86
|
-
if (
|
|
100
|
+
if (width && height && width !== height) {
|
|
87
101
|
messages.push({
|
|
88
102
|
status: CheckerStatus.Error,
|
|
89
103
|
id: MessageId.svgFaviconNotSquare,
|
|
90
|
-
text: `The SVG is not square (${
|
|
104
|
+
text: `The SVG is not square (${width}x${height})`
|
|
91
105
|
});
|
|
92
106
|
} else {
|
|
93
107
|
messages.push({
|
|
94
108
|
status: CheckerStatus.Ok,
|
|
95
109
|
id: MessageId.svgFaviconSquare,
|
|
96
|
-
text: `The SVG is square (${
|
|
110
|
+
text: `The SVG is square (${width}x${height})`
|
|
97
111
|
});
|
|
98
112
|
}
|
|
99
113
|
}
|
|
100
114
|
|
|
101
|
-
return
|
|
115
|
+
return {
|
|
116
|
+
messages,
|
|
117
|
+
icon: {
|
|
118
|
+
content: content ? await bufferToDataUrl(Buffer.from(content), 'image/svg+xml') : null,
|
|
119
|
+
url: svgUrl,
|
|
120
|
+
width, height
|
|
121
|
+
}
|
|
122
|
+
};
|
|
102
123
|
}
|
|
103
124
|
|
|
104
|
-
export const checkPngFavicon = async (baseUrl: string, head: HTMLElement | null, fetcher: Fetcher = fetchFetcher): Promise<
|
|
125
|
+
export const checkPngFavicon = async (baseUrl: string, head: HTMLElement | null, fetcher: Fetcher = fetchFetcher): Promise<DesktopSingleReport> => {
|
|
105
126
|
const messages: CheckerMessage[] = [];
|
|
106
127
|
|
|
107
128
|
if (!head) {
|
|
@@ -111,7 +132,7 @@ export const checkPngFavicon = async (baseUrl: string, head: HTMLElement | null,
|
|
|
111
132
|
text: 'No <head> element'
|
|
112
133
|
});
|
|
113
134
|
|
|
114
|
-
return { messages, icon: null };
|
|
135
|
+
return { messages, icon: { content: null, url: null, width: null, height: null } };
|
|
115
136
|
}
|
|
116
137
|
|
|
117
138
|
const icons = head?.querySelectorAll("link[rel='icon'][type='image/png']");
|
|
@@ -193,15 +214,21 @@ export const checkPngFavicon = async (baseUrl: string, head: HTMLElement | null,
|
|
|
193
214
|
}
|
|
194
215
|
}
|
|
195
216
|
|
|
196
|
-
return { messages, icon: null };
|
|
217
|
+
return { messages, icon: { content: null, url: null, width: null, height: null } };
|
|
197
218
|
}
|
|
198
219
|
|
|
199
220
|
export const checkDesktopFavicon = async (baseUrl: string, head: HTMLElement | null, fetcher: Fetcher = fetchFetcher): Promise<DesktopFaviconReport> => {
|
|
200
|
-
const
|
|
221
|
+
const svgReport = await checkSvgFavicon(baseUrl, head, fetcher);
|
|
201
222
|
const pngReport = await checkPngFavicon(baseUrl, head, fetcher);
|
|
202
223
|
const icoReport = await checkIcoFavicon(baseUrl, head, fetcher);
|
|
224
|
+
|
|
203
225
|
return {
|
|
204
|
-
messages: [ ...
|
|
205
|
-
icon: pngReport.icon
|
|
226
|
+
messages: [ ...svgReport.messages, ...pngReport.messages, ...icoReport.messages ],
|
|
227
|
+
icon: pngReport.icon ? pngReport.icon.content : null,
|
|
228
|
+
icons: {
|
|
229
|
+
png: pngReport.icon,
|
|
230
|
+
ico: icoReport.icon,
|
|
231
|
+
svg: svgReport.icon
|
|
232
|
+
}
|
|
206
233
|
};
|
|
207
234
|
}
|