@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/src/desktop/ico.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { CheckerMessage, CheckerStatus, Fetcher, MessageId } from "../types";
|
|
1
|
+
import { CheckedIcon, CheckerMessage, CheckerStatus, DesktopSingleReport, Fetcher, MessageId } from "../types";
|
|
2
2
|
import { HTMLElement } from 'node-html-parser'
|
|
3
|
-
import { mergeUrlAndPath, readableStreamToBuffer } from "../helper";
|
|
3
|
+
import { bufferToDataUrl, mergeUrlAndPath, readableStreamToBuffer } from "../helper";
|
|
4
4
|
import decodeIco from "decode-ico";
|
|
5
5
|
|
|
6
6
|
export const IcoFaviconSizes = [ 48, 32, 16 ];
|
|
7
7
|
|
|
8
|
-
export const checkIcoFavicon = async (url: string, head: HTMLElement | null, fetcher: Fetcher): Promise<
|
|
8
|
+
export const checkIcoFavicon = async (url: string, head: HTMLElement | null, fetcher: Fetcher): Promise<DesktopSingleReport> => {
|
|
9
9
|
const messages: CheckerMessage[] = [];
|
|
10
10
|
|
|
11
11
|
if (!head) {
|
|
@@ -15,13 +15,19 @@ export const checkIcoFavicon = async (url: string, head: HTMLElement | null, fet
|
|
|
15
15
|
text: 'No <head> element'
|
|
16
16
|
});
|
|
17
17
|
|
|
18
|
-
return
|
|
18
|
+
return {
|
|
19
|
+
messages,
|
|
20
|
+
icon : { content: null, url: null, width: null, height: null }
|
|
21
|
+
};
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
const icos =
|
|
22
25
|
head.querySelectorAll('link[rel="shortcut icon"]') ||
|
|
23
26
|
head.querySelectorAll('link[rel="icon"][type="image/x-icon"]');
|
|
24
27
|
|
|
28
|
+
let iconUrl: string | null = null;
|
|
29
|
+
let images;
|
|
30
|
+
|
|
25
31
|
if (icos.length === 0) {
|
|
26
32
|
messages.push({
|
|
27
33
|
status: CheckerStatus.Error,
|
|
@@ -49,7 +55,7 @@ export const checkIcoFavicon = async (url: string, head: HTMLElement | null, fet
|
|
|
49
55
|
text: 'The ICO markup has no href attribute'
|
|
50
56
|
});
|
|
51
57
|
} else {
|
|
52
|
-
|
|
58
|
+
iconUrl = mergeUrlAndPath(url, href);
|
|
53
59
|
const iconResponse = await fetcher(iconUrl, 'image/x-icon');
|
|
54
60
|
if (iconResponse.status === 404) {
|
|
55
61
|
messages.push({
|
|
@@ -71,7 +77,7 @@ export const checkIcoFavicon = async (url: string, head: HTMLElement | null, fet
|
|
|
71
77
|
});
|
|
72
78
|
|
|
73
79
|
const iconBuffer = await readableStreamToBuffer(iconResponse.readableStream);
|
|
74
|
-
|
|
80
|
+
images = await decodeIco(iconBuffer);
|
|
75
81
|
|
|
76
82
|
const imageSizes = images.map(image => `${image.width}x${image.height}`);
|
|
77
83
|
|
|
@@ -106,5 +112,23 @@ export const checkIcoFavicon = async (url: string, head: HTMLElement | null, fet
|
|
|
106
112
|
}
|
|
107
113
|
}
|
|
108
114
|
|
|
109
|
-
|
|
115
|
+
let content: string | null = null;
|
|
116
|
+
const theIcon: CheckedIcon = {
|
|
117
|
+
content: null,
|
|
118
|
+
url: iconUrl,
|
|
119
|
+
width: null,
|
|
120
|
+
height: null
|
|
121
|
+
};
|
|
122
|
+
if (images) {
|
|
123
|
+
const image = images[0];
|
|
124
|
+
const mimeType = (image.type === "bmp") ? "image/bmp" : "image/png";
|
|
125
|
+
theIcon.content = await bufferToDataUrl(Buffer.from(image.data), mimeType);
|
|
126
|
+
theIcon.width = image.width;
|
|
127
|
+
theIcon.height = image.height;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
messages,
|
|
132
|
+
icon: theIcon,
|
|
133
|
+
};
|
|
110
134
|
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { checkRobotsFile, getRobotsFileUrl } from "./google";
|
|
2
|
+
import { stringToReadableStream } from "./helper";
|
|
3
|
+
import { testFetcher } from "./test-helper";
|
|
4
|
+
import { CheckerMessage, CheckerStatus, DesktopFaviconReport, MessageId } from "./types";
|
|
5
|
+
|
|
6
|
+
test('getRobotsFileUrl', () => {
|
|
7
|
+
expect(getRobotsFileUrl('https://example.com')).toEqual('https://example.com/robots.txt');
|
|
8
|
+
expect(getRobotsFileUrl('https://example.com/some-path')).toEqual('https://example.com/robots.txt');
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const runRobotsTest = async (urls: string[], robotsFile: string | null, messages: Pick<CheckerMessage, 'id' | 'status'>[]) => {
|
|
12
|
+
const report = await checkRobotsFile(
|
|
13
|
+
'https://example.com',
|
|
14
|
+
urls,
|
|
15
|
+
testFetcher(robotsFile ? {
|
|
16
|
+
'https://example.com/robots.txt': {
|
|
17
|
+
status: 200,
|
|
18
|
+
contentType: 'text/plain',
|
|
19
|
+
readableStream: await stringToReadableStream(robotsFile)
|
|
20
|
+
}
|
|
21
|
+
} : {})
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const filteredMessages = report.map(m => ({ status: m.status, id: m.id }));
|
|
25
|
+
expect(filteredMessages).toEqual(messages);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
test('checkRobotsFile - No robots file', async () => {
|
|
29
|
+
await runRobotsTest(
|
|
30
|
+
[ 'https://example.com/favicon.png' ],
|
|
31
|
+
null,
|
|
32
|
+
[
|
|
33
|
+
{
|
|
34
|
+
status: CheckerStatus.Ok,
|
|
35
|
+
id: MessageId.googleNoRobotsFile
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('checkRobotsFile - PNG favicon is accessible', async () => {
|
|
42
|
+
await runRobotsTest(
|
|
43
|
+
[ 'https://example.com/favicon.png' ],
|
|
44
|
+
`
|
|
45
|
+
User-agent: *
|
|
46
|
+
Allow: /`,
|
|
47
|
+
[
|
|
48
|
+
{
|
|
49
|
+
status: CheckerStatus.Ok,
|
|
50
|
+
id: MessageId.googleRobotsFileFound
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
status: CheckerStatus.Ok,
|
|
54
|
+
id: MessageId.googlePngIconAllowedByRobots
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('checkRobotsFile - PNG favicon is *not* accessible', async () => {
|
|
61
|
+
await runRobotsTest(
|
|
62
|
+
[ 'https://example.com/favicon.png' ],
|
|
63
|
+
`
|
|
64
|
+
# *
|
|
65
|
+
User-agent: *
|
|
66
|
+
Allow: /
|
|
67
|
+
|
|
68
|
+
User-agent: Googlebot-Image
|
|
69
|
+
Disallow: /*.png
|
|
70
|
+
`,
|
|
71
|
+
[
|
|
72
|
+
{
|
|
73
|
+
status: CheckerStatus.Ok,
|
|
74
|
+
id: MessageId.googleRobotsFileFound
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
status: CheckerStatus.Error,
|
|
78
|
+
id: MessageId.googlePngIconBlockedByRobots
|
|
79
|
+
}
|
|
80
|
+
]
|
|
81
|
+
);
|
|
82
|
+
});
|
package/src/google.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import robotsParser from "robots-parser";
|
|
2
|
+
import { checkDesktopFavicon } from "./desktop/desktop";
|
|
3
|
+
import { fetchFetcher, readableStreamToBuffer, readableStreamToString } from "./helper";
|
|
4
|
+
import { CheckedIcon, CheckerMessage, CheckerStatus, DesktopFaviconReport, Fetcher, GoogleReport, MessageId } from "./types";
|
|
5
|
+
import { HTMLElement } from "node-html-parser";
|
|
6
|
+
|
|
7
|
+
export const GoogleBot = 'Googlebot';
|
|
8
|
+
export const GoogleImageBot = 'Googlebot-Image';
|
|
9
|
+
|
|
10
|
+
export const getRobotsFileUrl = (baseUrl: string): string => {
|
|
11
|
+
try {
|
|
12
|
+
const url = new URL(baseUrl);
|
|
13
|
+
url.pathname = '/robots.txt';
|
|
14
|
+
return url.toString();
|
|
15
|
+
} catch (error) {
|
|
16
|
+
throw new Error(`Invalid URL ${baseUrl}`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const checkRobotsFile = async (baseUrl: string, iconUrls: string[], fetcher: Fetcher = fetchFetcher): Promise<CheckerMessage[]> => {
|
|
21
|
+
const robotsUrl = getRobotsFileUrl(baseUrl);
|
|
22
|
+
const robotsResponse = await fetcher(robotsUrl);
|
|
23
|
+
|
|
24
|
+
const messages: CheckerMessage[] = [];
|
|
25
|
+
|
|
26
|
+
if (robotsResponse.status === 200) {
|
|
27
|
+
messages.push({
|
|
28
|
+
status: CheckerStatus.Ok,
|
|
29
|
+
text: `robots.txt file found at ${robotsUrl}`,
|
|
30
|
+
id: MessageId.googleRobotsFileFound
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const robotsFile = robotsResponse.readableStream ? await readableStreamToString(robotsResponse.readableStream) : '';
|
|
34
|
+
|
|
35
|
+
const robots = robotsParser(robotsUrl, robotsFile);
|
|
36
|
+
|
|
37
|
+
iconUrls.forEach(url => {
|
|
38
|
+
if (url) {
|
|
39
|
+
if (robots.isAllowed(url, GoogleImageBot)) {
|
|
40
|
+
messages.push({
|
|
41
|
+
status: CheckerStatus.Ok,
|
|
42
|
+
text: `Access to \`${url}\` is allowed for \`${GoogleImageBot}\``,
|
|
43
|
+
id: MessageId.googlePngIconAllowedByRobots
|
|
44
|
+
});
|
|
45
|
+
} else {
|
|
46
|
+
const line = robots.getMatchingLineNumber(url, GoogleImageBot);
|
|
47
|
+
messages.push({
|
|
48
|
+
status: CheckerStatus.Error,
|
|
49
|
+
text: `Access to \`${url}\` is blocked for \`${GoogleImageBot}\` (\`${robotsUrl}\`, line ${line})`,
|
|
50
|
+
id: MessageId.googlePngIconBlockedByRobots
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
} else {
|
|
56
|
+
messages.push({
|
|
57
|
+
status: CheckerStatus.Ok,
|
|
58
|
+
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.`,
|
|
59
|
+
id: MessageId.googleNoRobotsFile
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return messages;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const checkGoogleFaviconFromDesktopReport = async (baseUrl: string, desktopReport: DesktopFaviconReport, fetcher: Fetcher = fetchFetcher): Promise<GoogleReport> => {
|
|
67
|
+
const allIcons: CheckedIcon[] = [
|
|
68
|
+
desktopReport.icons.png,
|
|
69
|
+
desktopReport.icons.ico,
|
|
70
|
+
desktopReport.icons.svg
|
|
71
|
+
].filter((i): i is CheckedIcon => !!i);
|
|
72
|
+
|
|
73
|
+
const allIconUrls: string[] = allIcons.map(i => i.url).filter((i): i is string => !!i);
|
|
74
|
+
|
|
75
|
+
const robotsMessages = await checkRobotsFile(baseUrl, allIconUrls, fetcher);
|
|
76
|
+
|
|
77
|
+
const messages: CheckerMessage[] = [ ...desktopReport.messages, ...robotsMessages ];
|
|
78
|
+
|
|
79
|
+
let finalIcon: string | null = null;
|
|
80
|
+
let icons: CheckedIcon[] = [];
|
|
81
|
+
let maxWidth = 0;
|
|
82
|
+
|
|
83
|
+
allIcons.forEach(icon => {
|
|
84
|
+
if (icon.content && icon.width && icon.height && icon.url) {
|
|
85
|
+
icons.push(icon);
|
|
86
|
+
if (icon.width > maxWidth) {
|
|
87
|
+
finalIcon = icon.content;
|
|
88
|
+
maxWidth = icon.width;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
messages,
|
|
95
|
+
icon: finalIcon,
|
|
96
|
+
icons
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export const checkGoogleFavicon = async (baseUrl: string, head: HTMLElement | null, fetcher: Fetcher = fetchFetcher): Promise<GoogleReport> => {
|
|
101
|
+
const desktopReport = await checkDesktopFavicon(baseUrl, head, fetcher);
|
|
102
|
+
return checkGoogleFaviconFromDesktopReport(baseUrl, desktopReport, fetcher);
|
|
103
|
+
}
|
package/src/helper.test.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import sharp from "sharp";
|
|
2
|
-
import { CheckIconProcessor, checkIcon, filePathToReadableStream, mergeUrlAndPath, parseSizesAttribute } from "./helper";
|
|
2
|
+
import { CheckIconProcessor, bufferToDataUrl, checkIcon, filePathToDataUrl, filePathToReadableStream, filePathToString, mergeUrlAndPath, parseSizesAttribute } from "./helper";
|
|
3
3
|
import { testFetcher } from "./test-helper";
|
|
4
4
|
|
|
5
5
|
const getTestProcessor = () => {
|
|
@@ -30,7 +30,12 @@ test('checkIcon - noHref', async () => {
|
|
|
30
30
|
|
|
31
31
|
test('checkIcon - icon404', async () => {
|
|
32
32
|
const processor = getTestProcessor();
|
|
33
|
-
expect(await checkIcon('/does-not-exist.png', processor.processor, testFetcher({}), 'image/png')).
|
|
33
|
+
expect(await checkIcon('/does-not-exist.png', processor.processor, testFetcher({}), 'image/png')).toEqual({
|
|
34
|
+
content: null,
|
|
35
|
+
url: '/does-not-exist.png',
|
|
36
|
+
width: null,
|
|
37
|
+
height: null
|
|
38
|
+
});
|
|
34
39
|
expect(processor.messages).toEqual(['icon404']);
|
|
35
40
|
})
|
|
36
41
|
|
|
@@ -41,7 +46,12 @@ test('checkIcon - icon404', async () => {
|
|
|
41
46
|
contentType: 'image/png',
|
|
42
47
|
status: 500
|
|
43
48
|
}
|
|
44
|
-
}), 'image/png')).
|
|
49
|
+
}), 'image/png')).toEqual({
|
|
50
|
+
content: null,
|
|
51
|
+
url: '/bad-icon.png',
|
|
52
|
+
width: null,
|
|
53
|
+
height: null
|
|
54
|
+
});
|
|
45
55
|
expect(processor.messages).toEqual(['cannotGet 500']);
|
|
46
56
|
})
|
|
47
57
|
|
|
@@ -100,7 +110,12 @@ test('checkIcon - downloadable & notSquare', async () => {
|
|
|
100
110
|
contentType: 'image/png',
|
|
101
111
|
readableStream: await filePathToReadableStream(nonSquareIcon)
|
|
102
112
|
}
|
|
103
|
-
}), 'image/png', 500)).
|
|
113
|
+
}), 'image/png', 500)).toEqual({
|
|
114
|
+
content: await filePathToDataUrl(nonSquareIcon),
|
|
115
|
+
url: '/non-square-icon.png',
|
|
116
|
+
width: 240,
|
|
117
|
+
height: 180
|
|
118
|
+
});
|
|
104
119
|
expect(processor.messages).toEqual([
|
|
105
120
|
'downloadable',
|
|
106
121
|
'notSquare 240x180'
|
|
@@ -117,6 +132,11 @@ test('mergeUrlAndPath', () => {
|
|
|
117
132
|
expect(mergeUrlAndPath('https://example.com/sub-page', 'some/path')).toBe('https://example.com/sub-page/some/path');
|
|
118
133
|
|
|
119
134
|
expect(mergeUrlAndPath('https://example.com', 'https://elsewhere.com/some-path')).toBe('https://elsewhere.com/some-path');
|
|
135
|
+
|
|
136
|
+
// Protocol-relative URL
|
|
137
|
+
// For https://github.com/RealFaviconGenerator/core/issues/2
|
|
138
|
+
expect(mergeUrlAndPath('https://example.com', '//elsewhere.com/some-path')).toBe('https://elsewhere.com/some-path');
|
|
139
|
+
expect(mergeUrlAndPath('http://example.com', '//elsewhere.com/some-other/path')).toBe('http://elsewhere.com/some-other/path');
|
|
120
140
|
})
|
|
121
141
|
|
|
122
142
|
test('parseSizesAttribute', () => {
|
package/src/helper.ts
CHANGED
|
@@ -24,9 +24,12 @@ export const filePathToString = async (path: string): Promise<string> => (
|
|
|
24
24
|
)
|
|
25
25
|
|
|
26
26
|
export const stringToReadableStream = (str: string): ReadableStream => {
|
|
27
|
+
const encoder = new TextEncoder();
|
|
28
|
+
const uint8Array = encoder.encode(str);
|
|
29
|
+
|
|
27
30
|
return new ReadableStream({
|
|
28
31
|
start(controller) {
|
|
29
|
-
controller.enqueue(
|
|
32
|
+
controller.enqueue(uint8Array);
|
|
30
33
|
controller.close();
|
|
31
34
|
}
|
|
32
35
|
});
|
|
@@ -100,13 +103,20 @@ export const pathToMimeType = (path: string): string => {
|
|
|
100
103
|
}
|
|
101
104
|
}
|
|
102
105
|
|
|
106
|
+
export type CheckIconOutput = {
|
|
107
|
+
content: string | null,
|
|
108
|
+
url: string | null,
|
|
109
|
+
width: number | null,
|
|
110
|
+
height: number | null,
|
|
111
|
+
}
|
|
112
|
+
|
|
103
113
|
export const checkIcon = async (
|
|
104
114
|
iconUrl: string | undefined,
|
|
105
115
|
processor: CheckIconProcessor,
|
|
106
116
|
fetcher: Fetcher,
|
|
107
117
|
mimeType: string | undefined,
|
|
108
118
|
expectedWidthHeight?: number
|
|
109
|
-
): Promise<
|
|
119
|
+
): Promise<CheckIconOutput | null> => {
|
|
110
120
|
if (!iconUrl) {
|
|
111
121
|
processor.noHref();
|
|
112
122
|
return null;
|
|
@@ -120,15 +130,16 @@ export const checkIcon = async (
|
|
|
120
130
|
} else if (res.readableStream) {
|
|
121
131
|
processor.downloadable();
|
|
122
132
|
|
|
123
|
-
const
|
|
124
|
-
const meta = await sharp(
|
|
133
|
+
const rawContent = await readableStreamToBuffer(res.readableStream);
|
|
134
|
+
const meta = await sharp(rawContent).metadata();
|
|
125
135
|
|
|
126
136
|
const contentType = res.contentType || pathToMimeType(iconUrl);
|
|
127
137
|
|
|
138
|
+
const content = await bufferToDataUrl(rawContent, contentType);
|
|
139
|
+
|
|
128
140
|
if (meta.width && meta.height) {
|
|
129
141
|
if (meta.width !== meta.height) {
|
|
130
142
|
processor.notSquare(meta.width, meta.height);
|
|
131
|
-
return null;
|
|
132
143
|
} else {
|
|
133
144
|
processor.square(meta.width);
|
|
134
145
|
|
|
@@ -142,10 +153,20 @@ export const checkIcon = async (
|
|
|
142
153
|
}
|
|
143
154
|
}
|
|
144
155
|
|
|
145
|
-
return
|
|
156
|
+
return {
|
|
157
|
+
content,
|
|
158
|
+
url: iconUrl,
|
|
159
|
+
width: meta.width || null,
|
|
160
|
+
height: meta.height || null
|
|
161
|
+
}
|
|
146
162
|
}
|
|
147
163
|
|
|
148
|
-
return
|
|
164
|
+
return {
|
|
165
|
+
content: null,
|
|
166
|
+
url: iconUrl,
|
|
167
|
+
width: null,
|
|
168
|
+
height: null
|
|
169
|
+
};
|
|
149
170
|
}
|
|
150
171
|
|
|
151
172
|
export const mergeUrlAndPath = (baseUrl: string, absoluteOrRelativePath: string): string => {
|
|
@@ -156,8 +177,11 @@ export const mergeUrlAndPath = (baseUrl: string, absoluteOrRelativePath: string)
|
|
|
156
177
|
|
|
157
178
|
const url = new URL(baseUrl);
|
|
158
179
|
|
|
159
|
-
//
|
|
160
|
-
if (absoluteOrRelativePath.startsWith('
|
|
180
|
+
// Protocol-relative URL
|
|
181
|
+
if (absoluteOrRelativePath.startsWith('//')) {
|
|
182
|
+
return `${url.protocol}${absoluteOrRelativePath}`;
|
|
183
|
+
} else if (absoluteOrRelativePath.startsWith('/')) {
|
|
184
|
+
// If the path starts with a slash, replace the pathname
|
|
161
185
|
return `${url.origin}${absoluteOrRelativePath}`;
|
|
162
186
|
} else {
|
|
163
187
|
// Otherwise, append the path to the existing pathname
|
|
@@ -186,6 +210,13 @@ export const bufferToDataUrl = (buffer: Buffer, mimeType: string): string => {
|
|
|
186
210
|
return `data:${mimeType};base64,${buffer.toString('base64')}`;
|
|
187
211
|
}
|
|
188
212
|
|
|
213
|
+
export const filePathToDataUrl = async (filePath: string): Promise<string> => {
|
|
214
|
+
const readStream = await filePathToReadableStream(filePath);
|
|
215
|
+
const rawContent = await readableStreamToBuffer(readStream);
|
|
216
|
+
const contentType = pathToMimeType(filePath);
|
|
217
|
+
return bufferToDataUrl(rawContent, contentType);
|
|
218
|
+
}
|
|
219
|
+
|
|
189
220
|
export const fetchFetcher: Fetcher = async (url, contentType) => {
|
|
190
221
|
const res = await fetch(url, {
|
|
191
222
|
headers: {
|
package/src/touch-icon.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CheckerMessage, CheckerStatus, Fetcher, MessageId, TouchIconIconReport, TouchIconReport, TouchIconTitleReport } from "./types";
|
|
2
2
|
import { HTMLElement } from 'node-html-parser'
|
|
3
|
-
import { CheckIconProcessor, checkIcon, fetchFetcher, mergeUrlAndPath } from "./helper";
|
|
3
|
+
import { CheckIconOutput, CheckIconProcessor, checkIcon, fetchFetcher, mergeUrlAndPath } from "./helper";
|
|
4
4
|
|
|
5
5
|
export const checkTouchIconTitle = async (baseUrl: string, head: HTMLElement | null, fetcher: Fetcher = fetchFetcher): Promise<TouchIconTitleReport> => {
|
|
6
6
|
const messages: CheckerMessage[] = [];
|
|
@@ -59,7 +59,7 @@ export const checkTouchIconTitle = async (baseUrl: string, head: HTMLElement | n
|
|
|
59
59
|
|
|
60
60
|
export const checkTouchIconIcon = async (baseUrl: string, head: HTMLElement | null, fetcher: Fetcher = fetchFetcher): Promise<TouchIconIconReport> => {
|
|
61
61
|
const messages: CheckerMessage[] = [];
|
|
62
|
-
let touchIcon:
|
|
62
|
+
let touchIcon: CheckIconOutput | null = null;
|
|
63
63
|
|
|
64
64
|
if (!head) {
|
|
65
65
|
messages.push({
|
|
@@ -176,7 +176,7 @@ export const checkTouchIconIcon = async (baseUrl: string, head: HTMLElement | nu
|
|
|
176
176
|
undefined
|
|
177
177
|
);
|
|
178
178
|
|
|
179
|
-
return { messages, touchIcon };
|
|
179
|
+
return { messages, touchIcon: touchIcon ? touchIcon.content : null };
|
|
180
180
|
}
|
|
181
181
|
|
|
182
182
|
export const getDuplicatedSizes = (sizes: (string | undefined)[]): (string | undefined)[] => {
|
package/src/types.ts
CHANGED
|
@@ -79,7 +79,16 @@ export enum MessageId {
|
|
|
79
79
|
manifestIconNotSquare,
|
|
80
80
|
manifestIconRightSize,
|
|
81
81
|
manifestIconSquare,
|
|
82
|
-
manifestIconWrongSize
|
|
82
|
+
manifestIconWrongSize,
|
|
83
|
+
|
|
84
|
+
googleNoRobotsFile,
|
|
85
|
+
googleRobotsFileFound,
|
|
86
|
+
googleIcoBlockedByRobots,
|
|
87
|
+
googleIcoAllowedByRobots,
|
|
88
|
+
googleSvgIconBlockedByRobots,
|
|
89
|
+
googleSvgIconAllowedByRobots,
|
|
90
|
+
googlePngIconBlockedByRobots,
|
|
91
|
+
googlePngIconAllowedByRobots,
|
|
83
92
|
}
|
|
84
93
|
|
|
85
94
|
export type CheckerMessage = {
|
|
@@ -96,9 +105,26 @@ export type FetchResponse = {
|
|
|
96
105
|
|
|
97
106
|
export type Fetcher = (url: string, contentType?: string) => Promise<FetchResponse>;
|
|
98
107
|
|
|
108
|
+
export type CheckedIcon = {
|
|
109
|
+
content: string | null,
|
|
110
|
+
url: string | null,
|
|
111
|
+
width: number | null,
|
|
112
|
+
height: number | null
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export type DesktopSingleReport = {
|
|
116
|
+
messages: CheckerMessage[],
|
|
117
|
+
icon: CheckedIcon | null,
|
|
118
|
+
}
|
|
119
|
+
|
|
99
120
|
export type DesktopFaviconReport = {
|
|
100
121
|
messages: CheckerMessage[],
|
|
101
122
|
icon: string | null,
|
|
123
|
+
icons: {
|
|
124
|
+
png: CheckedIcon | null,
|
|
125
|
+
ico: CheckedIcon | null,
|
|
126
|
+
svg: CheckedIcon | null,
|
|
127
|
+
}
|
|
102
128
|
}
|
|
103
129
|
|
|
104
130
|
export type TouchIconTitleReport = {
|
|
@@ -127,3 +153,9 @@ export type FaviconReport = {
|
|
|
127
153
|
}
|
|
128
154
|
|
|
129
155
|
export type TouchIconReport = TouchIconIconReport & TouchIconTitleReport;
|
|
156
|
+
|
|
157
|
+
export type GoogleReport = {
|
|
158
|
+
messages: CheckerMessage[],
|
|
159
|
+
icon: string | null,
|
|
160
|
+
icons: CheckedIcon[]
|
|
161
|
+
}
|
package/src/web-app-manifest.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { HTMLElement } from "node-html-parser";
|
|
2
2
|
import { CheckerMessage, CheckerStatus, Fetcher, MessageId, WebAppManifestReport } from "./types";
|
|
3
|
-
import { CheckIconProcessor, checkIcon, fetchFetcher, mergeUrlAndPath, pathToMimeType } from "./helper";
|
|
3
|
+
import { CheckIconOutput, CheckIconProcessor, checkIcon, fetchFetcher, mergeUrlAndPath, pathToMimeType } from "./helper";
|
|
4
4
|
|
|
5
5
|
export const checkWebAppManifest = async (baseUrl: string, head: HTMLElement | null, fetcher: Fetcher = fetchFetcher): Promise<WebAppManifestReport> => {
|
|
6
6
|
const messages: CheckerMessage[] = [];
|
|
@@ -101,7 +101,7 @@ const readableStreamToJson = async (stream: ReadableStream): Promise<any> => {
|
|
|
101
101
|
|
|
102
102
|
export const checkWebAppManifestFile = async (manifest: any, baseUrl: string, fetcher: Fetcher): Promise<WebAppManifestReport> => {
|
|
103
103
|
const messages: CheckerMessage[] = [];
|
|
104
|
-
let icon = null;
|
|
104
|
+
let icon: CheckIconOutput | null = null;
|
|
105
105
|
|
|
106
106
|
const name = manifest.name || undefined;
|
|
107
107
|
if (!name) {
|
|
@@ -257,5 +257,5 @@ export const checkWebAppManifestFile = async (manifest: any, baseUrl: string, fe
|
|
|
257
257
|
}
|
|
258
258
|
}
|
|
259
259
|
|
|
260
|
-
return { messages, name, shortName, backgroundColor, themeColor, icon };
|
|
260
|
+
return { messages, name, shortName, backgroundColor, themeColor, icon: icon ? icon.content : null };
|
|
261
261
|
}
|
package/dist/web-manifest.d.ts
DELETED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import { HTMLElement } from "node-html-parser";
|
|
2
|
-
import { Fetcher, WebManifestReport } from "./types";
|
|
3
|
-
export declare const checkWebAppManifest: (baseUrl: string, head: HTMLElement | null, fetcher?: Fetcher) => Promise<WebManifestReport>;
|
|
4
|
-
export declare const checkWebManifestFile: (manifest: any, baseUrl: string, fetcher: Fetcher) => Promise<WebManifestReport>;
|