@realfavicongenerator/check-favicon 0.4.18 → 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.
@@ -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 === 0) {
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
- const iconResponse = await fetcher(iconUrl, 'image/x-icon');
61
- if (iconResponse.status === 404) {
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 if (iconResponse.status >= 300 || !iconResponse.readableStream) {
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.icoFaviconDownloadable,
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
- const iconBuffer = await readableStreamToBuffer(iconResponse.readableStream);
81
- images = await decodeIco(iconBuffer);
82
-
83
- const imageSizes = images.map(image => `${image.width}x${image.height}`);
84
-
85
- const expectedSizes = IcoFaviconSizes.map(size => `${size}x${size}`);
86
-
87
- const extraSizes = imageSizes.filter(size => !expectedSizes.includes(size));
88
- if (extraSizes.length > 0) {
89
- messages.push({
90
- status: CheckerStatus.Warning,
91
- id: MessageId.icoFaviconExtraSizes,
92
- text: `Extra sizes found in ICO favicon: ${extraSizes.join(', ')}`
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 = await bufferToDataUrl(Buffer.from(image.data), mimeType);
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
  }
File without changes
package/src/google.ts CHANGED
File without changes
File without changes
package/src/helper.ts CHANGED
File without changes
package/src/index.ts CHANGED
File without changes
File without changes
@@ -1,5 +1,5 @@
1
1
  import { parse } from 'node-html-parser'
2
- import { CheckerMessage, CheckerStatus, FetchResponse, MessageId } from "./types";
2
+ import { CheckedIcon, CheckerMessage, CheckerStatus, FetchResponse, MessageId } from "./types";
3
3
  import { checkTouchIcon, checkTouchIconIcon, checkTouchIconTitle, getDuplicatedSizes } from "./touch-icon";
4
4
  import { testFetcher } from './test-helper';
5
5
  import { bufferToDataUrl, filePathToReadableStream, readableStreamToBuffer } from './helper';
@@ -7,7 +7,7 @@ import { bufferToDataUrl, filePathToReadableStream, readableStreamToBuffer } fro
7
7
  type TestOutput = {
8
8
  messages: Pick<CheckerMessage, 'id' | 'status'>[],
9
9
  appTitle?: string,
10
- touchIcon?: string | null
10
+ icon?: CheckedIcon | null,
11
11
  }
12
12
 
13
13
  const runCheckTouchIconTitleTest = async (
@@ -67,10 +67,10 @@ const runCheckTouchIconTest = async (
67
67
  const filteredMessages = result.messages.map(m => ({ status: m.status, id: m.id }));
68
68
  expect({
69
69
  messages: filteredMessages,
70
- touchIcon: result.touchIcon
70
+ icon: result.icon
71
71
  }).toEqual({
72
72
  ...output,
73
- touchIcon: output.touchIcon || null
73
+ icon: output.icon || null
74
74
  });
75
75
  }
76
76
 
@@ -98,7 +98,13 @@ test('checkTouchIcon - multipleTouchIcon - no size', async () => {
98
98
  }, {
99
99
  status: CheckerStatus.Error,
100
100
  id: MessageId.duplicatedTouchIconSizes,
101
- }]}, {
101
+ }], icon: {
102
+ content: null,
103
+ url: 'https://example.com/some-icon.png',
104
+ width: null,
105
+ height: null
106
+ },
107
+ }, {
102
108
  'https://example.com/some-icon.png': {
103
109
  status: 200,
104
110
  contentType: 'image/png',
@@ -122,7 +128,14 @@ test('checkTouchIcon - multipleTouchIcon - specific size', async () => {
122
128
  }, {
123
129
  status: CheckerStatus.Error,
124
130
  id: MessageId.duplicatedTouchIconSizes,
125
- }]}, {
131
+ }],
132
+ icon: {
133
+ content: null,
134
+ url: 'https://example.com/some-icon.png',
135
+ width: null,
136
+ height: null
137
+ },
138
+ }, {
126
139
  'https://example.com/some-icon.png': {
127
140
  status: 200,
128
141
  contentType: 'image/png',
@@ -150,7 +163,12 @@ test('checkTouchIcon - Regular case', async () => {
150
163
  },{
151
164
  status: CheckerStatus.Ok,
152
165
  id: MessageId.touchIconSquare
153
- }], touchIcon: bufferToDataUrl(await readableStreamToBuffer(await filePathToReadableStream(testIcon)), 'image/png')
166
+ }], icon: {
167
+ content: bufferToDataUrl(await readableStreamToBuffer(await filePathToReadableStream(testIcon)), 'image/png'),
168
+ url: 'https://example.com/some-other-icon.png',
169
+ width: 180,
170
+ height: 180,
171
+ }
154
172
  }, {
155
173
  'https://example.com/some-other-icon.png': {
156
174
  status: 200,
@@ -161,7 +179,6 @@ test('checkTouchIcon - Regular case', async () => {
161
179
  })
162
180
 
163
181
 
164
-
165
182
  test('getDuplicatedSizes', () => {
166
183
  // No duplicates
167
184
  expect(getDuplicatedSizes([])).toEqual([]);
package/src/touch-icon.ts CHANGED
@@ -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: CheckIconOutput | null = null;
62
+ let icon: CheckIconOutput | null = null;
63
63
 
64
64
  if (!head) {
65
65
  messages.push({
@@ -68,7 +68,7 @@ export const checkTouchIconIcon = async (baseUrl: string, head: HTMLElement | nu
68
68
  text: 'No <head> element'
69
69
  });
70
70
 
71
- return { messages, touchIcon };
71
+ return { messages, icon };
72
72
  }
73
73
 
74
74
  const iconMarkup = head.querySelectorAll("link[rel='apple-touch-icon']");
@@ -79,7 +79,7 @@ export const checkTouchIconIcon = async (baseUrl: string, head: HTMLElement | nu
79
79
  text: 'No touch icon declared'
80
80
  });
81
81
 
82
- return { messages, touchIcon };
82
+ return { messages, icon };
83
83
  }
84
84
 
85
85
  messages.push({
@@ -105,7 +105,7 @@ export const checkTouchIconIcon = async (baseUrl: string, head: HTMLElement | nu
105
105
  text: 'The touch icon has no href'
106
106
  });
107
107
 
108
- return { messages, touchIcon };
108
+ return { messages, icon };
109
109
  }
110
110
 
111
111
  const touchIconUrl = mergeUrlAndPath(baseUrl, iconHref);
@@ -169,14 +169,14 @@ export const checkTouchIconIcon = async (baseUrl: string, head: HTMLElement | nu
169
169
  }
170
170
  }
171
171
 
172
- touchIcon = await checkIcon(
172
+ icon = await checkIcon(
173
173
  touchIconUrl,
174
174
  processor,
175
175
  fetcher,
176
176
  undefined
177
177
  );
178
178
 
179
- return { messages, touchIcon: touchIcon ? touchIcon.content : null };
179
+ return { messages, icon };
180
180
  }
181
181
 
182
182
  export const getDuplicatedSizes = (sizes: (string | undefined)[]): (string | undefined)[] => {
@@ -191,6 +191,6 @@ export const checkTouchIcon = async (baseUrl: string, head: HTMLElement | null,
191
191
  return {
192
192
  messages: [...titleReport.messages, ...iconReport.messages],
193
193
  appTitle: titleReport.appTitle,
194
- touchIcon: iconReport.touchIcon
194
+ icon: iconReport.icon
195
195
  }
196
196
  }
package/src/types.ts CHANGED
@@ -21,6 +21,7 @@ export enum MessageId {
21
21
  noIcoFavicon,
22
22
  multipleIcoFavicons,
23
23
  icoFaviconDeclared,
24
+ icoFaviconImplicitInRoot,
24
25
  noIcoFaviconHref,
25
26
  icoFavicon404,
26
27
  icoFaviconCannotGet,
@@ -134,7 +135,7 @@ export type TouchIconTitleReport = {
134
135
 
135
136
  export type TouchIconIconReport = {
136
137
  messages: CheckerMessage[],
137
- touchIcon: string | null,
138
+ icon: CheckedIcon | null,
138
139
  }
139
140
 
140
141
  export type WebAppManifestReport = {
@@ -143,7 +144,7 @@ export type WebAppManifestReport = {
143
144
  shortName?: string,
144
145
  backgroundColor?: string,
145
146
  themeColor?: string,
146
- icon: string | null
147
+ icon: CheckedIcon | null
147
148
  }
148
149
 
149
150
  export type FaviconReport = {
@@ -191,6 +191,11 @@ test('checkWebAppManifestFile - Everything is fine', async () => {
191
191
  shortName: 'Short!',
192
192
  backgroundColor: '#123456',
193
193
  themeColor: '#abcdef',
194
- icon: bufferToDataUrl(await readableStreamToBuffer(await filePathToReadableStream(testIcon512)), 'image/png')
194
+ icon: {
195
+ content: bufferToDataUrl(await readableStreamToBuffer(await filePathToReadableStream(testIcon512)), 'image/png'),
196
+ url: "https://example.com/icon-512.png",
197
+ width: 512,
198
+ height: 512,
199
+ },
195
200
  });
196
201
  })
@@ -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: icon ? icon.content : null };
260
+ return { messages, name, shortName, backgroundColor, themeColor, icon };
261
261
  }
package/tsconfig.json CHANGED
File without changes