@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/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(str);
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 content = yield (0, exports.readableStreamToBuffer)(res.readableStream);
118
- const meta = yield (0, sharp_1.default)(content).metadata();
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 (0, exports.bufferToDataUrl)(content, contentType);
139
+ return {
140
+ content,
141
+ url: iconUrl,
142
+ width: meta.width || null,
143
+ height: meta.height || null
144
+ };
138
145
  }
139
- return null;
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
- // If the path starts with a slash, replace the pathname
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: {
@@ -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')).toBeNull();
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')).toBeNull();
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)).toBeNull();
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);
@@ -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 = {}));
@@ -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.6",
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": "f62941a779e6ce1b49ad97bfea8a9eb71e7ceba5"
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 = Pick<CheckerMessage, 'id' | 'status'>[];
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
- status: CheckerStatus.Error,
23
- id: MessageId.noHead,
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>SOme text</title>`, [{
29
- status: CheckerStatus.Error,
30
- id: MessageId.noSvgFavicon,
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
- status: CheckerStatus.Error,
40
- id: MessageId.multipleSvgFavicons,
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
- status: CheckerStatus.Ok,
47
- id: MessageId.svgFaviconDeclared,
48
- }, {
49
- status: CheckerStatus.Error,
50
- id: MessageId.noSvgFaviconHref,
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
- status: CheckerStatus.Ok,
57
- id: MessageId.svgFaviconDeclared,
58
- }, {
59
- status: CheckerStatus.Error,
60
- id: MessageId.svgFavicon404,
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
- status: CheckerStatus.Ok,
67
- id: MessageId.svgFaviconDeclared,
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
- status: CheckerStatus.Ok,
87
- id: MessageId.svgFaviconDeclared,
88
- }, {
89
- status: CheckerStatus.Ok,
90
- id: MessageId.svgFaviconDownloadable,
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
- status: CheckerStatus.Ok,
127
- id: MessageId.desktopPngFaviconDeclared,
128
- }, {
129
- status: CheckerStatus.Ok,
130
- id: MessageId.desktopPngFaviconDownloadable,
131
- }, {
132
- status: CheckerStatus.Ok,
133
- id: MessageId.desktopPngFaviconRightSize,
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,
@@ -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<CheckerMessage[]> => {
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 messages;
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 iconMessages = await checkSvgFaviconFile(baseUrl, href, fetcher)
51
- return [ ...messages, ...iconMessages ];
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 messages;
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<CheckerMessage[]> => {
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
- const content = await readableStreamToString(res.readableStream);
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 (meta.width !== meta.height) {
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 (${meta.width}x${meta.height})`
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 (${meta.width}x${meta.height})`
110
+ text: `The SVG is square (${width}x${height})`
97
111
  });
98
112
  }
99
113
  }
100
114
 
101
- return messages;
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<DesktopFaviconReport> => {
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 svgMessages = await checkSvgFavicon(baseUrl, head, fetcher);
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: [ ...svgMessages, ...pngReport.messages, ...icoReport ],
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
  }