gscdump 4.4.0 → 4.6.0
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 +36 -0
- package/dist/bing/normalize.mjs +1 -1
- package/dist/core/coverage-state.d.mts +74 -0
- package/dist/core/coverage-state.mjs +80 -0
- package/dist/core/indexing-issues.d.mts +5 -3
- package/dist/core/indexing-issues.mjs +2 -23
- package/dist/index.d.mts +2 -1
- package/dist/index.mjs +2 -1
- package/dist/indexnow.d.mts +53 -0
- package/dist/indexnow.mjs +129 -0
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -92,6 +92,41 @@ See [URL inspection and indexing](../../docs/gscdump-cli/guides/6.url-indexing.m
|
|
|
92
92
|
|
|
93
93
|
The package root also exports batch and projection helpers such as `fetchSitesWithSitemaps`, `batchInspectUrlsFlatSettled`, `inspectUrlFlat`, and `batchRequestIndexing`.
|
|
94
94
|
|
|
95
|
+
## Send IndexNow change notifications
|
|
96
|
+
|
|
97
|
+
Publish a UTF-8 key file on the exact host you submit.
|
|
98
|
+
The key file contains your key, with no other content.
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
import { indexNow } from 'gscdump/indexnow'
|
|
102
|
+
|
|
103
|
+
const client = indexNow()
|
|
104
|
+
const input = {
|
|
105
|
+
host: 'example.com',
|
|
106
|
+
key: 'your-key-12345',
|
|
107
|
+
keyLocation: 'https://example.com/your-key-12345.txt',
|
|
108
|
+
}
|
|
109
|
+
const verification = await client.verify(input)
|
|
110
|
+
if (verification.ok && verification.value._tag === 'verified') {
|
|
111
|
+
const result = await client.submit({
|
|
112
|
+
...input,
|
|
113
|
+
urls: ['https://example.com/changed-page'],
|
|
114
|
+
})
|
|
115
|
+
console.log(result)
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
The client rejects URLs outside the exact host or key directory before sending.
|
|
120
|
+
Verification allows HTTPS, disables redirects, and reads at most 1,024 bytes within ten seconds.
|
|
121
|
+
Inject `fetch`, `clock`, or `timeoutMs` when you need different transport or timing.
|
|
122
|
+
Expected input failures return `Result`; infrastructure failures propagate.
|
|
123
|
+
|
|
124
|
+
Submission Receipts describe delivery. HTTP 200 means accepted; HTTP 202 means key validation remains pending.
|
|
125
|
+
Neither response proves indexing.
|
|
126
|
+
The direct client sends one batch of up to 10,000 URLs without retries.
|
|
127
|
+
Hosted callers use the SDK operations, which cap each batch at 1,000 URLs.
|
|
128
|
+
See the [IndexNow protocol](https://www.indexnow.org/documentation).
|
|
129
|
+
|
|
95
130
|
## Read Bing Indexing Evidence
|
|
96
131
|
|
|
97
132
|
Use `gscdump/bing` with an OAuth access token or Bing Webmaster API key. The client returns tagged
|
|
@@ -129,6 +164,7 @@ sitemap membership is available through `@gscdump/sdk/v1`.
|
|
|
129
164
|
|
|
130
165
|
- `gscdump/client`: Google client and authentication types
|
|
131
166
|
- `gscdump/indexing`: inspection and indexing helpers
|
|
167
|
+
- `gscdump/indexnow`: key verification and change notifications
|
|
132
168
|
- `gscdump/errors`: typed Google API failures
|
|
133
169
|
- `gscdump/sites`: Site helpers
|
|
134
170
|
- `gscdump/sitemap-identity`: sitemap scope and membership hashing
|
package/dist/bing/normalize.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { err, ok } from "../core/result.mjs";
|
|
2
|
-
const BING_UNAVAILABLE_DATE_MS = -
|
|
2
|
+
const BING_UNAVAILABLE_DATE_MS = -0xa9730b66800;
|
|
3
3
|
const BING_DATE_PATTERN = /^\/Date\((-?\d+)(?:[+-]\d{4})?\)\/$/;
|
|
4
4
|
function isRecord(value) {
|
|
5
5
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { CoverageStateCounts, CoverageStateTag, VerdictFreshness } from "@gscdump/contracts";
|
|
2
|
+
type MappedCoverageStateTag = Exclude<CoverageStateTag, 'unrecognized' | 'not_reported'>;
|
|
3
|
+
/**
|
|
4
|
+
* Parsed coverage state for one URL Inspection result.
|
|
5
|
+
*
|
|
6
|
+
* - A mapped tag: Google's prose names one known state.
|
|
7
|
+
* - `unrecognized`: Google returned prose no tag maps. `coverageState` keeps it.
|
|
8
|
+
* - `not_reported`: the result carried no coverage prose.
|
|
9
|
+
*/
|
|
10
|
+
export type CoverageState = {
|
|
11
|
+
_tag: MappedCoverageStateTag;
|
|
12
|
+
} | {
|
|
13
|
+
_tag: 'unrecognized';
|
|
14
|
+
coverageState: string;
|
|
15
|
+
} | {
|
|
16
|
+
_tag: 'not_reported';
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Every coverage prose string Google returns, by tag. Two spellings exist for the
|
|
20
|
+
* noindex exclusion: the URL Inspection API uses typographic quotes, and the Page
|
|
21
|
+
* indexing report uses straight quotes.
|
|
22
|
+
*/
|
|
23
|
+
export declare const GOOGLE_COVERAGE_STATES: {
|
|
24
|
+
readonly unknown_to_google: readonly ["URL is unknown to Google"];
|
|
25
|
+
readonly discovered_not_indexed: readonly ["Discovered - currently not indexed"];
|
|
26
|
+
readonly crawled_not_indexed: readonly ["Crawled - currently not indexed"];
|
|
27
|
+
readonly indexed: readonly ["Submitted and indexed", "Indexed, not submitted in sitemap", "Indexed, though blocked by robots.txt", "Indexed; consider marking as canonical", "Page indexed without content"];
|
|
28
|
+
readonly noindex: readonly ["Excluded by ‘noindex’ tag", "Excluded by 'noindex' tag"];
|
|
29
|
+
readonly blocked_robots: readonly ["Blocked by robots.txt"];
|
|
30
|
+
readonly soft_404: readonly ["Soft 404"];
|
|
31
|
+
readonly not_found: readonly ["Not found (404)"];
|
|
32
|
+
readonly server_error: readonly ["Server error (5xx)"];
|
|
33
|
+
readonly access_denied: readonly ["Blocked due to unauthorized request (401)"];
|
|
34
|
+
readonly access_forbidden: readonly ["Blocked due to access forbidden (403)"];
|
|
35
|
+
readonly blocked_4xx: readonly ["Blocked due to other 4xx issue"];
|
|
36
|
+
readonly redirect: readonly ["Page with redirect"];
|
|
37
|
+
readonly redirect_error: readonly ["Redirect error"];
|
|
38
|
+
readonly alternate_canonical: readonly ["Alternate page with proper canonical tag"];
|
|
39
|
+
readonly duplicate_no_canonical: readonly ["Duplicate without user-selected canonical"];
|
|
40
|
+
readonly duplicate_google_canonical: readonly ["Duplicate, Google chose different canonical than user"];
|
|
41
|
+
readonly page_removed: readonly ["Blocked by page removal tool"];
|
|
42
|
+
};
|
|
43
|
+
/** Parse Google's coverage prose for one URL. Surrounding whitespace is ignored. */
|
|
44
|
+
export declare function parseCoverageState(coverageState: string | null | undefined): CoverageState;
|
|
45
|
+
/** Zero for every coverage state tag. */
|
|
46
|
+
export declare function emptyCoverageStateCounts(): CoverageStateCounts;
|
|
47
|
+
/** Count parsed coverage states over URL Inspection results. */
|
|
48
|
+
export declare function countCoverageStates(coverageStates: Iterable<string | null | undefined>): CoverageStateCounts;
|
|
49
|
+
/**
|
|
50
|
+
* A SQL `CASE` expression that yields the coverage state tag for `column`, from
|
|
51
|
+
* the same prose table as {@link parseCoverageState}. It uses exact `IN` matches
|
|
52
|
+
* and `TRIM`, so it runs unchanged on SQLite and DuckDB. `column` is trusted SQL:
|
|
53
|
+
* pass a column name or a cast, never user input.
|
|
54
|
+
*/
|
|
55
|
+
export declare function coverageStateTagSql(column: string): string;
|
|
56
|
+
/**
|
|
57
|
+
* The inspection-time cutoffs for verdicts older than 7 and 30 days, as ISO
|
|
58
|
+
* strings. RFC 3339 UTC strings sort in time order, so a stored `inspectedAt`
|
|
59
|
+
* string compares against these directly.
|
|
60
|
+
*/
|
|
61
|
+
export declare function verdictAgeCutoffs(nowMs: number): {
|
|
62
|
+
olderThan7d: string;
|
|
63
|
+
olderThan30d: string;
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Turn verdict age counts into the wire freshness shape. The input is untrusted
|
|
67
|
+
* arithmetic from a SQL row, so counts are clamped: an older bucket can never
|
|
68
|
+
* exceed a younger one, and no bucket can exceed `verdicts`.
|
|
69
|
+
*/
|
|
70
|
+
export declare function measureVerdictFreshness(input: {
|
|
71
|
+
verdicts: number;
|
|
72
|
+
olderThan7d: number;
|
|
73
|
+
olderThan30d: number;
|
|
74
|
+
}): VerdictFreshness;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
const GOOGLE_COVERAGE_STATES = {
|
|
2
|
+
unknown_to_google: ["URL is unknown to Google"],
|
|
3
|
+
discovered_not_indexed: ["Discovered - currently not indexed"],
|
|
4
|
+
crawled_not_indexed: ["Crawled - currently not indexed"],
|
|
5
|
+
indexed: [
|
|
6
|
+
"Submitted and indexed",
|
|
7
|
+
"Indexed, not submitted in sitemap",
|
|
8
|
+
"Indexed, though blocked by robots.txt",
|
|
9
|
+
"Indexed; consider marking as canonical",
|
|
10
|
+
"Page indexed without content"
|
|
11
|
+
],
|
|
12
|
+
noindex: ["Excluded by ‘noindex’ tag", "Excluded by 'noindex' tag"],
|
|
13
|
+
blocked_robots: ["Blocked by robots.txt"],
|
|
14
|
+
soft_404: ["Soft 404"],
|
|
15
|
+
not_found: ["Not found (404)"],
|
|
16
|
+
server_error: ["Server error (5xx)"],
|
|
17
|
+
access_denied: ["Blocked due to unauthorized request (401)"],
|
|
18
|
+
access_forbidden: ["Blocked due to access forbidden (403)"],
|
|
19
|
+
blocked_4xx: ["Blocked due to other 4xx issue"],
|
|
20
|
+
redirect: ["Page with redirect"],
|
|
21
|
+
redirect_error: ["Redirect error"],
|
|
22
|
+
alternate_canonical: ["Alternate page with proper canonical tag"],
|
|
23
|
+
duplicate_no_canonical: ["Duplicate without user-selected canonical"],
|
|
24
|
+
duplicate_google_canonical: ["Duplicate, Google chose different canonical than user"],
|
|
25
|
+
page_removed: ["Blocked by page removal tool"]
|
|
26
|
+
};
|
|
27
|
+
const MAPPED_TAGS = Object.keys(GOOGLE_COVERAGE_STATES);
|
|
28
|
+
const TAG_BY_PROSE = new Map(MAPPED_TAGS.flatMap((tag) => GOOGLE_COVERAGE_STATES[tag].map((prose) => [prose, tag])));
|
|
29
|
+
function parseCoverageState(coverageState) {
|
|
30
|
+
const prose = coverageState?.trim();
|
|
31
|
+
if (!prose) return { _tag: "not_reported" };
|
|
32
|
+
const tag = TAG_BY_PROSE.get(prose);
|
|
33
|
+
return tag ? { _tag: tag } : {
|
|
34
|
+
_tag: "unrecognized",
|
|
35
|
+
coverageState: prose
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function emptyCoverageStateCounts() {
|
|
39
|
+
const counts = {
|
|
40
|
+
unrecognized: 0,
|
|
41
|
+
not_reported: 0
|
|
42
|
+
};
|
|
43
|
+
for (const tag of MAPPED_TAGS) counts[tag] = 0;
|
|
44
|
+
return counts;
|
|
45
|
+
}
|
|
46
|
+
function countCoverageStates(coverageStates) {
|
|
47
|
+
const counts = emptyCoverageStateCounts();
|
|
48
|
+
for (const coverageState of coverageStates) counts[parseCoverageState(coverageState)._tag]++;
|
|
49
|
+
return counts;
|
|
50
|
+
}
|
|
51
|
+
function sqlString(value) {
|
|
52
|
+
return `'${value.replaceAll("'", "''")}'`;
|
|
53
|
+
}
|
|
54
|
+
function coverageStateTagSql(column) {
|
|
55
|
+
return `CASE WHEN ${column} IS NULL OR TRIM(${column}) = '' THEN 'not_reported' ${MAPPED_TAGS.map((tag) => `WHEN TRIM(${column}) IN (${GOOGLE_COVERAGE_STATES[tag].map(sqlString).join(", ")}) THEN ${sqlString(tag)}`).join(" ")} ELSE 'unrecognized' END`;
|
|
56
|
+
}
|
|
57
|
+
const DAY_MS = 864e5;
|
|
58
|
+
function verdictAgeCutoffs(nowMs) {
|
|
59
|
+
return {
|
|
60
|
+
olderThan7d: (/* @__PURE__ */ new Date(nowMs - 7 * DAY_MS)).toISOString(),
|
|
61
|
+
olderThan30d: (/* @__PURE__ */ new Date(nowMs - 30 * DAY_MS)).toISOString()
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function percentOf(part, whole) {
|
|
65
|
+
return whole > 0 ? Math.round(part / whole * 1e3) / 10 : 0;
|
|
66
|
+
}
|
|
67
|
+
function measureVerdictFreshness(input) {
|
|
68
|
+
const verdicts = Math.max(0, Math.trunc(input.verdicts));
|
|
69
|
+
const olderThan7d = Math.min(verdicts, Math.max(0, Math.trunc(input.olderThan7d)));
|
|
70
|
+
const olderThan30d = Math.min(olderThan7d, Math.max(0, Math.trunc(input.olderThan30d)));
|
|
71
|
+
return {
|
|
72
|
+
_tag: "measured",
|
|
73
|
+
verdicts,
|
|
74
|
+
olderThan7d,
|
|
75
|
+
olderThan30d,
|
|
76
|
+
olderThan7dPercent: percentOf(olderThan7d, verdicts),
|
|
77
|
+
olderThan30dPercent: percentOf(olderThan30d, verdicts)
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
export { GOOGLE_COVERAGE_STATES, countCoverageStates, coverageStateTagSql, emptyCoverageStateCounts, measureVerdictFreshness, parseCoverageState, verdictAgeCutoffs };
|
|
@@ -51,9 +51,11 @@ export declare const INDEXING_ISSUE_SEVERITY: Record<IndexingIssueType, 'error'
|
|
|
51
51
|
*/
|
|
52
52
|
export declare const KNOWN_PAGE_FETCH_STATES: ReadonlySet<string>;
|
|
53
53
|
/**
|
|
54
|
-
* Every `coverageState` string
|
|
55
|
-
* that are not faults.
|
|
56
|
-
*
|
|
54
|
+
* Every `coverageState` string gscdump maps to a tag, plus the indexed-state
|
|
55
|
+
* strings that are not faults. Derived from the one prose table behind
|
|
56
|
+
* `parseCoverageState`, so this canary and the coverage tags cannot drift. Google
|
|
57
|
+
* is free to invent new prose here at any time; that is what
|
|
58
|
+
* {@link unmappedInspectionReasons} exists to catch.
|
|
57
59
|
*/
|
|
58
60
|
export declare const KNOWN_COVERAGE_STATES: ReadonlySet<string>;
|
|
59
61
|
export interface UnmappedInspectionReasons {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { GOOGLE_COVERAGE_STATES } from "./coverage-state.mjs";
|
|
1
2
|
function isFragmentUrl(url) {
|
|
2
3
|
return typeof url === "string" && url.includes("#");
|
|
3
4
|
}
|
|
@@ -111,29 +112,7 @@ const KNOWN_PAGE_FETCH_STATES = /* @__PURE__ */ new Set([
|
|
|
111
112
|
"INTERNAL_CRAWL_ERROR",
|
|
112
113
|
"INVALID_URL"
|
|
113
114
|
]);
|
|
114
|
-
const KNOWN_COVERAGE_STATES =
|
|
115
|
-
"Submitted and indexed",
|
|
116
|
-
"Indexed, not submitted in sitemap",
|
|
117
|
-
"Indexed, though blocked by robots.txt",
|
|
118
|
-
"Indexed; consider marking as canonical",
|
|
119
|
-
"URL is unknown to Google",
|
|
120
|
-
"Crawled - currently not indexed",
|
|
121
|
-
"Discovered - currently not indexed",
|
|
122
|
-
"Not found (404)",
|
|
123
|
-
"Soft 404",
|
|
124
|
-
"Server error (5xx)",
|
|
125
|
-
"Blocked due to access forbidden (403)",
|
|
126
|
-
"Blocked due to unauthorized request (401)",
|
|
127
|
-
"Blocked due to other 4xx issue",
|
|
128
|
-
"Redirect error",
|
|
129
|
-
"Blocked by robots.txt",
|
|
130
|
-
"Excluded by ‘noindex’ tag",
|
|
131
|
-
"Page with redirect",
|
|
132
|
-
"Alternate page with proper canonical tag",
|
|
133
|
-
"Duplicate without user-selected canonical",
|
|
134
|
-
"Duplicate, Google chose different canonical than user",
|
|
135
|
-
"Blocked by page removal tool"
|
|
136
|
-
]);
|
|
115
|
+
const KNOWN_COVERAGE_STATES = new Set(Object.values(GOOGLE_COVERAGE_STATES).flat());
|
|
137
116
|
function unmappedInspectionReasons(rows) {
|
|
138
117
|
const coverageStates = /* @__PURE__ */ new Set();
|
|
139
118
|
const pageFetchStates = /* @__PURE__ */ new Set();
|
package/dist/index.d.mts
CHANGED
|
@@ -12,6 +12,7 @@ import { OAuthTokenInfo, OAuthTokens, exchangeAuthCodeResult, introspectAccessTo
|
|
|
12
12
|
import { FetchSitesWithSitemapsOptions, addSite, deleteSite, deleteSitemap, fetchSitemap, fetchSitemaps, fetchSites, fetchSitesWithSitemaps, submitSitemap } from "./api/sites.mjs";
|
|
13
13
|
import { getVerificationToken, getVerifiedSite, listVerifiedSites, resolveVerificationTarget, siteUrlToVerificationSite, unverifySite, verificationMethodsFor, verifySite } from "./api/verification.mjs";
|
|
14
14
|
import { CanonicalDifferenceKind, classifyCanonicalDifference, isCanonicalMismatch } from "./core/canonical.mjs";
|
|
15
|
+
import { CoverageState, GOOGLE_COVERAGE_STATES, countCoverageStates, coverageStateTagSql, emptyCoverageStateCounts, measureVerdictFreshness, parseCoverageState, verdictAgeCutoffs } from "./core/coverage-state.mjs";
|
|
15
16
|
import { INDEXING_ISSUE_FILTERS, INDEXING_ISSUE_LABELS, INDEXING_ISSUE_SEVERITY, IndexingIssueType, KNOWN_COVERAGE_STATES, KNOWN_PAGE_FETCH_STATES, UnmappedInspectionReasons, isFragmentUrl, unmappedInspectionReasons } from "./core/indexing-issues.mjs";
|
|
16
17
|
import { GscPropertyCandidate, SiteCandidate, SiteResolution, findBestGscProperty, findExactGscProperty, formatGscPropertyCandidates, gscPropertyMatchesTarget, isVerifiedGscPermission, isVerifiedGscProperty, matchGscSite, pickBestGscProperty, resolveSiteInput } from "./core/property.mjs";
|
|
17
18
|
import { URL_INSPECTION_EFFECTIVE_LIMIT } from "./core/quota.mjs";
|
|
@@ -20,4 +21,4 @@ import { normalizeUrl } from "./normalize.mjs";
|
|
|
20
21
|
import { AccountNextAction, AccountStatus, AnalyticsNextAction, AnalyticsStatus, GSCDUMP_ONBOARDING_CONTRACT_VERSION, GSCDUMP_OPTIONAL_INDEXING_SCOPE, GSCDUMP_REQUIRED_ANALYTICS_SCOPE, GSCDUMP_WRITE_ANALYTICS_SCOPE, IndexingNextAction, IndexingStatus, LifecycleError, LifecycleErrorCode, LifecycleProgress, LifecycleWebhookEnvelope, LifecycleWebhookEvent, PartnerLifecycleAccount, PartnerLifecycleResponse, PartnerLifecycleSite, PropertyNextAction, PropertyStatus, QuerySourceMode, SitemapNextAction, SitemapStatus, accountNextActions, accountStatuses, analyticsNextActions, analyticsStatuses, hasOptionalIndexingScope, hasRequiredAnalyticsScope, indexingNextActions, indexingStatuses, lifecycleErrorCodes, parseGrantedScopes, propertyNextActions, propertyStatuses, querySourceModes, sitemapNextActions, sitemapStatuses } from "./onboarding.mjs";
|
|
21
22
|
import { decodeSiteId, encodeSiteId, normalizeSiteUrl } from "./tenant.mjs";
|
|
22
23
|
import { urlMatchKey } from "./url.mjs";
|
|
23
|
-
export { type AccountNextAction, type AccountStatus, AmpInspectionResult, AmpIssue, type AnalyticsNextAction, type AnalyticsStatus, ApiSite, ApiSitemap, ApiSitemapContent, Auth, AuthClient, AuthOptions, type BackfillProgress, BatchInspectUrlsFlatSettledOptions, CallOptions, CanonicalDifferenceKind, DAYS_PER_RANGE, DEFAULT_GSC_REQUEST_TIMEOUT_MS, DataRow, DimensionFilter, DimensionFilterGroup, Err, FetchSitesWithSitemapsOptions, GSCDUMP_ONBOARDING_CONTRACT_VERSION, GSCDUMP_OPTIONAL_INDEXING_SCOPE, GSCDUMP_REQUIRED_ANALYTICS_SCOPE, GSCDUMP_WRITE_ANALYTICS_SCOPE, GSC_FINALIZED_LAG_DAYS, GSC_FRESHEST_LAG_DAYS, GSC_INDEXING_SCOPE, GSC_READ_SCOPE, GSC_RETENTION_MONTHS, GSC_SITE_VERIFICATION_SCOPE, GSC_WRITE_SCOPE, GoogleSearchConsoleClient, GoogleSearchConsoleClientOptions, GscApiError, type GscApiErrorInfo, type GscError, type GscErrorKind, GscPropertyCandidate, type GscQuota, INDEXING_ISSUE_FILTERS, INDEXING_ISSUE_LABELS, INDEXING_ISSUE_SEVERITY, IndexStatusResult, IndexingEligibility, IndexingIneligibleReason, IndexingIssueType, IndexingMetadata, type IndexingNextAction, IndexingNotificationType, IndexingResult, type IndexingStatus, InspectUrlFlatSettledResult, InspectUrlIndexResponse, InspectUrlResult, InspectionPriority, KNOWN_COVERAGE_STATES, KNOWN_PAGE_FETCH_STATES, LegacyInspectionPriority, type LifecycleError, type LifecycleErrorCode, type LifecycleProgress, type LifecycleWebhookEnvelope, type LifecycleWebhookEvent, MAX_FLAT_INSPECTION_BATCH_CONCURRENCY, MS_PER_DAY, MobileUsabilityIssue, MobileUsabilityResult, OAuthTokenInfo, OAuthTokens, Ok, ParsedGscSiteUrl, ParsedIndexingResult, type PartnerLifecycleAccount, type PartnerLifecycleResponse, type PartnerLifecycleSite, Period, type PropertyNextAction, type PropertyStatus, PublishUrlNotificationResponse, QueryReturn, type QuerySourceMode, RequiredNonNullable, ResolvedAnalyticsRange, Result, RichResultsDetectedItem, RichResultsIssue, RichResultsItem, RichResultsResult, SearchAnalyticsMetadata, SearchAnalyticsQuery, SearchAnalyticsResponse, Site, SiteAnalytics, SiteCandidate, SiteResolution, type SitemapNextAction, type SitemapStatus, URL_INSPECTION_EFFECTIVE_LIMIT, URL_INSPECTION_QUOTA, UnmappedInspectionReasons, UrlInspectionResult, UrlNotification, UrlNotificationMetadata, ValueWeightedInspectionPriority, VerificationMethod, VerificationSite, VerificationSiteType, VerificationToken, VerificationWebResource, accountNextActions, accountStatuses, addDays, addSite, analyticsNextActions, analyticsStatuses, batchInspectUrls, batchInspectUrlsFlatSettled, batchRequestIndexing, canUseUrlInspection, classifyCanonicalDifference, classifyError, countDays, createAuth, createFetch, decodeSiteId, deleteSite, deleteSitemap, describeInspectionError, encodeSiteId, err, exchangeAuthCodeResult, fetchSitemap, fetchSitemaps, fetchSites, fetchSitesWithSitemaps, findBestGscProperty, findExactGscProperty, formatGscPropertyCandidates, generateGscDateRange, getBackfillProgress, getDateRange, getFreshestGscDate, getIndexingEligibility, getIndexingMetadata, getLatestGscDate, getNextCheckAfter, getNextCheckPriority, getNextDate, getOldestGscDate, getPendingDates, getPreviousDate, getPstDate, getVerificationToken, getVerifiedSite, googleSearchConsole, groupIntoRanges, gscErrorToException, gscPropertyMatchesTarget, hasGoogleScope, hasGscReadScope, hasGscWriteScope, hasIndexingScope, hasOptionalIndexingScope, hasRequiredAnalyticsScope, indexingNextActions, indexingStatuses, inspectUrl, inspectUrlFlat, introspectAccessToken, introspectAccessTokenResult, isCanonicalMismatch, isErr, isFragmentUrl, isOk, isPermissionDeniedError, isValidGscDate, isVerifiedGscPermission, isVerifiedGscProperty, lifecycleErrorCodes, listVerifiedSites, matchGscSite, normalizeGscSiteUrl, normalizeRegistrationTarget, normalizeSiteUrl, normalizeUrl, ok, parseGoogleError, parseGrantedScopes, parseGscSiteUrl, pickBestGscProperty, propertyNextActions, propertyStatuses, querySourceModes, refreshAccessToken, requestIndexing, resolveSiteInput, resolveVerificationTarget, rethrowAsGscApiError, revokeOAuthToken, revokeOAuthTokenResult, runSequentialBatch, siteUrlToVerificationSite, sitemapNextActions, sitemapStatuses, submitSitemap, toIsoDate, unmappedInspectionReasons, unverifySite, unwrapResult, urlMatchKey, verificationMethodsFor, verifySite };
|
|
24
|
+
export { type AccountNextAction, type AccountStatus, AmpInspectionResult, AmpIssue, type AnalyticsNextAction, type AnalyticsStatus, ApiSite, ApiSitemap, ApiSitemapContent, Auth, AuthClient, AuthOptions, type BackfillProgress, BatchInspectUrlsFlatSettledOptions, CallOptions, CanonicalDifferenceKind, CoverageState, DAYS_PER_RANGE, DEFAULT_GSC_REQUEST_TIMEOUT_MS, DataRow, DimensionFilter, DimensionFilterGroup, Err, FetchSitesWithSitemapsOptions, GOOGLE_COVERAGE_STATES, GSCDUMP_ONBOARDING_CONTRACT_VERSION, GSCDUMP_OPTIONAL_INDEXING_SCOPE, GSCDUMP_REQUIRED_ANALYTICS_SCOPE, GSCDUMP_WRITE_ANALYTICS_SCOPE, GSC_FINALIZED_LAG_DAYS, GSC_FRESHEST_LAG_DAYS, GSC_INDEXING_SCOPE, GSC_READ_SCOPE, GSC_RETENTION_MONTHS, GSC_SITE_VERIFICATION_SCOPE, GSC_WRITE_SCOPE, GoogleSearchConsoleClient, GoogleSearchConsoleClientOptions, GscApiError, type GscApiErrorInfo, type GscError, type GscErrorKind, GscPropertyCandidate, type GscQuota, INDEXING_ISSUE_FILTERS, INDEXING_ISSUE_LABELS, INDEXING_ISSUE_SEVERITY, IndexStatusResult, IndexingEligibility, IndexingIneligibleReason, IndexingIssueType, IndexingMetadata, type IndexingNextAction, IndexingNotificationType, IndexingResult, type IndexingStatus, InspectUrlFlatSettledResult, InspectUrlIndexResponse, InspectUrlResult, InspectionPriority, KNOWN_COVERAGE_STATES, KNOWN_PAGE_FETCH_STATES, LegacyInspectionPriority, type LifecycleError, type LifecycleErrorCode, type LifecycleProgress, type LifecycleWebhookEnvelope, type LifecycleWebhookEvent, MAX_FLAT_INSPECTION_BATCH_CONCURRENCY, MS_PER_DAY, MobileUsabilityIssue, MobileUsabilityResult, OAuthTokenInfo, OAuthTokens, Ok, ParsedGscSiteUrl, ParsedIndexingResult, type PartnerLifecycleAccount, type PartnerLifecycleResponse, type PartnerLifecycleSite, Period, type PropertyNextAction, type PropertyStatus, PublishUrlNotificationResponse, QueryReturn, type QuerySourceMode, RequiredNonNullable, ResolvedAnalyticsRange, Result, RichResultsDetectedItem, RichResultsIssue, RichResultsItem, RichResultsResult, SearchAnalyticsMetadata, SearchAnalyticsQuery, SearchAnalyticsResponse, Site, SiteAnalytics, SiteCandidate, SiteResolution, type SitemapNextAction, type SitemapStatus, URL_INSPECTION_EFFECTIVE_LIMIT, URL_INSPECTION_QUOTA, UnmappedInspectionReasons, UrlInspectionResult, UrlNotification, UrlNotificationMetadata, ValueWeightedInspectionPriority, VerificationMethod, VerificationSite, VerificationSiteType, VerificationToken, VerificationWebResource, accountNextActions, accountStatuses, addDays, addSite, analyticsNextActions, analyticsStatuses, batchInspectUrls, batchInspectUrlsFlatSettled, batchRequestIndexing, canUseUrlInspection, classifyCanonicalDifference, classifyError, countCoverageStates, countDays, coverageStateTagSql, createAuth, createFetch, decodeSiteId, deleteSite, deleteSitemap, describeInspectionError, emptyCoverageStateCounts, encodeSiteId, err, exchangeAuthCodeResult, fetchSitemap, fetchSitemaps, fetchSites, fetchSitesWithSitemaps, findBestGscProperty, findExactGscProperty, formatGscPropertyCandidates, generateGscDateRange, getBackfillProgress, getDateRange, getFreshestGscDate, getIndexingEligibility, getIndexingMetadata, getLatestGscDate, getNextCheckAfter, getNextCheckPriority, getNextDate, getOldestGscDate, getPendingDates, getPreviousDate, getPstDate, getVerificationToken, getVerifiedSite, googleSearchConsole, groupIntoRanges, gscErrorToException, gscPropertyMatchesTarget, hasGoogleScope, hasGscReadScope, hasGscWriteScope, hasIndexingScope, hasOptionalIndexingScope, hasRequiredAnalyticsScope, indexingNextActions, indexingStatuses, inspectUrl, inspectUrlFlat, introspectAccessToken, introspectAccessTokenResult, isCanonicalMismatch, isErr, isFragmentUrl, isOk, isPermissionDeniedError, isValidGscDate, isVerifiedGscPermission, isVerifiedGscProperty, lifecycleErrorCodes, listVerifiedSites, matchGscSite, measureVerdictFreshness, normalizeGscSiteUrl, normalizeRegistrationTarget, normalizeSiteUrl, normalizeUrl, ok, parseCoverageState, parseGoogleError, parseGrantedScopes, parseGscSiteUrl, pickBestGscProperty, propertyNextActions, propertyStatuses, querySourceModes, refreshAccessToken, requestIndexing, resolveSiteInput, resolveVerificationTarget, rethrowAsGscApiError, revokeOAuthToken, revokeOAuthTokenResult, runSequentialBatch, siteUrlToVerificationSite, sitemapNextActions, sitemapStatuses, submitSitemap, toIsoDate, unmappedInspectionReasons, unverifySite, unwrapResult, urlMatchKey, verdictAgeCutoffs, verificationMethodsFor, verifySite };
|
package/dist/index.mjs
CHANGED
|
@@ -11,6 +11,7 @@ import { getVerificationToken, getVerifiedSite, listVerifiedSites, resolveVerifi
|
|
|
11
11
|
import { classifyCanonicalDifference, isCanonicalMismatch } from "./core/canonical.mjs";
|
|
12
12
|
import { DAYS_PER_RANGE, GSC_FINALIZED_LAG_DAYS, GSC_FRESHEST_LAG_DAYS, GSC_RETENTION_MONTHS, MS_PER_DAY, addDays, countDays, generateGscDateRange, getBackfillProgress, getDateRange, getFreshestGscDate, getLatestGscDate, getNextDate, getOldestGscDate, getPendingDates, getPreviousDate, getPstDate, groupIntoRanges, isValidGscDate, toIsoDate } from "./core/gsc-dates.mjs";
|
|
13
13
|
import { DEFAULT_GSC_REQUEST_TIMEOUT_MS, createAuth, createFetch, googleSearchConsole } from "./core/client.mjs";
|
|
14
|
+
import { GOOGLE_COVERAGE_STATES, countCoverageStates, coverageStateTagSql, emptyCoverageStateCounts, measureVerdictFreshness, parseCoverageState, verdictAgeCutoffs } from "./core/coverage-state.mjs";
|
|
14
15
|
import { INDEXING_ISSUE_FILTERS, INDEXING_ISSUE_LABELS, INDEXING_ISSUE_SEVERITY, KNOWN_COVERAGE_STATES, KNOWN_PAGE_FETCH_STATES, isFragmentUrl, unmappedInspectionReasons } from "./core/indexing-issues.mjs";
|
|
15
16
|
import { normalizeGscSiteUrl, normalizeRegistrationTarget, parseGscSiteUrl } from "./core/site-url.mjs";
|
|
16
17
|
import { findBestGscProperty, findExactGscProperty, formatGscPropertyCandidates, gscPropertyMatchesTarget, isVerifiedGscPermission, isVerifiedGscProperty, matchGscSite, pickBestGscProperty, resolveSiteInput } from "./core/property.mjs";
|
|
@@ -20,4 +21,4 @@ import { normalizeUrl } from "./normalize.mjs";
|
|
|
20
21
|
import { GSCDUMP_ONBOARDING_CONTRACT_VERSION, GSCDUMP_OPTIONAL_INDEXING_SCOPE, GSCDUMP_REQUIRED_ANALYTICS_SCOPE, GSCDUMP_WRITE_ANALYTICS_SCOPE, accountNextActions, accountStatuses, analyticsNextActions, analyticsStatuses, hasOptionalIndexingScope, hasRequiredAnalyticsScope, indexingNextActions, indexingStatuses, lifecycleErrorCodes, parseGrantedScopes, propertyNextActions, propertyStatuses, querySourceModes, sitemapNextActions, sitemapStatuses } from "./onboarding.mjs";
|
|
21
22
|
import { decodeSiteId, encodeSiteId, normalizeSiteUrl } from "./tenant.mjs";
|
|
22
23
|
import { urlMatchKey } from "./url.mjs";
|
|
23
|
-
export { DAYS_PER_RANGE, DEFAULT_GSC_REQUEST_TIMEOUT_MS, GSCDUMP_ONBOARDING_CONTRACT_VERSION, GSCDUMP_OPTIONAL_INDEXING_SCOPE, GSCDUMP_REQUIRED_ANALYTICS_SCOPE, GSCDUMP_WRITE_ANALYTICS_SCOPE, GSC_FINALIZED_LAG_DAYS, GSC_FRESHEST_LAG_DAYS, GSC_INDEXING_SCOPE, GSC_READ_SCOPE, GSC_RETENTION_MONTHS, GSC_SITE_VERIFICATION_SCOPE, GSC_WRITE_SCOPE, GscApiError, INDEXING_ISSUE_FILTERS, INDEXING_ISSUE_LABELS, INDEXING_ISSUE_SEVERITY, KNOWN_COVERAGE_STATES, KNOWN_PAGE_FETCH_STATES, MAX_FLAT_INSPECTION_BATCH_CONCURRENCY, MS_PER_DAY, URL_INSPECTION_EFFECTIVE_LIMIT, URL_INSPECTION_QUOTA, accountNextActions, accountStatuses, addDays, addSite, analyticsNextActions, analyticsStatuses, batchInspectUrls, batchInspectUrlsFlatSettled, batchRequestIndexing, canUseUrlInspection, classifyCanonicalDifference, classifyError, countDays, createAuth, createFetch, decodeSiteId, deleteSite, deleteSitemap, describeInspectionError, encodeSiteId, err, exchangeAuthCodeResult, fetchSitemap, fetchSitemaps, fetchSites, fetchSitesWithSitemaps, findBestGscProperty, findExactGscProperty, formatGscPropertyCandidates, generateGscDateRange, getBackfillProgress, getDateRange, getFreshestGscDate, getIndexingEligibility, getIndexingMetadata, getLatestGscDate, getNextCheckAfter, getNextCheckPriority, getNextDate, getOldestGscDate, getPendingDates, getPreviousDate, getPstDate, getVerificationToken, getVerifiedSite, googleSearchConsole, groupIntoRanges, gscErrorToException, gscPropertyMatchesTarget, hasGoogleScope, hasGscReadScope, hasGscWriteScope, hasIndexingScope, hasOptionalIndexingScope, hasRequiredAnalyticsScope, indexingNextActions, indexingStatuses, inspectUrl, inspectUrlFlat, introspectAccessToken, introspectAccessTokenResult, isCanonicalMismatch, isErr, isFragmentUrl, isOk, isPermissionDeniedError, isValidGscDate, isVerifiedGscPermission, isVerifiedGscProperty, lifecycleErrorCodes, listVerifiedSites, matchGscSite, normalizeGscSiteUrl, normalizeRegistrationTarget, normalizeSiteUrl, normalizeUrl, ok, parseGoogleError, parseGrantedScopes, parseGscSiteUrl, pickBestGscProperty, propertyNextActions, propertyStatuses, querySourceModes, refreshAccessToken, requestIndexing, resolveSiteInput, resolveVerificationTarget, rethrowAsGscApiError, revokeOAuthToken, revokeOAuthTokenResult, runSequentialBatch, siteUrlToVerificationSite, sitemapNextActions, sitemapStatuses, submitSitemap, toIsoDate, unmappedInspectionReasons, unverifySite, unwrapResult, urlMatchKey, verificationMethodsFor, verifySite };
|
|
24
|
+
export { DAYS_PER_RANGE, DEFAULT_GSC_REQUEST_TIMEOUT_MS, GOOGLE_COVERAGE_STATES, GSCDUMP_ONBOARDING_CONTRACT_VERSION, GSCDUMP_OPTIONAL_INDEXING_SCOPE, GSCDUMP_REQUIRED_ANALYTICS_SCOPE, GSCDUMP_WRITE_ANALYTICS_SCOPE, GSC_FINALIZED_LAG_DAYS, GSC_FRESHEST_LAG_DAYS, GSC_INDEXING_SCOPE, GSC_READ_SCOPE, GSC_RETENTION_MONTHS, GSC_SITE_VERIFICATION_SCOPE, GSC_WRITE_SCOPE, GscApiError, INDEXING_ISSUE_FILTERS, INDEXING_ISSUE_LABELS, INDEXING_ISSUE_SEVERITY, KNOWN_COVERAGE_STATES, KNOWN_PAGE_FETCH_STATES, MAX_FLAT_INSPECTION_BATCH_CONCURRENCY, MS_PER_DAY, URL_INSPECTION_EFFECTIVE_LIMIT, URL_INSPECTION_QUOTA, accountNextActions, accountStatuses, addDays, addSite, analyticsNextActions, analyticsStatuses, batchInspectUrls, batchInspectUrlsFlatSettled, batchRequestIndexing, canUseUrlInspection, classifyCanonicalDifference, classifyError, countCoverageStates, countDays, coverageStateTagSql, createAuth, createFetch, decodeSiteId, deleteSite, deleteSitemap, describeInspectionError, emptyCoverageStateCounts, encodeSiteId, err, exchangeAuthCodeResult, fetchSitemap, fetchSitemaps, fetchSites, fetchSitesWithSitemaps, findBestGscProperty, findExactGscProperty, formatGscPropertyCandidates, generateGscDateRange, getBackfillProgress, getDateRange, getFreshestGscDate, getIndexingEligibility, getIndexingMetadata, getLatestGscDate, getNextCheckAfter, getNextCheckPriority, getNextDate, getOldestGscDate, getPendingDates, getPreviousDate, getPstDate, getVerificationToken, getVerifiedSite, googleSearchConsole, groupIntoRanges, gscErrorToException, gscPropertyMatchesTarget, hasGoogleScope, hasGscReadScope, hasGscWriteScope, hasIndexingScope, hasOptionalIndexingScope, hasRequiredAnalyticsScope, indexingNextActions, indexingStatuses, inspectUrl, inspectUrlFlat, introspectAccessToken, introspectAccessTokenResult, isCanonicalMismatch, isErr, isFragmentUrl, isOk, isPermissionDeniedError, isValidGscDate, isVerifiedGscPermission, isVerifiedGscProperty, lifecycleErrorCodes, listVerifiedSites, matchGscSite, measureVerdictFreshness, normalizeGscSiteUrl, normalizeRegistrationTarget, normalizeSiteUrl, normalizeUrl, ok, parseCoverageState, parseGoogleError, parseGrantedScopes, parseGscSiteUrl, pickBestGscProperty, propertyNextActions, propertyStatuses, querySourceModes, refreshAccessToken, requestIndexing, resolveSiteInput, resolveVerificationTarget, rethrowAsGscApiError, revokeOAuthToken, revokeOAuthTokenResult, runSequentialBatch, siteUrlToVerificationSite, sitemapNextActions, sitemapStatuses, submitSitemap, toIsoDate, unmappedInspectionReasons, unverifySite, unwrapResult, urlMatchKey, verdictAgeCutoffs, verificationMethodsFor, verifySite };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Result } from "./core/result.mjs";
|
|
2
|
+
export interface IndexNowInput {
|
|
3
|
+
host: string;
|
|
4
|
+
key: string;
|
|
5
|
+
keyLocation: string;
|
|
6
|
+
}
|
|
7
|
+
export type IndexNowDelivery = {
|
|
8
|
+
_tag: 'accepted';
|
|
9
|
+
httpStatus: 200;
|
|
10
|
+
reason: null;
|
|
11
|
+
retryAfterMs: null;
|
|
12
|
+
} | {
|
|
13
|
+
_tag: 'pending';
|
|
14
|
+
httpStatus: 202;
|
|
15
|
+
reason: 'key-validation-pending';
|
|
16
|
+
retryAfterMs: null;
|
|
17
|
+
} | {
|
|
18
|
+
_tag: 'rejected';
|
|
19
|
+
httpStatus: 400 | 403 | 422;
|
|
20
|
+
reason: string;
|
|
21
|
+
retryAfterMs: null;
|
|
22
|
+
} | {
|
|
23
|
+
_tag: 'retrying';
|
|
24
|
+
httpStatus: number;
|
|
25
|
+
reason: string;
|
|
26
|
+
retryAfterMs: number | null;
|
|
27
|
+
} | {
|
|
28
|
+
_tag: 'failed';
|
|
29
|
+
httpStatus: number;
|
|
30
|
+
reason: string;
|
|
31
|
+
retryAfterMs: null;
|
|
32
|
+
};
|
|
33
|
+
export interface IndexNowInvalidRequest {
|
|
34
|
+
_tag: 'InvalidRequest';
|
|
35
|
+
reason: string;
|
|
36
|
+
}
|
|
37
|
+
export type IndexNowVerification = {
|
|
38
|
+
_tag: 'verified';
|
|
39
|
+
} | {
|
|
40
|
+
_tag: 'verification-required';
|
|
41
|
+
reason: string;
|
|
42
|
+
};
|
|
43
|
+
export interface IndexNowClient {
|
|
44
|
+
verify: (input: IndexNowInput) => Promise<Result<IndexNowVerification, IndexNowInvalidRequest>>;
|
|
45
|
+
submit: (input: IndexNowInput & {
|
|
46
|
+
urls: string[];
|
|
47
|
+
}) => Promise<Result<IndexNowDelivery, IndexNowInvalidRequest>>;
|
|
48
|
+
}
|
|
49
|
+
export declare function indexNow(options?: {
|
|
50
|
+
fetch?: typeof globalThis.fetch;
|
|
51
|
+
clock?: () => Date;
|
|
52
|
+
timeoutMs?: number;
|
|
53
|
+
}): IndexNowClient;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { err, ok } from "./core/result.mjs";
|
|
2
|
+
function parseUrl(value) {
|
|
3
|
+
return URL.canParse(value) ? new URL(value) : null;
|
|
4
|
+
}
|
|
5
|
+
function parseInput(input) {
|
|
6
|
+
const location = parseUrl(input.keyLocation);
|
|
7
|
+
if (!/^[a-z0-9-]{8,128}$/i.test(input.key)) return err({
|
|
8
|
+
_tag: "InvalidRequest",
|
|
9
|
+
reason: "invalid-key"
|
|
10
|
+
});
|
|
11
|
+
if (!location || location.protocol !== "https:" || location.host !== input.host || location.username || location.password || location.hash || location.search || location.port) return err({
|
|
12
|
+
_tag: "InvalidRequest",
|
|
13
|
+
reason: "invalid-key-location"
|
|
14
|
+
});
|
|
15
|
+
return ok(location);
|
|
16
|
+
}
|
|
17
|
+
function indexNow(options = {}) {
|
|
18
|
+
const fetch = options.fetch ?? globalThis.fetch;
|
|
19
|
+
const timeoutMs = options.timeoutMs ?? 1e4;
|
|
20
|
+
const clock = options.clock ?? (() => /* @__PURE__ */ new Date());
|
|
21
|
+
return {
|
|
22
|
+
async verify(input) {
|
|
23
|
+
const parsed = parseInput(input);
|
|
24
|
+
if (!parsed.ok) return parsed;
|
|
25
|
+
const response = await fetch(input.keyLocation, {
|
|
26
|
+
redirect: "error",
|
|
27
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
28
|
+
});
|
|
29
|
+
if (response.status !== 200) return ok({
|
|
30
|
+
_tag: "verification-required",
|
|
31
|
+
reason: "key-file-unavailable"
|
|
32
|
+
});
|
|
33
|
+
const reader = response.body?.getReader();
|
|
34
|
+
if (!reader) return ok({
|
|
35
|
+
_tag: "verification-required",
|
|
36
|
+
reason: "key-file-empty"
|
|
37
|
+
});
|
|
38
|
+
const chunks = [];
|
|
39
|
+
let length = 0;
|
|
40
|
+
for (;;) {
|
|
41
|
+
const next = await reader.read();
|
|
42
|
+
if (next.done) break;
|
|
43
|
+
length += next.value.length;
|
|
44
|
+
if (length > 1024) {
|
|
45
|
+
await reader.cancel();
|
|
46
|
+
return ok({
|
|
47
|
+
_tag: "verification-required",
|
|
48
|
+
reason: "key-file-too-large"
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
chunks.push(next.value);
|
|
52
|
+
}
|
|
53
|
+
const bytes = new Uint8Array(length);
|
|
54
|
+
let offset = 0;
|
|
55
|
+
for (const chunk of chunks) {
|
|
56
|
+
bytes.set(chunk, offset);
|
|
57
|
+
offset += chunk.length;
|
|
58
|
+
}
|
|
59
|
+
return ok(new TextDecoder().decode(bytes).trim() === input.key ? { _tag: "verified" } : {
|
|
60
|
+
_tag: "verification-required",
|
|
61
|
+
reason: "key-file-mismatch"
|
|
62
|
+
});
|
|
63
|
+
},
|
|
64
|
+
async submit(input) {
|
|
65
|
+
const parsed = parseInput(input);
|
|
66
|
+
if (!parsed.ok) return parsed;
|
|
67
|
+
if (!input.urls.length || input.urls.length > 1e4) return err({
|
|
68
|
+
_tag: "InvalidRequest",
|
|
69
|
+
reason: "invalid-url-count"
|
|
70
|
+
});
|
|
71
|
+
const directory = parsed.value.pathname.slice(0, parsed.value.pathname.lastIndexOf("/") + 1);
|
|
72
|
+
for (const value of input.urls) {
|
|
73
|
+
const url = parseUrl(value);
|
|
74
|
+
if (!url || !["https:", "http:"].includes(url.protocol) || url.host !== input.host || url.username || url.password || url.port || url.hash || !url.pathname.startsWith(directory)) return err({
|
|
75
|
+
_tag: "InvalidRequest",
|
|
76
|
+
reason: "url-outside-key-scope"
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
const response = await fetch("https://api.indexnow.org/indexnow", {
|
|
80
|
+
method: "POST",
|
|
81
|
+
redirect: "error",
|
|
82
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
83
|
+
headers: { "content-type": "application/json; charset=utf-8" },
|
|
84
|
+
body: JSON.stringify({
|
|
85
|
+
host: input.host,
|
|
86
|
+
key: input.key,
|
|
87
|
+
keyLocation: input.keyLocation,
|
|
88
|
+
urlList: input.urls
|
|
89
|
+
})
|
|
90
|
+
});
|
|
91
|
+
if (response.status === 200) return ok({
|
|
92
|
+
_tag: "accepted",
|
|
93
|
+
httpStatus: 200,
|
|
94
|
+
reason: null,
|
|
95
|
+
retryAfterMs: null
|
|
96
|
+
});
|
|
97
|
+
if (response.status === 202) return ok({
|
|
98
|
+
_tag: "pending",
|
|
99
|
+
httpStatus: 202,
|
|
100
|
+
reason: "key-validation-pending",
|
|
101
|
+
retryAfterMs: null
|
|
102
|
+
});
|
|
103
|
+
if (response.status === 400 || response.status === 403 || response.status === 422) return ok({
|
|
104
|
+
_tag: "rejected",
|
|
105
|
+
httpStatus: response.status,
|
|
106
|
+
reason: response.status === 403 ? "invalid-key" : "invalid-request",
|
|
107
|
+
retryAfterMs: null
|
|
108
|
+
});
|
|
109
|
+
if (response.status === 429 || response.status >= 500) {
|
|
110
|
+
const retry = response.headers.get("retry-after");
|
|
111
|
+
const seconds = retry === null ? NaN : Number(retry);
|
|
112
|
+
const delay = Number.isFinite(seconds) && seconds >= 0 ? seconds * 1e3 : retry === null ? NaN : Date.parse(retry) - clock().getTime();
|
|
113
|
+
return ok({
|
|
114
|
+
_tag: "retrying",
|
|
115
|
+
httpStatus: response.status,
|
|
116
|
+
reason: response.status === 429 ? "rate-limited" : "service-unavailable",
|
|
117
|
+
retryAfterMs: Number.isFinite(delay) ? Math.max(0, delay) : null
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
return ok({
|
|
121
|
+
_tag: "failed",
|
|
122
|
+
httpStatus: response.status,
|
|
123
|
+
reason: "unexpected-response",
|
|
124
|
+
retryAfterMs: null
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
export { indexNow };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gscdump",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "4.
|
|
4
|
+
"version": "4.6.0",
|
|
5
5
|
"description": "Direct Google Search Console and Bing Webmaster clients with typed queries and Indexing Evidence",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Harlan Wilton",
|
|
@@ -102,6 +102,11 @@
|
|
|
102
102
|
"types": "./dist/tenant.d.mts",
|
|
103
103
|
"import": "./dist/tenant.mjs",
|
|
104
104
|
"default": "./dist/tenant.mjs"
|
|
105
|
+
},
|
|
106
|
+
"./indexnow": {
|
|
107
|
+
"types": "./dist/indexnow.d.mts",
|
|
108
|
+
"import": "./dist/indexnow.mjs",
|
|
109
|
+
"default": "./dist/indexnow.mjs"
|
|
105
110
|
}
|
|
106
111
|
},
|
|
107
112
|
"main": "./dist/index.mjs",
|
|
@@ -113,7 +118,7 @@
|
|
|
113
118
|
"node": ">=22.13.0"
|
|
114
119
|
},
|
|
115
120
|
"dependencies": {
|
|
116
|
-
"@gscdump/contracts": "^4.
|
|
121
|
+
"@gscdump/contracts": "^4.6.0",
|
|
117
122
|
"ofetch": "^1.5.1"
|
|
118
123
|
},
|
|
119
124
|
"scripts": {
|