webstore-lint 1.0.31 → 1.0.32
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/README.md +4 -0
- package/package.json +2 -2
- package/src/privacy.mjs +12 -12
package/README.md
CHANGED
|
@@ -347,6 +347,10 @@ What it reports:
|
|
|
347
347
|
It checks *reachability*. It does not judge whether your policy is adequate,
|
|
348
348
|
which is the thing a reviewer actually reads it for.
|
|
349
349
|
|
|
350
|
+
The request has a ten-second deadline, including downloading the page body.
|
|
351
|
+
A timeout or interrupted download produces a network warning, with no verdict
|
|
352
|
+
on the unread page's content.
|
|
353
|
+
|
|
350
354
|
## What it cannot do
|
|
351
355
|
|
|
352
356
|
It reads your package. It cannot see your store listing, your screenshots or your
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "webstore-lint",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.32",
|
|
4
4
|
"description": "Check an unpacked Chrome extension against the Chrome Web Store program policies before you submit it. Every finding cites Google's own notification ID and verbatim policy text. Covers the 1 August 2026 policy updates.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"chrome-extension",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"node": ">=18"
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|
|
41
|
-
"test": "node test/lint.test.mjs && node test/action.test.mjs && node --test test/permissions-output.test.mjs test/cli-modes.test.mjs"
|
|
41
|
+
"test": "node test/lint.test.mjs && node test/action.test.mjs && node --test test/permissions-output.test.mjs test/cli-modes.test.mjs test/privacy-timeout.test.mjs"
|
|
42
42
|
},
|
|
43
43
|
"repository": {
|
|
44
44
|
"type": "git",
|
package/src/privacy.mjs
CHANGED
|
@@ -118,6 +118,7 @@ export async function checkPolicyUrl(url, { timeoutMs = 10000, fetchImpl = fetch
|
|
|
118
118
|
const ctl = new AbortController();
|
|
119
119
|
const timer = setTimeout(() => ctl.abort(), timeoutMs);
|
|
120
120
|
let res;
|
|
121
|
+
let body;
|
|
121
122
|
try {
|
|
122
123
|
res = await fetchImpl(u.href, {
|
|
123
124
|
signal: ctl.signal,
|
|
@@ -129,24 +130,30 @@ export async function checkPolicyUrl(url, { timeoutMs = 10000, fetchImpl = fetch
|
|
|
129
130
|
"user-agent": "webstore-lint (privacy policy reachability check)",
|
|
130
131
|
},
|
|
131
132
|
});
|
|
133
|
+
// fetch resolves at headers. Keep the deadline active until the body is
|
|
134
|
+
// complete too, or a stalled page can hang the CLI and Action indefinitely.
|
|
135
|
+
if (res.ok) body = await res.text();
|
|
136
|
+
else ctl.abort(); // The status is enough evidence; do not download its body.
|
|
132
137
|
} catch (e) {
|
|
133
|
-
clearTimeout(timer);
|
|
134
138
|
// COULD NOT LOOK IS NOT THE SAME FACT AS NOT THERE. A DNS failure on the
|
|
135
139
|
// developer's own machine, an offline CI runner or a proxy is not evidence
|
|
136
140
|
// about the URL, so this warns and says which it is rather than failing.
|
|
137
|
-
const aborted = e.name === "AbortError";
|
|
141
|
+
const aborted = ctl.signal.aborted || e.name === "AbortError";
|
|
138
142
|
return [...findings, f(
|
|
139
143
|
"warn",
|
|
140
144
|
aborted
|
|
141
|
-
? `The privacy policy URL did not
|
|
142
|
-
:
|
|
145
|
+
? `The privacy policy URL did not finish loading within ${timeoutMs / 1000}s`
|
|
146
|
+
: res
|
|
147
|
+
? `The privacy policy response could not be read from here: ${e.message}`
|
|
148
|
+
: `The privacy policy URL could not be reached from here: ${e.message}`,
|
|
143
149
|
"This is not the same as the address being broken. It may be your network, a proxy, or an offline " +
|
|
144
150
|
"runner. Open it yourself in a private browser window before you trust either answer. " +
|
|
145
151
|
"A reviewer will be doing exactly that.",
|
|
146
152
|
[{ file: "--privacy-policy", text: url }],
|
|
147
153
|
)];
|
|
154
|
+
} finally {
|
|
155
|
+
clearTimeout(timer);
|
|
148
156
|
}
|
|
149
|
-
clearTimeout(timer);
|
|
150
157
|
|
|
151
158
|
const finalUrl = res.url || u.href;
|
|
152
159
|
const redirected = finalUrl.replace(/\/$/, "") !== u.href.replace(/\/$/, "");
|
|
@@ -163,13 +170,6 @@ export async function checkPolicyUrl(url, { timeoutMs = 10000, fetchImpl = fetch
|
|
|
163
170
|
)];
|
|
164
171
|
}
|
|
165
172
|
|
|
166
|
-
let body = "";
|
|
167
|
-
try {
|
|
168
|
-
body = await res.text();
|
|
169
|
-
} catch {
|
|
170
|
-
body = "";
|
|
171
|
-
}
|
|
172
|
-
|
|
173
173
|
// Strip tags and scripts before counting words, or a single-page app's bundle
|
|
174
174
|
// reads as a wall of text that mentions everything.
|
|
175
175
|
const text = body
|