@power-seo/search-console 1.0.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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts","../src/auth.ts","../src/client.ts","../src/analytics.ts","../src/inspection.ts","../src/sitemaps.ts"],"names":["createTokenBucket","getWaitTime","sleep","consumeToken"],"mappings":"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","file":"index.cjs","sourcesContent":["// ============================================================================\r\n// @power-seo/search-console — Types\r\n// ============================================================================\r\n\r\n// --- Auth Types ---\r\n\r\nexport interface OAuthCredentials {\r\n clientId: string;\r\n clientSecret: string;\r\n refreshToken: string;\r\n}\r\n\r\nexport interface ServiceAccountCredentials {\r\n clientEmail: string;\r\n privateKeyId: string;\r\n signJwt: (payload: JwtPayload) => Promise<string>;\r\n}\r\n\r\nexport interface JwtPayload {\r\n iss: string;\r\n scope: string;\r\n aud: string;\r\n exp: number;\r\n iat: number;\r\n}\r\n\r\nexport interface TokenResult {\r\n accessToken: string;\r\n expiresAt: number;\r\n}\r\n\r\nexport interface TokenManager {\r\n getToken: () => Promise<string>;\r\n invalidate: () => void;\r\n}\r\n\r\n// --- Client Types ---\r\n\r\nexport interface GSCClientConfig {\r\n auth: TokenManager;\r\n siteUrl: string;\r\n rateLimitPerMinute?: number;\r\n maxRetries?: number;\r\n baseUrl?: string;\r\n}\r\n\r\nexport interface GSCClient {\r\n request: <T>(path: string, options?: RequestOptions) => Promise<T>;\r\n siteUrl: string;\r\n}\r\n\r\nexport interface RequestOptions {\r\n method?: string;\r\n body?: unknown;\r\n signal?: globalThis.AbortSignal;\r\n}\r\n\r\n// --- Error ---\r\n\r\nexport class GSCApiError extends Error {\r\n readonly status: number;\r\n readonly code: string;\r\n readonly retryable: boolean;\r\n\r\n constructor(message: string, status: number, code: string) {\r\n super(message);\r\n this.name = 'GSCApiError';\r\n this.status = status;\r\n this.code = code;\r\n this.retryable = status === 429 || status >= 500;\r\n }\r\n}\r\n\r\n// --- Analytics Types ---\r\n\r\nexport type SearchType = 'web' | 'image' | 'video' | 'news' | 'discover' | 'googleNews';\r\nexport type Dimension = 'query' | 'page' | 'country' | 'device' | 'date' | 'searchAppearance';\r\nexport type AggregationType = 'auto' | 'byPage' | 'byProperty';\r\nexport type DataState = 'all' | 'final';\r\n\r\nexport interface DimensionFilter {\r\n dimension: Dimension;\r\n operator: 'equals' | 'notEquals' | 'contains' | 'notContains' | 'includingRegex' | 'excludingRegex';\r\n expression: string;\r\n}\r\n\r\nexport interface DimensionFilterGroup {\r\n groupType?: 'and';\r\n filters: DimensionFilter[];\r\n}\r\n\r\nexport interface SearchAnalyticsRequest {\r\n startDate: string;\r\n endDate: string;\r\n dimensions?: Dimension[];\r\n searchType?: SearchType;\r\n dimensionFilterGroups?: DimensionFilterGroup[];\r\n aggregationType?: AggregationType;\r\n rowLimit?: number;\r\n startRow?: number;\r\n dataState?: DataState;\r\n}\r\n\r\nexport interface SearchAnalyticsRow {\r\n keys: string[];\r\n clicks: number;\r\n impressions: number;\r\n ctr: number;\r\n position: number;\r\n}\r\n\r\nexport interface SearchAnalyticsResponse {\r\n rows: SearchAnalyticsRow[];\r\n responseAggregationType: string;\r\n}\r\n\r\n// --- Inspection Types ---\r\n\r\nexport interface InspectionRequest {\r\n inspectionUrl: string;\r\n languageCode?: string;\r\n}\r\n\r\nexport type VerdictState = 'PASS' | 'PARTIAL' | 'FAIL' | 'VERDICT_UNSPECIFIED' | 'NEUTRAL';\r\nexport type CrawlState = 'SUCCESSFUL' | 'NOT_FOUND' | 'SERVER_ERROR' | 'ROBOTS_BLOCKED' | 'REDIRECT_ERROR' | 'ACCESS_DENIED' | 'ACCESS_FORBIDDEN';\r\nexport type IndexState = 'INDEXED' | 'NOT_INDEXED' | 'SUBMITTED_AND_INDEXED' | 'CRAWLED_NOT_INDEXED' | 'DISCOVERED_NOT_INDEXED' | 'PAGE_WITH_REDIRECT';\r\nexport type RobotsTxtState = 'ALLOWED' | 'DISALLOWED';\r\n\r\nexport interface IndexStatusResult {\r\n verdict: VerdictState;\r\n coverageState: IndexState;\r\n robotsTxtState: RobotsTxtState;\r\n indexingState: string;\r\n lastCrawlTime?: string;\r\n pageFetchState?: CrawlState;\r\n referringUrls?: string[];\r\n sitemap?: string[];\r\n}\r\n\r\nexport interface MobileUsabilityResult {\r\n verdict: VerdictState;\r\n issues?: Array<{ issueType: string; severity: string; message: string }>;\r\n}\r\n\r\nexport interface RichResultsResult {\r\n verdict: VerdictState;\r\n detectedItems?: Array<{ richResultType: string; items: Array<{ name: string; issues?: Array<{ issueMessage: string; severity: string }> }> }>;\r\n}\r\n\r\nexport interface InspectionResult {\r\n inspectionResultLink: string;\r\n indexStatusResult: IndexStatusResult;\r\n mobileUsabilityResult?: MobileUsabilityResult;\r\n richResultsResult?: RichResultsResult;\r\n}\r\n\r\n// --- Sitemap Types ---\r\n\r\nexport type SitemapType = 'sitemap' | 'atomFeed' | 'rssFeed' | 'notSitemap';\r\n\r\nexport interface SitemapEntry {\r\n path: string;\r\n lastSubmitted?: string;\r\n isPending: boolean;\r\n isSitemapsIndex: boolean;\r\n type: SitemapType;\r\n lastDownloaded?: string;\r\n warnings: number;\r\n errors: number;\r\n contents?: Array<{ type: string; submitted: number; indexed: number }>;\r\n}\r\n\r\nexport interface SitemapListResponse {\r\n sitemap: SitemapEntry[];\r\n}\r\n","// ============================================================================\r\n// @power-seo/search-console — Auth\r\n// ============================================================================\r\n\r\nimport type {\r\n OAuthCredentials,\r\n ServiceAccountCredentials,\r\n TokenResult,\r\n TokenManager,\r\n} from './types.js';\r\nimport { GSCApiError } from './types.js';\r\n\r\nconst TOKEN_URL = 'https://oauth2.googleapis.com/token';\r\nconst GSC_SCOPE = 'https://www.googleapis.com/auth/webmasters.readonly';\r\nconst TOKEN_BUFFER_MS = 60_000;\r\n\r\nexport async function exchangeRefreshToken(\r\n credentials: OAuthCredentials,\r\n): Promise<TokenResult> {\r\n const body = new globalThis.URLSearchParams({\r\n client_id: credentials.clientId,\r\n client_secret: credentials.clientSecret,\r\n refresh_token: credentials.refreshToken,\r\n grant_type: 'refresh_token',\r\n });\r\n\r\n const response = await globalThis.fetch(TOKEN_URL, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\r\n body: body.toString(),\r\n });\r\n\r\n if (!response.ok) {\r\n const text = await response.text();\r\n throw new GSCApiError(\r\n `Token exchange failed: ${text}`,\r\n response.status,\r\n 'TOKEN_EXCHANGE_ERROR',\r\n );\r\n }\r\n\r\n const data = (await response.json()) as { access_token: string; expires_in: number };\r\n return {\r\n accessToken: data.access_token,\r\n expiresAt: Date.now() + data.expires_in * 1000,\r\n };\r\n}\r\n\r\nexport async function getServiceAccountToken(\r\n credentials: ServiceAccountCredentials,\r\n): Promise<TokenResult> {\r\n const now = Math.floor(Date.now() / 1000);\r\n const payload = {\r\n iss: credentials.clientEmail,\r\n scope: GSC_SCOPE,\r\n aud: TOKEN_URL,\r\n exp: now + 3600,\r\n iat: now,\r\n };\r\n\r\n const assertion = await credentials.signJwt(payload);\r\n\r\n const body = new globalThis.URLSearchParams({\r\n grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',\r\n assertion,\r\n });\r\n\r\n const response = await globalThis.fetch(TOKEN_URL, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\r\n body: body.toString(),\r\n });\r\n\r\n if (!response.ok) {\r\n const text = await response.text();\r\n throw new GSCApiError(\r\n `Service account token failed: ${text}`,\r\n response.status,\r\n 'SERVICE_ACCOUNT_ERROR',\r\n );\r\n }\r\n\r\n const data = (await response.json()) as { access_token: string; expires_in: number };\r\n return {\r\n accessToken: data.access_token,\r\n expiresAt: Date.now() + data.expires_in * 1000,\r\n };\r\n}\r\n\r\nexport function createTokenManager(\r\n fetchToken: () => Promise<TokenResult>,\r\n): TokenManager {\r\n let cached: TokenResult | null = null;\r\n let pending: Promise<string> | null = null;\r\n\r\n return {\r\n async getToken(): Promise<string> {\r\n if (cached && cached.expiresAt > Date.now() + TOKEN_BUFFER_MS) {\r\n return cached.accessToken;\r\n }\r\n\r\n if (pending) {\r\n return pending;\r\n }\r\n\r\n pending = fetchToken().then((result) => {\r\n cached = result;\r\n pending = null;\r\n return result.accessToken;\r\n });\r\n\r\n return pending;\r\n },\r\n\r\n invalidate(): void {\r\n cached = null;\r\n pending = null;\r\n },\r\n };\r\n}\r\n","// ============================================================================\r\n// @power-seo/search-console — Client\r\n// ============================================================================\r\n\r\nimport type { GSCClientConfig, GSCClient, RequestOptions } from './types.js';\r\nimport { GSCApiError } from './types.js';\r\nimport { createTokenBucket, consumeToken, getWaitTime, sleep } from '@power-seo/core';\r\n\r\nconst DEFAULT_BASE_URL = 'https://searchconsole.googleapis.com/webmasters/v3';\r\nconst DEFAULT_RATE_LIMIT = 1200;\r\nconst DEFAULT_MAX_RETRIES = 3;\r\n\r\nexport function createGSCClient(config: GSCClientConfig): GSCClient {\r\n const baseUrl = config.baseUrl ?? DEFAULT_BASE_URL;\r\n const maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;\r\n const bucket = createTokenBucket(config.rateLimitPerMinute ?? DEFAULT_RATE_LIMIT);\r\n\r\n async function request<T>(path: string, options?: RequestOptions): Promise<T> {\r\n const waitTime = getWaitTime(bucket);\r\n if (waitTime > 0) {\r\n await sleep(waitTime);\r\n }\r\n consumeToken(bucket);\r\n\r\n const token = await config.auth.getToken();\r\n const url = `${baseUrl}${path}`;\r\n\r\n let lastError: Error | null = null;\r\n\r\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\r\n try {\r\n const response = await globalThis.fetch(url, {\r\n method: options?.method ?? 'GET',\r\n headers: {\r\n Authorization: `Bearer ${token}`,\r\n 'Content-Type': 'application/json',\r\n },\r\n body: options?.body ? JSON.stringify(options.body) : undefined,\r\n signal: options?.signal,\r\n });\r\n\r\n if (response.ok) {\r\n return (await response.json()) as T;\r\n }\r\n\r\n const text = await response.text();\r\n const error = new GSCApiError(\r\n `GSC API error: ${response.status} ${text}`,\r\n response.status,\r\n `HTTP_${response.status}`,\r\n );\r\n\r\n if (!error.retryable || attempt === maxRetries) {\r\n throw error;\r\n }\r\n\r\n lastError = error;\r\n const backoff = Math.min(1000 * Math.pow(2, attempt), 30_000);\r\n await sleep(backoff);\r\n } catch (err) {\r\n if (err instanceof GSCApiError) {\r\n if (!err.retryable || attempt === maxRetries) {\r\n throw err;\r\n }\r\n lastError = err;\r\n const backoff = Math.min(1000 * Math.pow(2, attempt), 30_000);\r\n await sleep(backoff);\r\n } else {\r\n throw err;\r\n }\r\n }\r\n }\r\n\r\n throw lastError ?? new GSCApiError('Request failed', 500, 'UNKNOWN');\r\n }\r\n\r\n return {\r\n request,\r\n siteUrl: config.siteUrl,\r\n };\r\n}\r\n","// ============================================================================\r\n// @power-seo/search-console — Analytics\r\n// ============================================================================\r\n\r\nimport type {\r\n GSCClient,\r\n SearchAnalyticsRequest,\r\n SearchAnalyticsResponse,\r\n SearchAnalyticsRow,\r\n} from './types.js';\r\n\r\nconst MAX_ROW_LIMIT = 25000;\r\nconst DEFAULT_ROW_LIMIT = 1000;\r\n\r\nexport async function querySearchAnalytics(\r\n client: GSCClient,\r\n request: SearchAnalyticsRequest,\r\n): Promise<SearchAnalyticsResponse> {\r\n const siteUrl = encodeURIComponent(client.siteUrl);\r\n const body = {\r\n ...request,\r\n rowLimit: Math.min(request.rowLimit ?? DEFAULT_ROW_LIMIT, MAX_ROW_LIMIT),\r\n };\r\n\r\n return client.request<SearchAnalyticsResponse>(\r\n `/sites/${siteUrl}/searchAnalytics/query`,\r\n { method: 'POST', body },\r\n );\r\n}\r\n\r\nexport async function querySearchAnalyticsAll(\r\n client: GSCClient,\r\n request: Omit<SearchAnalyticsRequest, 'startRow' | 'rowLimit'>,\r\n): Promise<SearchAnalyticsRow[]> {\r\n const allRows: SearchAnalyticsRow[] = [];\r\n let startRow = 0;\r\n\r\n while (true) {\r\n const response = await querySearchAnalytics(client, {\r\n ...request,\r\n rowLimit: MAX_ROW_LIMIT,\r\n startRow,\r\n });\r\n\r\n const rows = response.rows ?? [];\r\n allRows.push(...rows);\r\n\r\n if (rows.length < MAX_ROW_LIMIT) {\r\n break;\r\n }\r\n\r\n startRow += rows.length;\r\n }\r\n\r\n return allRows;\r\n}\r\n","// ============================================================================\r\n// @power-seo/search-console — URL Inspection\r\n// ============================================================================\r\n\r\nimport type {\r\n GSCClient,\r\n InspectionRequest,\r\n InspectionResult,\r\n} from './types.js';\r\n\r\nconst INSPECTION_BASE = 'https://searchconsole.googleapis.com/v1/urlInspection/index:inspect';\r\n\r\nexport async function inspectUrl(\r\n client: GSCClient,\r\n request: InspectionRequest,\r\n): Promise<InspectionResult> {\r\n const body = {\r\n inspectionUrl: request.inspectionUrl,\r\n siteUrl: client.siteUrl,\r\n languageCode: request.languageCode ?? 'en',\r\n };\r\n\r\n const token = await client.request<{ inspectionResult: InspectionResult }>(\r\n '',\r\n {\r\n method: 'POST',\r\n body,\r\n },\r\n );\r\n\r\n // The URL Inspection API has a different base URL, so we use a direct fetch.\r\n // However, since the client handles auth and retries, we override for the standard path approach.\r\n // The API endpoint doesn't follow the webmasters/v3 pattern.\r\n // We'll use the client's request method with a custom approach.\r\n\r\n // Actually, let's do a direct fetch since the base URL differs:\r\n return token.inspectionResult;\r\n}\r\n\r\nexport async function inspectUrlDirect(\r\n getToken: () => Promise<string>,\r\n siteUrl: string,\r\n inspectionUrl: string,\r\n languageCode?: string,\r\n): Promise<InspectionResult> {\r\n const token = await getToken();\r\n\r\n const response = await globalThis.fetch(INSPECTION_BASE, {\r\n method: 'POST',\r\n headers: {\r\n Authorization: `Bearer ${token}`,\r\n 'Content-Type': 'application/json',\r\n },\r\n body: JSON.stringify({\r\n inspectionUrl,\r\n siteUrl,\r\n languageCode: languageCode ?? 'en',\r\n }),\r\n });\r\n\r\n if (!response.ok) {\r\n const text = await response.text();\r\n throw new Error(`URL Inspection API error: ${response.status} ${text}`);\r\n }\r\n\r\n const data = (await response.json()) as { inspectionResult: InspectionResult };\r\n return data.inspectionResult;\r\n}\r\n","// ============================================================================\r\n// @power-seo/search-console — Sitemaps\r\n// ============================================================================\r\n\r\nimport type {\r\n GSCClient,\r\n SitemapEntry,\r\n SitemapListResponse,\r\n} from './types.js';\r\n\r\nexport async function listSitemaps(\r\n client: GSCClient,\r\n): Promise<SitemapEntry[]> {\r\n const siteUrl = encodeURIComponent(client.siteUrl);\r\n const response = await client.request<SitemapListResponse>(\r\n `/sites/${siteUrl}/sitemaps`,\r\n );\r\n return response.sitemap ?? [];\r\n}\r\n\r\nexport async function submitSitemap(\r\n client: GSCClient,\r\n feedpath: string,\r\n): Promise<void> {\r\n const siteUrl = encodeURIComponent(client.siteUrl);\r\n const encodedFeed = encodeURIComponent(feedpath);\r\n await client.request<void>(\r\n `/sites/${siteUrl}/sitemaps/${encodedFeed}`,\r\n { method: 'PUT' },\r\n );\r\n}\r\n\r\nexport async function deleteSitemap(\r\n client: GSCClient,\r\n feedpath: string,\r\n): Promise<void> {\r\n const siteUrl = encodeURIComponent(client.siteUrl);\r\n const encodedFeed = encodeURIComponent(feedpath);\r\n await client.request<void>(\r\n `/sites/${siteUrl}/sitemaps/${encodedFeed}`,\r\n { method: 'DELETE' },\r\n );\r\n}\r\n"]}
@@ -0,0 +1,164 @@
1
+ interface OAuthCredentials {
2
+ clientId: string;
3
+ clientSecret: string;
4
+ refreshToken: string;
5
+ }
6
+ interface ServiceAccountCredentials {
7
+ clientEmail: string;
8
+ privateKeyId: string;
9
+ signJwt: (payload: JwtPayload) => Promise<string>;
10
+ }
11
+ interface JwtPayload {
12
+ iss: string;
13
+ scope: string;
14
+ aud: string;
15
+ exp: number;
16
+ iat: number;
17
+ }
18
+ interface TokenResult {
19
+ accessToken: string;
20
+ expiresAt: number;
21
+ }
22
+ interface TokenManager {
23
+ getToken: () => Promise<string>;
24
+ invalidate: () => void;
25
+ }
26
+ interface GSCClientConfig {
27
+ auth: TokenManager;
28
+ siteUrl: string;
29
+ rateLimitPerMinute?: number;
30
+ maxRetries?: number;
31
+ baseUrl?: string;
32
+ }
33
+ interface GSCClient {
34
+ request: <T>(path: string, options?: RequestOptions) => Promise<T>;
35
+ siteUrl: string;
36
+ }
37
+ interface RequestOptions {
38
+ method?: string;
39
+ body?: unknown;
40
+ signal?: globalThis.AbortSignal;
41
+ }
42
+ declare class GSCApiError extends Error {
43
+ readonly status: number;
44
+ readonly code: string;
45
+ readonly retryable: boolean;
46
+ constructor(message: string, status: number, code: string);
47
+ }
48
+ type SearchType = 'web' | 'image' | 'video' | 'news' | 'discover' | 'googleNews';
49
+ type Dimension = 'query' | 'page' | 'country' | 'device' | 'date' | 'searchAppearance';
50
+ type AggregationType = 'auto' | 'byPage' | 'byProperty';
51
+ type DataState = 'all' | 'final';
52
+ interface DimensionFilter {
53
+ dimension: Dimension;
54
+ operator: 'equals' | 'notEquals' | 'contains' | 'notContains' | 'includingRegex' | 'excludingRegex';
55
+ expression: string;
56
+ }
57
+ interface DimensionFilterGroup {
58
+ groupType?: 'and';
59
+ filters: DimensionFilter[];
60
+ }
61
+ interface SearchAnalyticsRequest {
62
+ startDate: string;
63
+ endDate: string;
64
+ dimensions?: Dimension[];
65
+ searchType?: SearchType;
66
+ dimensionFilterGroups?: DimensionFilterGroup[];
67
+ aggregationType?: AggregationType;
68
+ rowLimit?: number;
69
+ startRow?: number;
70
+ dataState?: DataState;
71
+ }
72
+ interface SearchAnalyticsRow {
73
+ keys: string[];
74
+ clicks: number;
75
+ impressions: number;
76
+ ctr: number;
77
+ position: number;
78
+ }
79
+ interface SearchAnalyticsResponse {
80
+ rows: SearchAnalyticsRow[];
81
+ responseAggregationType: string;
82
+ }
83
+ interface InspectionRequest {
84
+ inspectionUrl: string;
85
+ languageCode?: string;
86
+ }
87
+ type VerdictState = 'PASS' | 'PARTIAL' | 'FAIL' | 'VERDICT_UNSPECIFIED' | 'NEUTRAL';
88
+ type CrawlState = 'SUCCESSFUL' | 'NOT_FOUND' | 'SERVER_ERROR' | 'ROBOTS_BLOCKED' | 'REDIRECT_ERROR' | 'ACCESS_DENIED' | 'ACCESS_FORBIDDEN';
89
+ type IndexState = 'INDEXED' | 'NOT_INDEXED' | 'SUBMITTED_AND_INDEXED' | 'CRAWLED_NOT_INDEXED' | 'DISCOVERED_NOT_INDEXED' | 'PAGE_WITH_REDIRECT';
90
+ type RobotsTxtState = 'ALLOWED' | 'DISALLOWED';
91
+ interface IndexStatusResult {
92
+ verdict: VerdictState;
93
+ coverageState: IndexState;
94
+ robotsTxtState: RobotsTxtState;
95
+ indexingState: string;
96
+ lastCrawlTime?: string;
97
+ pageFetchState?: CrawlState;
98
+ referringUrls?: string[];
99
+ sitemap?: string[];
100
+ }
101
+ interface MobileUsabilityResult {
102
+ verdict: VerdictState;
103
+ issues?: Array<{
104
+ issueType: string;
105
+ severity: string;
106
+ message: string;
107
+ }>;
108
+ }
109
+ interface RichResultsResult {
110
+ verdict: VerdictState;
111
+ detectedItems?: Array<{
112
+ richResultType: string;
113
+ items: Array<{
114
+ name: string;
115
+ issues?: Array<{
116
+ issueMessage: string;
117
+ severity: string;
118
+ }>;
119
+ }>;
120
+ }>;
121
+ }
122
+ interface InspectionResult {
123
+ inspectionResultLink: string;
124
+ indexStatusResult: IndexStatusResult;
125
+ mobileUsabilityResult?: MobileUsabilityResult;
126
+ richResultsResult?: RichResultsResult;
127
+ }
128
+ type SitemapType = 'sitemap' | 'atomFeed' | 'rssFeed' | 'notSitemap';
129
+ interface SitemapEntry {
130
+ path: string;
131
+ lastSubmitted?: string;
132
+ isPending: boolean;
133
+ isSitemapsIndex: boolean;
134
+ type: SitemapType;
135
+ lastDownloaded?: string;
136
+ warnings: number;
137
+ errors: number;
138
+ contents?: Array<{
139
+ type: string;
140
+ submitted: number;
141
+ indexed: number;
142
+ }>;
143
+ }
144
+ interface SitemapListResponse {
145
+ sitemap: SitemapEntry[];
146
+ }
147
+
148
+ declare function exchangeRefreshToken(credentials: OAuthCredentials): Promise<TokenResult>;
149
+ declare function getServiceAccountToken(credentials: ServiceAccountCredentials): Promise<TokenResult>;
150
+ declare function createTokenManager(fetchToken: () => Promise<TokenResult>): TokenManager;
151
+
152
+ declare function createGSCClient(config: GSCClientConfig): GSCClient;
153
+
154
+ declare function querySearchAnalytics(client: GSCClient, request: SearchAnalyticsRequest): Promise<SearchAnalyticsResponse>;
155
+ declare function querySearchAnalyticsAll(client: GSCClient, request: Omit<SearchAnalyticsRequest, 'startRow' | 'rowLimit'>): Promise<SearchAnalyticsRow[]>;
156
+
157
+ declare function inspectUrl(client: GSCClient, request: InspectionRequest): Promise<InspectionResult>;
158
+ declare function inspectUrlDirect(getToken: () => Promise<string>, siteUrl: string, inspectionUrl: string, languageCode?: string): Promise<InspectionResult>;
159
+
160
+ declare function listSitemaps(client: GSCClient): Promise<SitemapEntry[]>;
161
+ declare function submitSitemap(client: GSCClient, feedpath: string): Promise<void>;
162
+ declare function deleteSitemap(client: GSCClient, feedpath: string): Promise<void>;
163
+
164
+ export { type AggregationType, type CrawlState, type DataState, type Dimension, type DimensionFilter, type DimensionFilterGroup, GSCApiError, type GSCClient, type GSCClientConfig, type IndexState, type IndexStatusResult, type InspectionRequest, type InspectionResult, type JwtPayload, type MobileUsabilityResult, type OAuthCredentials, type RequestOptions, type RichResultsResult, type RobotsTxtState, type SearchAnalyticsRequest, type SearchAnalyticsResponse, type SearchAnalyticsRow, type SearchType, type ServiceAccountCredentials, type SitemapEntry, type SitemapListResponse, type SitemapType, type TokenManager, type TokenResult, type VerdictState, createGSCClient, createTokenManager, deleteSitemap, exchangeRefreshToken, getServiceAccountToken, inspectUrl, inspectUrlDirect, listSitemaps, querySearchAnalytics, querySearchAnalyticsAll, submitSitemap };
@@ -0,0 +1,164 @@
1
+ interface OAuthCredentials {
2
+ clientId: string;
3
+ clientSecret: string;
4
+ refreshToken: string;
5
+ }
6
+ interface ServiceAccountCredentials {
7
+ clientEmail: string;
8
+ privateKeyId: string;
9
+ signJwt: (payload: JwtPayload) => Promise<string>;
10
+ }
11
+ interface JwtPayload {
12
+ iss: string;
13
+ scope: string;
14
+ aud: string;
15
+ exp: number;
16
+ iat: number;
17
+ }
18
+ interface TokenResult {
19
+ accessToken: string;
20
+ expiresAt: number;
21
+ }
22
+ interface TokenManager {
23
+ getToken: () => Promise<string>;
24
+ invalidate: () => void;
25
+ }
26
+ interface GSCClientConfig {
27
+ auth: TokenManager;
28
+ siteUrl: string;
29
+ rateLimitPerMinute?: number;
30
+ maxRetries?: number;
31
+ baseUrl?: string;
32
+ }
33
+ interface GSCClient {
34
+ request: <T>(path: string, options?: RequestOptions) => Promise<T>;
35
+ siteUrl: string;
36
+ }
37
+ interface RequestOptions {
38
+ method?: string;
39
+ body?: unknown;
40
+ signal?: globalThis.AbortSignal;
41
+ }
42
+ declare class GSCApiError extends Error {
43
+ readonly status: number;
44
+ readonly code: string;
45
+ readonly retryable: boolean;
46
+ constructor(message: string, status: number, code: string);
47
+ }
48
+ type SearchType = 'web' | 'image' | 'video' | 'news' | 'discover' | 'googleNews';
49
+ type Dimension = 'query' | 'page' | 'country' | 'device' | 'date' | 'searchAppearance';
50
+ type AggregationType = 'auto' | 'byPage' | 'byProperty';
51
+ type DataState = 'all' | 'final';
52
+ interface DimensionFilter {
53
+ dimension: Dimension;
54
+ operator: 'equals' | 'notEquals' | 'contains' | 'notContains' | 'includingRegex' | 'excludingRegex';
55
+ expression: string;
56
+ }
57
+ interface DimensionFilterGroup {
58
+ groupType?: 'and';
59
+ filters: DimensionFilter[];
60
+ }
61
+ interface SearchAnalyticsRequest {
62
+ startDate: string;
63
+ endDate: string;
64
+ dimensions?: Dimension[];
65
+ searchType?: SearchType;
66
+ dimensionFilterGroups?: DimensionFilterGroup[];
67
+ aggregationType?: AggregationType;
68
+ rowLimit?: number;
69
+ startRow?: number;
70
+ dataState?: DataState;
71
+ }
72
+ interface SearchAnalyticsRow {
73
+ keys: string[];
74
+ clicks: number;
75
+ impressions: number;
76
+ ctr: number;
77
+ position: number;
78
+ }
79
+ interface SearchAnalyticsResponse {
80
+ rows: SearchAnalyticsRow[];
81
+ responseAggregationType: string;
82
+ }
83
+ interface InspectionRequest {
84
+ inspectionUrl: string;
85
+ languageCode?: string;
86
+ }
87
+ type VerdictState = 'PASS' | 'PARTIAL' | 'FAIL' | 'VERDICT_UNSPECIFIED' | 'NEUTRAL';
88
+ type CrawlState = 'SUCCESSFUL' | 'NOT_FOUND' | 'SERVER_ERROR' | 'ROBOTS_BLOCKED' | 'REDIRECT_ERROR' | 'ACCESS_DENIED' | 'ACCESS_FORBIDDEN';
89
+ type IndexState = 'INDEXED' | 'NOT_INDEXED' | 'SUBMITTED_AND_INDEXED' | 'CRAWLED_NOT_INDEXED' | 'DISCOVERED_NOT_INDEXED' | 'PAGE_WITH_REDIRECT';
90
+ type RobotsTxtState = 'ALLOWED' | 'DISALLOWED';
91
+ interface IndexStatusResult {
92
+ verdict: VerdictState;
93
+ coverageState: IndexState;
94
+ robotsTxtState: RobotsTxtState;
95
+ indexingState: string;
96
+ lastCrawlTime?: string;
97
+ pageFetchState?: CrawlState;
98
+ referringUrls?: string[];
99
+ sitemap?: string[];
100
+ }
101
+ interface MobileUsabilityResult {
102
+ verdict: VerdictState;
103
+ issues?: Array<{
104
+ issueType: string;
105
+ severity: string;
106
+ message: string;
107
+ }>;
108
+ }
109
+ interface RichResultsResult {
110
+ verdict: VerdictState;
111
+ detectedItems?: Array<{
112
+ richResultType: string;
113
+ items: Array<{
114
+ name: string;
115
+ issues?: Array<{
116
+ issueMessage: string;
117
+ severity: string;
118
+ }>;
119
+ }>;
120
+ }>;
121
+ }
122
+ interface InspectionResult {
123
+ inspectionResultLink: string;
124
+ indexStatusResult: IndexStatusResult;
125
+ mobileUsabilityResult?: MobileUsabilityResult;
126
+ richResultsResult?: RichResultsResult;
127
+ }
128
+ type SitemapType = 'sitemap' | 'atomFeed' | 'rssFeed' | 'notSitemap';
129
+ interface SitemapEntry {
130
+ path: string;
131
+ lastSubmitted?: string;
132
+ isPending: boolean;
133
+ isSitemapsIndex: boolean;
134
+ type: SitemapType;
135
+ lastDownloaded?: string;
136
+ warnings: number;
137
+ errors: number;
138
+ contents?: Array<{
139
+ type: string;
140
+ submitted: number;
141
+ indexed: number;
142
+ }>;
143
+ }
144
+ interface SitemapListResponse {
145
+ sitemap: SitemapEntry[];
146
+ }
147
+
148
+ declare function exchangeRefreshToken(credentials: OAuthCredentials): Promise<TokenResult>;
149
+ declare function getServiceAccountToken(credentials: ServiceAccountCredentials): Promise<TokenResult>;
150
+ declare function createTokenManager(fetchToken: () => Promise<TokenResult>): TokenManager;
151
+
152
+ declare function createGSCClient(config: GSCClientConfig): GSCClient;
153
+
154
+ declare function querySearchAnalytics(client: GSCClient, request: SearchAnalyticsRequest): Promise<SearchAnalyticsResponse>;
155
+ declare function querySearchAnalyticsAll(client: GSCClient, request: Omit<SearchAnalyticsRequest, 'startRow' | 'rowLimit'>): Promise<SearchAnalyticsRow[]>;
156
+
157
+ declare function inspectUrl(client: GSCClient, request: InspectionRequest): Promise<InspectionResult>;
158
+ declare function inspectUrlDirect(getToken: () => Promise<string>, siteUrl: string, inspectionUrl: string, languageCode?: string): Promise<InspectionResult>;
159
+
160
+ declare function listSitemaps(client: GSCClient): Promise<SitemapEntry[]>;
161
+ declare function submitSitemap(client: GSCClient, feedpath: string): Promise<void>;
162
+ declare function deleteSitemap(client: GSCClient, feedpath: string): Promise<void>;
163
+
164
+ export { type AggregationType, type CrawlState, type DataState, type Dimension, type DimensionFilter, type DimensionFilterGroup, GSCApiError, type GSCClient, type GSCClientConfig, type IndexState, type IndexStatusResult, type InspectionRequest, type InspectionResult, type JwtPayload, type MobileUsabilityResult, type OAuthCredentials, type RequestOptions, type RichResultsResult, type RobotsTxtState, type SearchAnalyticsRequest, type SearchAnalyticsResponse, type SearchAnalyticsRow, type SearchType, type ServiceAccountCredentials, type SitemapEntry, type SitemapListResponse, type SitemapType, type TokenManager, type TokenResult, type VerdictState, createGSCClient, createTokenManager, deleteSitemap, exchangeRefreshToken, getServiceAccountToken, inspectUrl, inspectUrlDirect, listSitemaps, querySearchAnalytics, querySearchAnalyticsAll, submitSitemap };
package/dist/index.js ADDED
@@ -0,0 +1,266 @@
1
+ import { createTokenBucket, getWaitTime, sleep, consumeToken } from '@power-seo/core';
2
+
3
+ // src/types.ts
4
+ var GSCApiError = class extends Error {
5
+ status;
6
+ code;
7
+ retryable;
8
+ constructor(message, status, code) {
9
+ super(message);
10
+ this.name = "GSCApiError";
11
+ this.status = status;
12
+ this.code = code;
13
+ this.retryable = status === 429 || status >= 500;
14
+ }
15
+ };
16
+
17
+ // src/auth.ts
18
+ var TOKEN_URL = "https://oauth2.googleapis.com/token";
19
+ var GSC_SCOPE = "https://www.googleapis.com/auth/webmasters.readonly";
20
+ var TOKEN_BUFFER_MS = 6e4;
21
+ async function exchangeRefreshToken(credentials) {
22
+ const body = new globalThis.URLSearchParams({
23
+ client_id: credentials.clientId,
24
+ client_secret: credentials.clientSecret,
25
+ refresh_token: credentials.refreshToken,
26
+ grant_type: "refresh_token"
27
+ });
28
+ const response = await globalThis.fetch(TOKEN_URL, {
29
+ method: "POST",
30
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
31
+ body: body.toString()
32
+ });
33
+ if (!response.ok) {
34
+ const text = await response.text();
35
+ throw new GSCApiError(
36
+ `Token exchange failed: ${text}`,
37
+ response.status,
38
+ "TOKEN_EXCHANGE_ERROR"
39
+ );
40
+ }
41
+ const data = await response.json();
42
+ return {
43
+ accessToken: data.access_token,
44
+ expiresAt: Date.now() + data.expires_in * 1e3
45
+ };
46
+ }
47
+ async function getServiceAccountToken(credentials) {
48
+ const now = Math.floor(Date.now() / 1e3);
49
+ const payload = {
50
+ iss: credentials.clientEmail,
51
+ scope: GSC_SCOPE,
52
+ aud: TOKEN_URL,
53
+ exp: now + 3600,
54
+ iat: now
55
+ };
56
+ const assertion = await credentials.signJwt(payload);
57
+ const body = new globalThis.URLSearchParams({
58
+ grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
59
+ assertion
60
+ });
61
+ const response = await globalThis.fetch(TOKEN_URL, {
62
+ method: "POST",
63
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
64
+ body: body.toString()
65
+ });
66
+ if (!response.ok) {
67
+ const text = await response.text();
68
+ throw new GSCApiError(
69
+ `Service account token failed: ${text}`,
70
+ response.status,
71
+ "SERVICE_ACCOUNT_ERROR"
72
+ );
73
+ }
74
+ const data = await response.json();
75
+ return {
76
+ accessToken: data.access_token,
77
+ expiresAt: Date.now() + data.expires_in * 1e3
78
+ };
79
+ }
80
+ function createTokenManager(fetchToken) {
81
+ let cached = null;
82
+ let pending = null;
83
+ return {
84
+ async getToken() {
85
+ if (cached && cached.expiresAt > Date.now() + TOKEN_BUFFER_MS) {
86
+ return cached.accessToken;
87
+ }
88
+ if (pending) {
89
+ return pending;
90
+ }
91
+ pending = fetchToken().then((result) => {
92
+ cached = result;
93
+ pending = null;
94
+ return result.accessToken;
95
+ });
96
+ return pending;
97
+ },
98
+ invalidate() {
99
+ cached = null;
100
+ pending = null;
101
+ }
102
+ };
103
+ }
104
+ var DEFAULT_BASE_URL = "https://searchconsole.googleapis.com/webmasters/v3";
105
+ var DEFAULT_RATE_LIMIT = 1200;
106
+ var DEFAULT_MAX_RETRIES = 3;
107
+ function createGSCClient(config) {
108
+ const baseUrl = config.baseUrl ?? DEFAULT_BASE_URL;
109
+ const maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;
110
+ const bucket = createTokenBucket(config.rateLimitPerMinute ?? DEFAULT_RATE_LIMIT);
111
+ async function request(path, options) {
112
+ const waitTime = getWaitTime(bucket);
113
+ if (waitTime > 0) {
114
+ await sleep(waitTime);
115
+ }
116
+ consumeToken(bucket);
117
+ const token = await config.auth.getToken();
118
+ const url = `${baseUrl}${path}`;
119
+ let lastError = null;
120
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
121
+ try {
122
+ const response = await globalThis.fetch(url, {
123
+ method: options?.method ?? "GET",
124
+ headers: {
125
+ Authorization: `Bearer ${token}`,
126
+ "Content-Type": "application/json"
127
+ },
128
+ body: options?.body ? JSON.stringify(options.body) : void 0,
129
+ signal: options?.signal
130
+ });
131
+ if (response.ok) {
132
+ return await response.json();
133
+ }
134
+ const text = await response.text();
135
+ const error = new GSCApiError(
136
+ `GSC API error: ${response.status} ${text}`,
137
+ response.status,
138
+ `HTTP_${response.status}`
139
+ );
140
+ if (!error.retryable || attempt === maxRetries) {
141
+ throw error;
142
+ }
143
+ lastError = error;
144
+ const backoff = Math.min(1e3 * Math.pow(2, attempt), 3e4);
145
+ await sleep(backoff);
146
+ } catch (err) {
147
+ if (err instanceof GSCApiError) {
148
+ if (!err.retryable || attempt === maxRetries) {
149
+ throw err;
150
+ }
151
+ lastError = err;
152
+ const backoff = Math.min(1e3 * Math.pow(2, attempt), 3e4);
153
+ await sleep(backoff);
154
+ } else {
155
+ throw err;
156
+ }
157
+ }
158
+ }
159
+ throw lastError ?? new GSCApiError("Request failed", 500, "UNKNOWN");
160
+ }
161
+ return {
162
+ request,
163
+ siteUrl: config.siteUrl
164
+ };
165
+ }
166
+
167
+ // src/analytics.ts
168
+ var MAX_ROW_LIMIT = 25e3;
169
+ var DEFAULT_ROW_LIMIT = 1e3;
170
+ async function querySearchAnalytics(client, request) {
171
+ const siteUrl = encodeURIComponent(client.siteUrl);
172
+ const body = {
173
+ ...request,
174
+ rowLimit: Math.min(request.rowLimit ?? DEFAULT_ROW_LIMIT, MAX_ROW_LIMIT)
175
+ };
176
+ return client.request(
177
+ `/sites/${siteUrl}/searchAnalytics/query`,
178
+ { method: "POST", body }
179
+ );
180
+ }
181
+ async function querySearchAnalyticsAll(client, request) {
182
+ const allRows = [];
183
+ let startRow = 0;
184
+ while (true) {
185
+ const response = await querySearchAnalytics(client, {
186
+ ...request,
187
+ rowLimit: MAX_ROW_LIMIT,
188
+ startRow
189
+ });
190
+ const rows = response.rows ?? [];
191
+ allRows.push(...rows);
192
+ if (rows.length < MAX_ROW_LIMIT) {
193
+ break;
194
+ }
195
+ startRow += rows.length;
196
+ }
197
+ return allRows;
198
+ }
199
+
200
+ // src/inspection.ts
201
+ var INSPECTION_BASE = "https://searchconsole.googleapis.com/v1/urlInspection/index:inspect";
202
+ async function inspectUrl(client, request) {
203
+ const body = {
204
+ inspectionUrl: request.inspectionUrl,
205
+ siteUrl: client.siteUrl,
206
+ languageCode: request.languageCode ?? "en"
207
+ };
208
+ const token = await client.request(
209
+ "",
210
+ {
211
+ method: "POST",
212
+ body
213
+ }
214
+ );
215
+ return token.inspectionResult;
216
+ }
217
+ async function inspectUrlDirect(getToken, siteUrl, inspectionUrl, languageCode) {
218
+ const token = await getToken();
219
+ const response = await globalThis.fetch(INSPECTION_BASE, {
220
+ method: "POST",
221
+ headers: {
222
+ Authorization: `Bearer ${token}`,
223
+ "Content-Type": "application/json"
224
+ },
225
+ body: JSON.stringify({
226
+ inspectionUrl,
227
+ siteUrl,
228
+ languageCode: languageCode ?? "en"
229
+ })
230
+ });
231
+ if (!response.ok) {
232
+ const text = await response.text();
233
+ throw new Error(`URL Inspection API error: ${response.status} ${text}`);
234
+ }
235
+ const data = await response.json();
236
+ return data.inspectionResult;
237
+ }
238
+
239
+ // src/sitemaps.ts
240
+ async function listSitemaps(client) {
241
+ const siteUrl = encodeURIComponent(client.siteUrl);
242
+ const response = await client.request(
243
+ `/sites/${siteUrl}/sitemaps`
244
+ );
245
+ return response.sitemap ?? [];
246
+ }
247
+ async function submitSitemap(client, feedpath) {
248
+ const siteUrl = encodeURIComponent(client.siteUrl);
249
+ const encodedFeed = encodeURIComponent(feedpath);
250
+ await client.request(
251
+ `/sites/${siteUrl}/sitemaps/${encodedFeed}`,
252
+ { method: "PUT" }
253
+ );
254
+ }
255
+ async function deleteSitemap(client, feedpath) {
256
+ const siteUrl = encodeURIComponent(client.siteUrl);
257
+ const encodedFeed = encodeURIComponent(feedpath);
258
+ await client.request(
259
+ `/sites/${siteUrl}/sitemaps/${encodedFeed}`,
260
+ { method: "DELETE" }
261
+ );
262
+ }
263
+
264
+ export { GSCApiError, createGSCClient, createTokenManager, deleteSitemap, exchangeRefreshToken, getServiceAccountToken, inspectUrl, inspectUrlDirect, listSitemaps, querySearchAnalytics, querySearchAnalyticsAll, submitSitemap };
265
+ //# sourceMappingURL=index.js.map
266
+ //# sourceMappingURL=index.js.map