@power-seo/search-console 1.0.0 → 1.0.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.
- package/README.md +405 -377
- package/dist/index.cjs +11 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +11 -22
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,377 +1,405 @@
|
|
|
1
|
-
# @power-seo/search-console — Google Search Console API Client for TypeScript — OAuth2, Service Account, URL Inspection & Auto-Paginated Analytics
|
|
2
|
-
|
|
3
|
-
[](https://www.npmjs.com/package/@power-seo/search-console)
|
|
4
|
-
[](https://www.npmjs.com/package/@power-seo/search-console)
|
|
5
|
-
[](../../LICENSE)
|
|
6
|
-
[](https://www.typescriptlang.org/)
|
|
7
|
-
[](#)
|
|
8
|
-
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
## Overview
|
|
12
|
-
|
|
13
|
-
**@power-seo/search-console** is a production-ready Google Search Console API client for TypeScript that helps you query search analytics, inspect URLs, and manage sitemaps — without writing OAuth2 token refresh boilerplate or pagination loops.
|
|
14
|
-
|
|
15
|
-
**What it does**
|
|
16
|
-
|
|
17
|
-
- ✅ **
|
|
18
|
-
- ✅ **
|
|
19
|
-
- ✅ **
|
|
20
|
-
- ✅ **
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
**
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
**
|
|
39
|
-
- **
|
|
40
|
-
- **
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
- **
|
|
53
|
-
- **
|
|
54
|
-
- **
|
|
55
|
-
- **
|
|
56
|
-
- **
|
|
57
|
-
- **
|
|
58
|
-
- **
|
|
59
|
-
- **
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
- **
|
|
146
|
-
- **
|
|
147
|
-
- **
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
-
|
|
157
|
-
-
|
|
158
|
-
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
-
|
|
174
|
-
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
- **
|
|
183
|
-
- **
|
|
184
|
-
|
|
185
|
-
**
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
|
211
|
-
|
|
212
|
-
|
|
|
213
|
-
|
|
|
214
|
-
|
|
|
215
|
-
|
|
|
216
|
-
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
|
225
|
-
|
|
|
226
|
-
| [`@power-seo/
|
|
227
|
-
| [`@power-seo/
|
|
228
|
-
| [`@power-seo/
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
|
233
|
-
|
|
234
|
-
|
|
|
235
|
-
|
|
|
236
|
-
|
|
|
237
|
-
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
**
|
|
264
|
-
|
|
265
|
-
-
|
|
266
|
-
-
|
|
267
|
-
-
|
|
268
|
-
|
|
269
|
-
**
|
|
270
|
-
|
|
271
|
-
-
|
|
272
|
-
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
|
304
|
-
|
|
305
|
-
| `
|
|
306
|
-
| `
|
|
307
|
-
| `
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
Returns `
|
|
319
|
-
|
|
320
|
-
### `
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
1
|
+
# @power-seo/search-console — Google Search Console API Client for TypeScript — OAuth2, Service Account, URL Inspection & Auto-Paginated Analytics
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@power-seo/search-console)
|
|
4
|
+
[](https://www.npmjs.com/package/@power-seo/search-console)
|
|
5
|
+
[](../../LICENSE)
|
|
6
|
+
[](https://www.typescriptlang.org/)
|
|
7
|
+
[](#)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Overview
|
|
12
|
+
|
|
13
|
+
**@power-seo/search-console** is a production-ready Google Search Console API client for TypeScript that helps you query search analytics, inspect URLs, and manage sitemaps — without writing OAuth2 token refresh boilerplate or pagination loops.
|
|
14
|
+
|
|
15
|
+
**What it does**
|
|
16
|
+
|
|
17
|
+
- ✅ **OAuth2 and service account auth** — `createTokenManager()` handles token refresh cycles and JWT signing automatically
|
|
18
|
+
- ✅ **Search analytics queries** — clicks, impressions, CTR, and position by query, page, country, device, date
|
|
19
|
+
- ✅ **Auto-paginated full fetch** — `querySearchAnalyticsAll()` transparently pages through the 25,000-row GSC API limit
|
|
20
|
+
- ✅ **URL inspection** — indexing verdict, last crawl time, canonical URL, mobile usability, rich result status
|
|
21
|
+
- ✅ **Sitemap management** — list, submit, and delete sitemaps from verified GSC properties
|
|
22
|
+
|
|
23
|
+
**What it is not**
|
|
24
|
+
|
|
25
|
+
- ❌ **Not a reporting dashboard** — returns raw data; use `@power-seo/analytics` to merge and visualize
|
|
26
|
+
- ❌ **Not a site verification tool** — requires a property already verified in Google Search Console
|
|
27
|
+
|
|
28
|
+
**Recommended for**
|
|
29
|
+
|
|
30
|
+
- **SEO automation pipelines**, **CI/CD keyword tracking**, **SaaS analytics dashboards**, and **reporting scripts** using GSC data
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Why @power-seo/search-console Matters
|
|
35
|
+
|
|
36
|
+
**The problem**
|
|
37
|
+
|
|
38
|
+
- **OAuth2 token refresh is complex** — access tokens expire every hour; service account JWT signing requires crypto operations
|
|
39
|
+
- **GSC returns only 25,000 rows per request** — sites with many queries need pagination loops that every team reimplements
|
|
40
|
+
- **URL inspection data is inaccessible without auth** — no simple way to check indexing status programmatically
|
|
41
|
+
|
|
42
|
+
**Why developers care**
|
|
43
|
+
|
|
44
|
+
- **SEO:** Programmatic access to real click/impression data enables data-driven content decisions
|
|
45
|
+
- **Performance:** Auto-paginated `querySearchAnalyticsAll` eliminates boilerplate that slows down analytics pipelines
|
|
46
|
+
- **UX:** URL inspection in CI enables instant indexing health checks after deployments
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Key Features
|
|
51
|
+
|
|
52
|
+
- **OAuth2 authentication** — `createTokenManager({ type: 'oauth' })` handles token refresh automatically
|
|
53
|
+
- **Service account JWT authentication** — `createTokenManager({ type: 'service-account' })` signs JWTs for server-to-server access
|
|
54
|
+
- **Low-level auth primitives** — `exchangeRefreshToken()` and `getServiceAccountToken()` for custom auth flows
|
|
55
|
+
- **Typed GSC client** — `createGSCClient(config)` returns a `GSCClient` scoped to a specific site URL
|
|
56
|
+
- **Search analytics** — `querySearchAnalytics()` supports all 6 dimensions: `query`, `page`, `country`, `device`, `date`, `searchAppearance`
|
|
57
|
+
- **All search types** — `web`, `image`, `video`, and `news` search types
|
|
58
|
+
- **Auto-paginated full fetch** — `querySearchAnalyticsAll()` merges all pages into a single `SearchAnalyticsRow[]` array
|
|
59
|
+
- **URL inspection** — `inspectUrl()` returns verdict, indexing state, last crawl time, mobile usability, and rich result status
|
|
60
|
+
- **Direct URL inspection** — `inspectUrlDirect()` for the direct URL inspection endpoint
|
|
61
|
+
- **Sitemap listing** — `listSitemaps()` with status, last download time, and error counts
|
|
62
|
+
- **Sitemap submission and deletion** — `submitSitemap()` and `deleteSitemap()`
|
|
63
|
+
- **Typed error handling** — `GSCApiError` class with `status`, `code`, and `message`
|
|
64
|
+
- **Type-safe throughout** — full TypeScript types for all request and response shapes
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Benefits of Using @power-seo/search-console
|
|
69
|
+
|
|
70
|
+
- **Faster analytics pipelines**: No token refresh boilerplate; token manager handles expiry transparently
|
|
71
|
+
- **Complete datasets**: `querySearchAnalyticsAll` fetches all rows regardless of API page limit
|
|
72
|
+
- **Safer integration**: `GSCApiError` structured error class enables reliable error handling in production
|
|
73
|
+
- **Faster delivery**: Connect to GSC in minutes; no OAuth2 library research required
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Quick Start
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
import {
|
|
81
|
+
createTokenManager,
|
|
82
|
+
createGSCClient,
|
|
83
|
+
querySearchAnalyticsAll,
|
|
84
|
+
} from '@power-seo/search-console';
|
|
85
|
+
|
|
86
|
+
// 1. Create a token manager (OAuth2)
|
|
87
|
+
const tokenManager = createTokenManager({
|
|
88
|
+
type: 'oauth',
|
|
89
|
+
clientId: process.env.GSC_CLIENT_ID!,
|
|
90
|
+
clientSecret: process.env.GSC_CLIENT_SECRET!,
|
|
91
|
+
refreshToken: process.env.GSC_REFRESH_TOKEN!,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// 2. Create a client scoped to your site
|
|
95
|
+
const client = createGSCClient({
|
|
96
|
+
siteUrl: 'https://example.com',
|
|
97
|
+
tokenManager,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// 3. Fetch all search analytics data (auto-paginated)
|
|
101
|
+
const rows = await querySearchAnalyticsAll(client, {
|
|
102
|
+
startDate: '2026-01-01',
|
|
103
|
+
endDate: '2026-01-31',
|
|
104
|
+
dimensions: ['query', 'page'],
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
rows.forEach(({ keys, clicks, impressions, ctr, position }) => {
|
|
108
|
+
console.log(`Query: "${keys[0]}", Page: ${keys[1]}`);
|
|
109
|
+
console.log(` ${clicks} clicks, ${impressions} impressions, pos ${position.toFixed(1)}`);
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**What you should see**
|
|
114
|
+
|
|
115
|
+
- A merged `SearchAnalyticsRow[]` array with all query-page combinations for the date range
|
|
116
|
+
- `clicks`, `impressions`, `ctr`, and `position` for each row
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Installation
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
npm i @power-seo/search-console
|
|
124
|
+
# or
|
|
125
|
+
yarn add @power-seo/search-console
|
|
126
|
+
# or
|
|
127
|
+
pnpm add @power-seo/search-console
|
|
128
|
+
# or
|
|
129
|
+
bun add @power-seo/search-console
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Framework Compatibility
|
|
135
|
+
|
|
136
|
+
**Supported**
|
|
137
|
+
|
|
138
|
+
- ✅ Next.js (App Router / Pages Router) — use in API routes or server actions
|
|
139
|
+
- ✅ Remix — use in loader functions and server-side actions
|
|
140
|
+
- ✅ Node.js 18+ — pure TypeScript client with `fetch` API
|
|
141
|
+
- ✅ CI/CD pipelines — run as standalone Node scripts in GitHub Actions or similar
|
|
142
|
+
|
|
143
|
+
**Environment notes**
|
|
144
|
+
|
|
145
|
+
- **SSR/SSG:** Fully supported — all operations are server-side
|
|
146
|
+
- **Edge runtime:** Not supported (requires crypto for JWT signing)
|
|
147
|
+
- **Browser-only usage:** Not supported — exposes credentials; server-side only
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Use Cases
|
|
152
|
+
|
|
153
|
+
- **Automated keyword ranking reports** — fetch all queries weekly and diff against previous week
|
|
154
|
+
- **Indexing health monitoring** — `inspectUrl` after deployments to verify new pages are indexed
|
|
155
|
+
- **Content gap analysis** — merge GSC data with `@power-seo/analytics` to find high-impression, low-click pages
|
|
156
|
+
- **Sitemap automation** — submit new sitemaps programmatically after content migrations
|
|
157
|
+
- **CI/CD SEO checks** — fail pipelines when key pages drop below position threshold
|
|
158
|
+
- **Multi-site SaaS dashboards** — aggregate GSC data across multiple client properties
|
|
159
|
+
- **Image and news search analytics** — query `image` and `news` search types separately
|
|
160
|
+
- **Country and device breakdowns** — segment click data by country or device for regional SEO
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Example (Before / After)
|
|
165
|
+
|
|
166
|
+
```text
|
|
167
|
+
Before:
|
|
168
|
+
- Manual OAuth2 token refresh: 50+ lines of token exchange code per project
|
|
169
|
+
- Pagination loop: separate rowOffset counter, multiple fetch calls, manual array merging
|
|
170
|
+
- URL inspection: no programmatic access → check Google Search Console UI manually
|
|
171
|
+
|
|
172
|
+
After (@power-seo/search-console):
|
|
173
|
+
- createTokenManager({ type: 'oauth', ... }) → tokens auto-refresh before every request
|
|
174
|
+
- querySearchAnalyticsAll(client, { dimensions: ['query'] }) → one call, complete dataset
|
|
175
|
+
- inspectUrl(client, url) → { verdict: 'PASS', lastCrawlTime: '...', mobileUsabilityResult: {...} }
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Implementation Best Practices
|
|
181
|
+
|
|
182
|
+
- **Use service accounts for CI/CD** — service accounts don't expire and don't require user interaction
|
|
183
|
+
- **Cache `querySearchAnalyticsAll` results** — GSC data is delayed by ~2 days; daily fetches are sufficient
|
|
184
|
+
- **Use `sc-domain:` prefix for domain properties** — e.g. `sc-domain:example.com` for domain-level verification
|
|
185
|
+
- **Add service account email as GSC user** — Settings → Users and permissions → Add user
|
|
186
|
+
- **Handle `GSCApiError` status 429** — implement exponential backoff for rate-limited requests
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Architecture Overview
|
|
191
|
+
|
|
192
|
+
**Where it runs**
|
|
193
|
+
|
|
194
|
+
- **Build-time**: Fetch GSC data for static site revalidation or build-time content ranking
|
|
195
|
+
- **Runtime**: Query analytics in serverless functions for real-time dashboard data
|
|
196
|
+
- **CI/CD**: Check indexing status and keyword positions in automated pipelines
|
|
197
|
+
|
|
198
|
+
**Data flow**
|
|
199
|
+
|
|
200
|
+
1. **Input**: OAuth2 credentials or service account key + site URL + query parameters
|
|
201
|
+
2. **Analysis**: Token manager handles auth; client sends typed requests to GSC API
|
|
202
|
+
3. **Output**: Typed `SearchAnalyticsRow[]`, `InspectionResult`, `SitemapListResponse`
|
|
203
|
+
4. **Action**: Feed into `@power-seo/analytics` for dashboards, or export as CSV/JSON reports
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## Features Comparison with Popular Packages
|
|
208
|
+
|
|
209
|
+
| Capability | google-auth-library | googleapis | custom fetch | @power-seo/search-console |
|
|
210
|
+
| ---------------------------------- | ------------------: | ---------: | -----------: | ------------------------: |
|
|
211
|
+
| OAuth2 token auto-refresh | ✅ | ✅ | ❌ | ✅ |
|
|
212
|
+
| Service account JWT signing | ✅ | ✅ | ❌ | ✅ |
|
|
213
|
+
| Auto-paginated analytics fetch | ❌ | ❌ | ❌ | ✅ |
|
|
214
|
+
| Typed GSC-specific response shapes | ❌ | ⚠️ | ❌ | ✅ |
|
|
215
|
+
| URL inspection support | ❌ | ⚠️ | ❌ | ✅ |
|
|
216
|
+
| Sitemap management | ❌ | ⚠️ | ❌ | ✅ |
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## @power-seo Ecosystem
|
|
221
|
+
|
|
222
|
+
All 17 packages are independently installable — use only what you need.
|
|
223
|
+
|
|
224
|
+
| Package | Install | Description |
|
|
225
|
+
| ------------------------------------------------------------------------------------------ | ----------------------------------- | ----------------------------------------------------------------------- |
|
|
226
|
+
| [`@power-seo/core`](https://www.npmjs.com/package/@power-seo/core) | `npm i @power-seo/core` | Framework-agnostic utilities, types, validators, and constants |
|
|
227
|
+
| [`@power-seo/react`](https://www.npmjs.com/package/@power-seo/react) | `npm i @power-seo/react` | React SEO components — meta, Open Graph, Twitter Card, breadcrumbs |
|
|
228
|
+
| [`@power-seo/meta`](https://www.npmjs.com/package/@power-seo/meta) | `npm i @power-seo/meta` | SSR meta helpers for Next.js App Router, Remix v2, and generic SSR |
|
|
229
|
+
| [`@power-seo/schema`](https://www.npmjs.com/package/@power-seo/schema) | `npm i @power-seo/schema` | Type-safe JSON-LD structured data — 20 builders + 18 React components |
|
|
230
|
+
| [`@power-seo/content-analysis`](https://www.npmjs.com/package/@power-seo/content-analysis) | `npm i @power-seo/content-analysis` | Yoast-style SEO content scoring engine with React components |
|
|
231
|
+
| [`@power-seo/readability`](https://www.npmjs.com/package/@power-seo/readability) | `npm i @power-seo/readability` | Readability scoring — Flesch-Kincaid, Gunning Fog, Coleman-Liau, ARI |
|
|
232
|
+
| [`@power-seo/preview`](https://www.npmjs.com/package/@power-seo/preview) | `npm i @power-seo/preview` | SERP, Open Graph, and Twitter/X Card preview generators |
|
|
233
|
+
| [`@power-seo/sitemap`](https://www.npmjs.com/package/@power-seo/sitemap) | `npm i @power-seo/sitemap` | XML sitemap generation, streaming, index splitting, and validation |
|
|
234
|
+
| [`@power-seo/redirects`](https://www.npmjs.com/package/@power-seo/redirects) | `npm i @power-seo/redirects` | Redirect engine with Next.js, Remix, and Express adapters |
|
|
235
|
+
| [`@power-seo/links`](https://www.npmjs.com/package/@power-seo/links) | `npm i @power-seo/links` | Link graph analysis — orphan detection, suggestions, equity scoring |
|
|
236
|
+
| [`@power-seo/audit`](https://www.npmjs.com/package/@power-seo/audit) | `npm i @power-seo/audit` | Full SEO audit engine — meta, content, structure, performance rules |
|
|
237
|
+
| [`@power-seo/images`](https://www.npmjs.com/package/@power-seo/images) | `npm i @power-seo/images` | Image SEO — alt text, lazy loading, format analysis, image sitemaps |
|
|
238
|
+
| [`@power-seo/ai`](https://www.npmjs.com/package/@power-seo/ai) | `npm i @power-seo/ai` | LLM-agnostic AI prompt templates and parsers for SEO tasks |
|
|
239
|
+
| [`@power-seo/analytics`](https://www.npmjs.com/package/@power-seo/analytics) | `npm i @power-seo/analytics` | Merge GSC + audit data, trend analysis, ranking insights, dashboard |
|
|
240
|
+
| [`@power-seo/search-console`](https://www.npmjs.com/package/@power-seo/search-console) | `npm i @power-seo/search-console` | Google Search Console API — OAuth2, service account, URL inspection |
|
|
241
|
+
| [`@power-seo/integrations`](https://www.npmjs.com/package/@power-seo/integrations) | `npm i @power-seo/integrations` | Semrush and Ahrefs API clients with rate limiting and pagination |
|
|
242
|
+
| [`@power-seo/tracking`](https://www.npmjs.com/package/@power-seo/tracking) | `npm i @power-seo/tracking` | GA4, Clarity, PostHog, Plausible, Fathom — scripts + consent management |
|
|
243
|
+
|
|
244
|
+
### Ecosystem vs alternatives
|
|
245
|
+
|
|
246
|
+
| Need | Common approach | @power-seo approach |
|
|
247
|
+
| -------------------- | ---------------------- | --------------------------------------------------- |
|
|
248
|
+
| GSC search analytics | `googleapis` (verbose) | `@power-seo/search-console` — typed, auto-paginated |
|
|
249
|
+
| Analytics dashboards | Google Looker Studio | `@power-seo/analytics` — merge GSC + audit data |
|
|
250
|
+
| Sitemap submission | Manual GSC UI | `@power-seo/search-console` — `submitSitemap()` |
|
|
251
|
+
| SEO auditing | Third-party tools | `@power-seo/audit` — in-code, CI-friendly |
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## Enterprise Integration
|
|
256
|
+
|
|
257
|
+
**Multi-tenant SaaS**
|
|
258
|
+
|
|
259
|
+
- **Per-client GSC properties**: Instantiate one `GSCClient` per client site; use service accounts for automation
|
|
260
|
+
- **Aggregated dashboards**: Loop over multiple `siteUrl` values and merge analytics into a multi-site report
|
|
261
|
+
- **Scheduled data sync**: Run `querySearchAnalyticsAll` on a daily cron for all client properties
|
|
262
|
+
|
|
263
|
+
**ERP / internal portals**
|
|
264
|
+
|
|
265
|
+
- Use URL inspection to verify that portal public pages are correctly indexed
|
|
266
|
+
- Track keyword positions for internal knowledge base articles
|
|
267
|
+
- Automate sitemap submission after content management system publishes
|
|
268
|
+
|
|
269
|
+
**Recommended integration pattern**
|
|
270
|
+
|
|
271
|
+
- Use **service accounts** in CI/CD — no user interaction, no token expiry
|
|
272
|
+
- Run `querySearchAnalyticsAll` in **weekly scheduled jobs**
|
|
273
|
+
- Feed data into `@power-seo/analytics` for **trend analysis** and **ranking insights**
|
|
274
|
+
- Export results as **JSON** to Jira/Notion/Slack for content team review
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## Scope and Limitations
|
|
279
|
+
|
|
280
|
+
**This package does**
|
|
281
|
+
|
|
282
|
+
- ✅ Authenticate with GSC API via OAuth2 and service account
|
|
283
|
+
- ✅ Fetch search analytics data with auto-pagination
|
|
284
|
+
- ✅ Inspect URL indexing status, mobile usability, and rich result status
|
|
285
|
+
- ✅ Submit, list, and delete sitemaps from verified GSC properties
|
|
286
|
+
|
|
287
|
+
**This package does not**
|
|
288
|
+
|
|
289
|
+
- ❌ Verify properties in GSC — property must already be verified
|
|
290
|
+
- ❌ Provide reporting UI — use `@power-seo/analytics` for dashboards
|
|
291
|
+
- ❌ Access Google Analytics data — use `@power-seo/tracking` for GA4 API access
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
## API Reference
|
|
296
|
+
|
|
297
|
+
### `createTokenManager(config)`
|
|
298
|
+
|
|
299
|
+
| Parameter | Type | Description |
|
|
300
|
+
| --------------------- | ------------------------------ | ------------------------------------------------------ |
|
|
301
|
+
| `config.type` | `'oauth' \| 'service-account'` | Authentication method |
|
|
302
|
+
| `config.clientId` | `string` | Google OAuth2 client ID (OAuth2 only) |
|
|
303
|
+
| `config.clientSecret` | `string` | Google OAuth2 client secret (OAuth2 only) |
|
|
304
|
+
| `config.refreshToken` | `string` | OAuth2 refresh token (OAuth2 only) |
|
|
305
|
+
| `config.clientEmail` | `string` | Service account email (service account only) |
|
|
306
|
+
| `config.privateKey` | `string` | Service account private key PEM (service account only) |
|
|
307
|
+
| `config.scopes` | `string[]` | OAuth2 scopes to request (service account only) |
|
|
308
|
+
|
|
309
|
+
Returns `TokenManager`: `{ getAccessToken(): Promise<TokenResult> }`.
|
|
310
|
+
|
|
311
|
+
### `createGSCClient(config)`
|
|
312
|
+
|
|
313
|
+
| Parameter | Type | Description |
|
|
314
|
+
| --------------------- | -------------- | --------------------------------------------------------------------- |
|
|
315
|
+
| `config.siteUrl` | `string` | Verified GSC property URL (`sc-domain:` prefix for domain properties) |
|
|
316
|
+
| `config.tokenManager` | `TokenManager` | Token manager from `createTokenManager` |
|
|
317
|
+
|
|
318
|
+
Returns `GSCClient`.
|
|
319
|
+
|
|
320
|
+
### `querySearchAnalytics(client, request)`
|
|
321
|
+
|
|
322
|
+
| Parameter | Type | Default | Description |
|
|
323
|
+
| ------------------------------- | ------------- | -------- | ---------------------------------------------------------------------------- |
|
|
324
|
+
| `request.startDate` | `string` | required | `YYYY-MM-DD` |
|
|
325
|
+
| `request.endDate` | `string` | required | `YYYY-MM-DD` |
|
|
326
|
+
| `request.dimensions` | `Dimension[]` | `[]` | `'query'`, `'page'`, `'country'`, `'device'`, `'date'`, `'searchAppearance'` |
|
|
327
|
+
| `request.searchType` | `SearchType` | `'web'` | `'web'`, `'image'`, `'video'`, `'news'` |
|
|
328
|
+
| `request.rowLimit` | `number` | `1000` | Rows per request (max 25,000) |
|
|
329
|
+
| `request.dimensionFilterGroups` | `object[]` | `[]` | Filter groups to narrow results |
|
|
330
|
+
|
|
331
|
+
### `querySearchAnalyticsAll(client, request)`
|
|
332
|
+
|
|
333
|
+
Same as `querySearchAnalytics` but `rowLimit` and `startRow` are managed automatically. Returns `Promise<SearchAnalyticsRow[]>`.
|
|
334
|
+
|
|
335
|
+
### `inspectUrl(client, url)` / `inspectUrlDirect(client, url)`
|
|
336
|
+
|
|
337
|
+
Returns `Promise<InspectionResult>`: `{ verdict, indexingState, lastCrawlTime, mobileUsabilityResult, richResultsResult, ... }`.
|
|
338
|
+
|
|
339
|
+
### `listSitemaps(client)` / `submitSitemap(client, url)` / `deleteSitemap(client, url)`
|
|
340
|
+
|
|
341
|
+
`listSitemaps` returns `Promise<SitemapListResponse>`. `submitSitemap` and `deleteSitemap` return `Promise<void>`.
|
|
342
|
+
|
|
343
|
+
### Types
|
|
344
|
+
|
|
345
|
+
```ts
|
|
346
|
+
import type {
|
|
347
|
+
OAuthCredentials,
|
|
348
|
+
ServiceAccountCredentials,
|
|
349
|
+
TokenResult,
|
|
350
|
+
TokenManager,
|
|
351
|
+
GSCClientConfig,
|
|
352
|
+
GSCClient,
|
|
353
|
+
SearchType,
|
|
354
|
+
Dimension,
|
|
355
|
+
SearchAnalyticsRequest,
|
|
356
|
+
SearchAnalyticsRow,
|
|
357
|
+
SearchAnalyticsResponse,
|
|
358
|
+
InspectionResult,
|
|
359
|
+
SitemapEntry,
|
|
360
|
+
SitemapListResponse,
|
|
361
|
+
} from '@power-seo/search-console';
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
---
|
|
365
|
+
|
|
366
|
+
## Contributing
|
|
367
|
+
|
|
368
|
+
- Issues: [github.com/cybercraftbd/power-seo/issues](https://github.com/cybercraftbd/power-seo/issues)
|
|
369
|
+
- PRs: [github.com/cybercraftbd/power-seo/pulls](https://github.com/cybercraftbd/power-seo/pulls)
|
|
370
|
+
- Development setup:
|
|
371
|
+
1. `pnpm i`
|
|
372
|
+
2. `pnpm build`
|
|
373
|
+
3. `pnpm test`
|
|
374
|
+
|
|
375
|
+
**Release workflow**
|
|
376
|
+
|
|
377
|
+
- `npm version patch|minor|major`
|
|
378
|
+
- `npm publish --access public`
|
|
379
|
+
|
|
380
|
+
---
|
|
381
|
+
|
|
382
|
+
## About CyberCraft Bangladesh
|
|
383
|
+
|
|
384
|
+
**CyberCraft Bangladesh** is a Bangladesh-based enterprise-grade software engineering company specializing in ERP system development, AI-powered SaaS and business applications, full-stack SEO services, custom website development, and scalable eCommerce platforms. We design and develop intelligent, automation-driven SaaS and enterprise solutions that help startups, SMEs, NGOs, educational institutes, and large organizations streamline operations, enhance digital visibility, and accelerate growth through modern cloud-native technologies.
|
|
385
|
+
|
|
386
|
+
| | |
|
|
387
|
+
| -------------------- | -------------------------------------------------------------- |
|
|
388
|
+
| **Website** | [ccbd.dev](https://ccbd.dev) |
|
|
389
|
+
| **GitHub** | [github.com/cybercraftbd](https://github.com/cybercraftbd) |
|
|
390
|
+
| **npm Organization** | [npmjs.com/org/power-seo](https://www.npmjs.com/org/power-seo) |
|
|
391
|
+
| **Email** | [info@ccbd.dev](mailto:info@ccbd.dev) |
|
|
392
|
+
|
|
393
|
+
---
|
|
394
|
+
|
|
395
|
+
## License
|
|
396
|
+
|
|
397
|
+
**MIT**
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
## Keywords
|
|
402
|
+
|
|
403
|
+
```text
|
|
404
|
+
seo, google-search-console, gsc, search-analytics, url-inspection, sitemap, oauth2, service-account, typescript, nextjs, analytics, keyword-tracking, click-through-rate, impressions
|
|
405
|
+
```
|
package/dist/index.cjs
CHANGED
|
@@ -175,10 +175,10 @@ async function querySearchAnalytics(client, request) {
|
|
|
175
175
|
...request,
|
|
176
176
|
rowLimit: Math.min(request.rowLimit ?? DEFAULT_ROW_LIMIT, MAX_ROW_LIMIT)
|
|
177
177
|
};
|
|
178
|
-
return client.request(
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
);
|
|
178
|
+
return client.request(`/sites/${siteUrl}/searchAnalytics/query`, {
|
|
179
|
+
method: "POST",
|
|
180
|
+
body
|
|
181
|
+
});
|
|
182
182
|
}
|
|
183
183
|
async function querySearchAnalyticsAll(client, request) {
|
|
184
184
|
const allRows = [];
|
|
@@ -207,13 +207,10 @@ async function inspectUrl(client, request) {
|
|
|
207
207
|
siteUrl: client.siteUrl,
|
|
208
208
|
languageCode: request.languageCode ?? "en"
|
|
209
209
|
};
|
|
210
|
-
const token = await client.request(
|
|
211
|
-
"",
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
body
|
|
215
|
-
}
|
|
216
|
-
);
|
|
210
|
+
const token = await client.request("", {
|
|
211
|
+
method: "POST",
|
|
212
|
+
body
|
|
213
|
+
});
|
|
217
214
|
return token.inspectionResult;
|
|
218
215
|
}
|
|
219
216
|
async function inspectUrlDirect(getToken, siteUrl, inspectionUrl, languageCode) {
|
|
@@ -241,26 +238,18 @@ async function inspectUrlDirect(getToken, siteUrl, inspectionUrl, languageCode)
|
|
|
241
238
|
// src/sitemaps.ts
|
|
242
239
|
async function listSitemaps(client) {
|
|
243
240
|
const siteUrl = encodeURIComponent(client.siteUrl);
|
|
244
|
-
const response = await client.request(
|
|
245
|
-
`/sites/${siteUrl}/sitemaps`
|
|
246
|
-
);
|
|
241
|
+
const response = await client.request(`/sites/${siteUrl}/sitemaps`);
|
|
247
242
|
return response.sitemap ?? [];
|
|
248
243
|
}
|
|
249
244
|
async function submitSitemap(client, feedpath) {
|
|
250
245
|
const siteUrl = encodeURIComponent(client.siteUrl);
|
|
251
246
|
const encodedFeed = encodeURIComponent(feedpath);
|
|
252
|
-
await client.request(
|
|
253
|
-
`/sites/${siteUrl}/sitemaps/${encodedFeed}`,
|
|
254
|
-
{ method: "PUT" }
|
|
255
|
-
);
|
|
247
|
+
await client.request(`/sites/${siteUrl}/sitemaps/${encodedFeed}`, { method: "PUT" });
|
|
256
248
|
}
|
|
257
249
|
async function deleteSitemap(client, feedpath) {
|
|
258
250
|
const siteUrl = encodeURIComponent(client.siteUrl);
|
|
259
251
|
const encodedFeed = encodeURIComponent(feedpath);
|
|
260
|
-
await client.request(
|
|
261
|
-
`/sites/${siteUrl}/sitemaps/${encodedFeed}`,
|
|
262
|
-
{ method: "DELETE" }
|
|
263
|
-
);
|
|
252
|
+
await client.request(`/sites/${siteUrl}/sitemaps/${encodedFeed}`, { method: "DELETE" });
|
|
264
253
|
}
|
|
265
254
|
|
|
266
255
|
exports.GSCApiError = GSCApiError;
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +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"]}
|
|
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":["// ============================================================================\n// @power-seo/search-console — Types\n// ============================================================================\n\n// --- Auth Types ---\n\nexport interface OAuthCredentials {\n clientId: string;\n clientSecret: string;\n refreshToken: string;\n}\n\nexport interface ServiceAccountCredentials {\n clientEmail: string;\n privateKeyId: string;\n signJwt: (payload: JwtPayload) => Promise<string>;\n}\n\nexport interface JwtPayload {\n iss: string;\n scope: string;\n aud: string;\n exp: number;\n iat: number;\n}\n\nexport interface TokenResult {\n accessToken: string;\n expiresAt: number;\n}\n\nexport interface TokenManager {\n getToken: () => Promise<string>;\n invalidate: () => void;\n}\n\n// --- Client Types ---\n\nexport interface GSCClientConfig {\n auth: TokenManager;\n siteUrl: string;\n rateLimitPerMinute?: number;\n maxRetries?: number;\n baseUrl?: string;\n}\n\nexport interface GSCClient {\n request: <T>(path: string, options?: RequestOptions) => Promise<T>;\n siteUrl: string;\n}\n\nexport interface RequestOptions {\n method?: string;\n body?: unknown;\n signal?: globalThis.AbortSignal;\n}\n\n// --- Error ---\n\nexport class GSCApiError extends Error {\n readonly status: number;\n readonly code: string;\n readonly retryable: boolean;\n\n constructor(message: string, status: number, code: string) {\n super(message);\n this.name = 'GSCApiError';\n this.status = status;\n this.code = code;\n this.retryable = status === 429 || status >= 500;\n }\n}\n\n// --- Analytics Types ---\n\nexport type SearchType = 'web' | 'image' | 'video' | 'news' | 'discover' | 'googleNews';\nexport type Dimension = 'query' | 'page' | 'country' | 'device' | 'date' | 'searchAppearance';\nexport type AggregationType = 'auto' | 'byPage' | 'byProperty';\nexport type DataState = 'all' | 'final';\n\nexport interface DimensionFilter {\n dimension: Dimension;\n operator:\n | 'equals'\n | 'notEquals'\n | 'contains'\n | 'notContains'\n | 'includingRegex'\n | 'excludingRegex';\n expression: string;\n}\n\nexport interface DimensionFilterGroup {\n groupType?: 'and';\n filters: DimensionFilter[];\n}\n\nexport interface SearchAnalyticsRequest {\n startDate: string;\n endDate: string;\n dimensions?: Dimension[];\n searchType?: SearchType;\n dimensionFilterGroups?: DimensionFilterGroup[];\n aggregationType?: AggregationType;\n rowLimit?: number;\n startRow?: number;\n dataState?: DataState;\n}\n\nexport interface SearchAnalyticsRow {\n keys: string[];\n clicks: number;\n impressions: number;\n ctr: number;\n position: number;\n}\n\nexport interface SearchAnalyticsResponse {\n rows: SearchAnalyticsRow[];\n responseAggregationType: string;\n}\n\n// --- Inspection Types ---\n\nexport interface InspectionRequest {\n inspectionUrl: string;\n languageCode?: string;\n}\n\nexport type VerdictState = 'PASS' | 'PARTIAL' | 'FAIL' | 'VERDICT_UNSPECIFIED' | 'NEUTRAL';\nexport type CrawlState =\n | 'SUCCESSFUL'\n | 'NOT_FOUND'\n | 'SERVER_ERROR'\n | 'ROBOTS_BLOCKED'\n | 'REDIRECT_ERROR'\n | 'ACCESS_DENIED'\n | 'ACCESS_FORBIDDEN';\nexport type IndexState =\n | 'INDEXED'\n | 'NOT_INDEXED'\n | 'SUBMITTED_AND_INDEXED'\n | 'CRAWLED_NOT_INDEXED'\n | 'DISCOVERED_NOT_INDEXED'\n | 'PAGE_WITH_REDIRECT';\nexport type RobotsTxtState = 'ALLOWED' | 'DISALLOWED';\n\nexport interface IndexStatusResult {\n verdict: VerdictState;\n coverageState: IndexState;\n robotsTxtState: RobotsTxtState;\n indexingState: string;\n lastCrawlTime?: string;\n pageFetchState?: CrawlState;\n referringUrls?: string[];\n sitemap?: string[];\n}\n\nexport interface MobileUsabilityResult {\n verdict: VerdictState;\n issues?: Array<{ issueType: string; severity: string; message: string }>;\n}\n\nexport interface RichResultsResult {\n verdict: VerdictState;\n detectedItems?: Array<{\n richResultType: string;\n items: Array<{ name: string; issues?: Array<{ issueMessage: string; severity: string }> }>;\n }>;\n}\n\nexport interface InspectionResult {\n inspectionResultLink: string;\n indexStatusResult: IndexStatusResult;\n mobileUsabilityResult?: MobileUsabilityResult;\n richResultsResult?: RichResultsResult;\n}\n\n// --- Sitemap Types ---\n\nexport type SitemapType = 'sitemap' | 'atomFeed' | 'rssFeed' | 'notSitemap';\n\nexport interface SitemapEntry {\n path: string;\n lastSubmitted?: string;\n isPending: boolean;\n isSitemapsIndex: boolean;\n type: SitemapType;\n lastDownloaded?: string;\n warnings: number;\n errors: number;\n contents?: Array<{ type: string; submitted: number; indexed: number }>;\n}\n\nexport interface SitemapListResponse {\n sitemap: SitemapEntry[];\n}\n","// ============================================================================\n// @power-seo/search-console — Auth\n// ============================================================================\n\nimport type {\n OAuthCredentials,\n ServiceAccountCredentials,\n TokenResult,\n TokenManager,\n} from './types.js';\nimport { GSCApiError } from './types.js';\n\nconst TOKEN_URL = 'https://oauth2.googleapis.com/token';\nconst GSC_SCOPE = 'https://www.googleapis.com/auth/webmasters.readonly';\nconst TOKEN_BUFFER_MS = 60_000;\n\nexport async function exchangeRefreshToken(credentials: OAuthCredentials): Promise<TokenResult> {\n const body = new globalThis.URLSearchParams({\n client_id: credentials.clientId,\n client_secret: credentials.clientSecret,\n refresh_token: credentials.refreshToken,\n grant_type: 'refresh_token',\n });\n\n const response = await globalThis.fetch(TOKEN_URL, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: body.toString(),\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new GSCApiError(\n `Token exchange failed: ${text}`,\n response.status,\n 'TOKEN_EXCHANGE_ERROR',\n );\n }\n\n const data = (await response.json()) as { access_token: string; expires_in: number };\n return {\n accessToken: data.access_token,\n expiresAt: Date.now() + data.expires_in * 1000,\n };\n}\n\nexport async function getServiceAccountToken(\n credentials: ServiceAccountCredentials,\n): Promise<TokenResult> {\n const now = Math.floor(Date.now() / 1000);\n const payload = {\n iss: credentials.clientEmail,\n scope: GSC_SCOPE,\n aud: TOKEN_URL,\n exp: now + 3600,\n iat: now,\n };\n\n const assertion = await credentials.signJwt(payload);\n\n const body = new globalThis.URLSearchParams({\n grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',\n assertion,\n });\n\n const response = await globalThis.fetch(TOKEN_URL, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: body.toString(),\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new GSCApiError(\n `Service account token failed: ${text}`,\n response.status,\n 'SERVICE_ACCOUNT_ERROR',\n );\n }\n\n const data = (await response.json()) as { access_token: string; expires_in: number };\n return {\n accessToken: data.access_token,\n expiresAt: Date.now() + data.expires_in * 1000,\n };\n}\n\nexport function createTokenManager(fetchToken: () => Promise<TokenResult>): TokenManager {\n let cached: TokenResult | null = null;\n let pending: Promise<string> | null = null;\n\n return {\n async getToken(): Promise<string> {\n if (cached && cached.expiresAt > Date.now() + TOKEN_BUFFER_MS) {\n return cached.accessToken;\n }\n\n if (pending) {\n return pending;\n }\n\n pending = fetchToken().then((result) => {\n cached = result;\n pending = null;\n return result.accessToken;\n });\n\n return pending;\n },\n\n invalidate(): void {\n cached = null;\n pending = null;\n },\n };\n}\n","// ============================================================================\n// @power-seo/search-console — Client\n// ============================================================================\n\nimport type { GSCClientConfig, GSCClient, RequestOptions } from './types.js';\nimport { GSCApiError } from './types.js';\nimport { createTokenBucket, consumeToken, getWaitTime, sleep } from '@power-seo/core';\n\nconst DEFAULT_BASE_URL = 'https://searchconsole.googleapis.com/webmasters/v3';\nconst DEFAULT_RATE_LIMIT = 1200;\nconst DEFAULT_MAX_RETRIES = 3;\n\nexport function createGSCClient(config: GSCClientConfig): GSCClient {\n const baseUrl = config.baseUrl ?? DEFAULT_BASE_URL;\n const maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;\n const bucket = createTokenBucket(config.rateLimitPerMinute ?? DEFAULT_RATE_LIMIT);\n\n async function request<T>(path: string, options?: RequestOptions): Promise<T> {\n const waitTime = getWaitTime(bucket);\n if (waitTime > 0) {\n await sleep(waitTime);\n }\n consumeToken(bucket);\n\n const token = await config.auth.getToken();\n const url = `${baseUrl}${path}`;\n\n let lastError: Error | null = null;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n const response = await globalThis.fetch(url, {\n method: options?.method ?? 'GET',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: options?.body ? JSON.stringify(options.body) : undefined,\n signal: options?.signal,\n });\n\n if (response.ok) {\n return (await response.json()) as T;\n }\n\n const text = await response.text();\n const error = new GSCApiError(\n `GSC API error: ${response.status} ${text}`,\n response.status,\n `HTTP_${response.status}`,\n );\n\n if (!error.retryable || attempt === maxRetries) {\n throw error;\n }\n\n lastError = error;\n const backoff = Math.min(1000 * Math.pow(2, attempt), 30_000);\n await sleep(backoff);\n } catch (err) {\n if (err instanceof GSCApiError) {\n if (!err.retryable || attempt === maxRetries) {\n throw err;\n }\n lastError = err;\n const backoff = Math.min(1000 * Math.pow(2, attempt), 30_000);\n await sleep(backoff);\n } else {\n throw err;\n }\n }\n }\n\n throw lastError ?? new GSCApiError('Request failed', 500, 'UNKNOWN');\n }\n\n return {\n request,\n siteUrl: config.siteUrl,\n };\n}\n","// ============================================================================\n// @power-seo/search-console — Analytics\n// ============================================================================\n\nimport type {\n GSCClient,\n SearchAnalyticsRequest,\n SearchAnalyticsResponse,\n SearchAnalyticsRow,\n} from './types.js';\n\nconst MAX_ROW_LIMIT = 25000;\nconst DEFAULT_ROW_LIMIT = 1000;\n\nexport async function querySearchAnalytics(\n client: GSCClient,\n request: SearchAnalyticsRequest,\n): Promise<SearchAnalyticsResponse> {\n const siteUrl = encodeURIComponent(client.siteUrl);\n const body = {\n ...request,\n rowLimit: Math.min(request.rowLimit ?? DEFAULT_ROW_LIMIT, MAX_ROW_LIMIT),\n };\n\n return client.request<SearchAnalyticsResponse>(`/sites/${siteUrl}/searchAnalytics/query`, {\n method: 'POST',\n body,\n });\n}\n\nexport async function querySearchAnalyticsAll(\n client: GSCClient,\n request: Omit<SearchAnalyticsRequest, 'startRow' | 'rowLimit'>,\n): Promise<SearchAnalyticsRow[]> {\n const allRows: SearchAnalyticsRow[] = [];\n let startRow = 0;\n\n while (true) {\n const response = await querySearchAnalytics(client, {\n ...request,\n rowLimit: MAX_ROW_LIMIT,\n startRow,\n });\n\n const rows = response.rows ?? [];\n allRows.push(...rows);\n\n if (rows.length < MAX_ROW_LIMIT) {\n break;\n }\n\n startRow += rows.length;\n }\n\n return allRows;\n}\n","// ============================================================================\n// @power-seo/search-console — URL Inspection\n// ============================================================================\n\nimport type { GSCClient, InspectionRequest, InspectionResult } from './types.js';\n\nconst INSPECTION_BASE = 'https://searchconsole.googleapis.com/v1/urlInspection/index:inspect';\n\nexport async function inspectUrl(\n client: GSCClient,\n request: InspectionRequest,\n): Promise<InspectionResult> {\n const body = {\n inspectionUrl: request.inspectionUrl,\n siteUrl: client.siteUrl,\n languageCode: request.languageCode ?? 'en',\n };\n\n const token = await client.request<{ inspectionResult: InspectionResult }>('', {\n method: 'POST',\n body,\n });\n\n // The URL Inspection API has a different base URL, so we use a direct fetch.\n // However, since the client handles auth and retries, we override for the standard path approach.\n // The API endpoint doesn't follow the webmasters/v3 pattern.\n // We'll use the client's request method with a custom approach.\n\n // Actually, let's do a direct fetch since the base URL differs:\n return token.inspectionResult;\n}\n\nexport async function inspectUrlDirect(\n getToken: () => Promise<string>,\n siteUrl: string,\n inspectionUrl: string,\n languageCode?: string,\n): Promise<InspectionResult> {\n const token = await getToken();\n\n const response = await globalThis.fetch(INSPECTION_BASE, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n inspectionUrl,\n siteUrl,\n languageCode: languageCode ?? 'en',\n }),\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`URL Inspection API error: ${response.status} ${text}`);\n }\n\n const data = (await response.json()) as { inspectionResult: InspectionResult };\n return data.inspectionResult;\n}\n","// ============================================================================\n// @power-seo/search-console — Sitemaps\n// ============================================================================\n\nimport type { GSCClient, SitemapEntry, SitemapListResponse } from './types.js';\n\nexport async function listSitemaps(client: GSCClient): Promise<SitemapEntry[]> {\n const siteUrl = encodeURIComponent(client.siteUrl);\n const response = await client.request<SitemapListResponse>(`/sites/${siteUrl}/sitemaps`);\n return response.sitemap ?? [];\n}\n\nexport async function submitSitemap(client: GSCClient, feedpath: string): Promise<void> {\n const siteUrl = encodeURIComponent(client.siteUrl);\n const encodedFeed = encodeURIComponent(feedpath);\n await client.request<void>(`/sites/${siteUrl}/sitemaps/${encodedFeed}`, { method: 'PUT' });\n}\n\nexport async function deleteSitemap(client: GSCClient, feedpath: string): Promise<void> {\n const siteUrl = encodeURIComponent(client.siteUrl);\n const encodedFeed = encodeURIComponent(feedpath);\n await client.request<void>(`/sites/${siteUrl}/sitemaps/${encodedFeed}`, { method: 'DELETE' });\n}\n"]}
|
package/dist/index.js
CHANGED
|
@@ -173,10 +173,10 @@ async function querySearchAnalytics(client, request) {
|
|
|
173
173
|
...request,
|
|
174
174
|
rowLimit: Math.min(request.rowLimit ?? DEFAULT_ROW_LIMIT, MAX_ROW_LIMIT)
|
|
175
175
|
};
|
|
176
|
-
return client.request(
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
);
|
|
176
|
+
return client.request(`/sites/${siteUrl}/searchAnalytics/query`, {
|
|
177
|
+
method: "POST",
|
|
178
|
+
body
|
|
179
|
+
});
|
|
180
180
|
}
|
|
181
181
|
async function querySearchAnalyticsAll(client, request) {
|
|
182
182
|
const allRows = [];
|
|
@@ -205,13 +205,10 @@ async function inspectUrl(client, request) {
|
|
|
205
205
|
siteUrl: client.siteUrl,
|
|
206
206
|
languageCode: request.languageCode ?? "en"
|
|
207
207
|
};
|
|
208
|
-
const token = await client.request(
|
|
209
|
-
"",
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
body
|
|
213
|
-
}
|
|
214
|
-
);
|
|
208
|
+
const token = await client.request("", {
|
|
209
|
+
method: "POST",
|
|
210
|
+
body
|
|
211
|
+
});
|
|
215
212
|
return token.inspectionResult;
|
|
216
213
|
}
|
|
217
214
|
async function inspectUrlDirect(getToken, siteUrl, inspectionUrl, languageCode) {
|
|
@@ -239,26 +236,18 @@ async function inspectUrlDirect(getToken, siteUrl, inspectionUrl, languageCode)
|
|
|
239
236
|
// src/sitemaps.ts
|
|
240
237
|
async function listSitemaps(client) {
|
|
241
238
|
const siteUrl = encodeURIComponent(client.siteUrl);
|
|
242
|
-
const response = await client.request(
|
|
243
|
-
`/sites/${siteUrl}/sitemaps`
|
|
244
|
-
);
|
|
239
|
+
const response = await client.request(`/sites/${siteUrl}/sitemaps`);
|
|
245
240
|
return response.sitemap ?? [];
|
|
246
241
|
}
|
|
247
242
|
async function submitSitemap(client, feedpath) {
|
|
248
243
|
const siteUrl = encodeURIComponent(client.siteUrl);
|
|
249
244
|
const encodedFeed = encodeURIComponent(feedpath);
|
|
250
|
-
await client.request(
|
|
251
|
-
`/sites/${siteUrl}/sitemaps/${encodedFeed}`,
|
|
252
|
-
{ method: "PUT" }
|
|
253
|
-
);
|
|
245
|
+
await client.request(`/sites/${siteUrl}/sitemaps/${encodedFeed}`, { method: "PUT" });
|
|
254
246
|
}
|
|
255
247
|
async function deleteSitemap(client, feedpath) {
|
|
256
248
|
const siteUrl = encodeURIComponent(client.siteUrl);
|
|
257
249
|
const encodedFeed = encodeURIComponent(feedpath);
|
|
258
|
-
await client.request(
|
|
259
|
-
`/sites/${siteUrl}/sitemaps/${encodedFeed}`,
|
|
260
|
-
{ method: "DELETE" }
|
|
261
|
-
);
|
|
250
|
+
await client.request(`/sites/${siteUrl}/sitemaps/${encodedFeed}`, { method: "DELETE" });
|
|
262
251
|
}
|
|
263
252
|
|
|
264
253
|
export { GSCApiError, createGSCClient, createTokenManager, deleteSitemap, exchangeRefreshToken, getServiceAccountToken, inspectUrl, inspectUrlDirect, listSitemaps, querySearchAnalytics, querySearchAnalyticsAll, submitSitemap };
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/types.ts","../src/auth.ts","../src/client.ts","../src/analytics.ts","../src/inspection.ts","../src/sitemaps.ts"],"names":[],"mappings":"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","file":"index.js","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"]}
|
|
1
|
+
{"version":3,"sources":["../src/types.ts","../src/auth.ts","../src/client.ts","../src/analytics.ts","../src/inspection.ts","../src/sitemaps.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["// ============================================================================\n// @power-seo/search-console — Types\n// ============================================================================\n\n// --- Auth Types ---\n\nexport interface OAuthCredentials {\n clientId: string;\n clientSecret: string;\n refreshToken: string;\n}\n\nexport interface ServiceAccountCredentials {\n clientEmail: string;\n privateKeyId: string;\n signJwt: (payload: JwtPayload) => Promise<string>;\n}\n\nexport interface JwtPayload {\n iss: string;\n scope: string;\n aud: string;\n exp: number;\n iat: number;\n}\n\nexport interface TokenResult {\n accessToken: string;\n expiresAt: number;\n}\n\nexport interface TokenManager {\n getToken: () => Promise<string>;\n invalidate: () => void;\n}\n\n// --- Client Types ---\n\nexport interface GSCClientConfig {\n auth: TokenManager;\n siteUrl: string;\n rateLimitPerMinute?: number;\n maxRetries?: number;\n baseUrl?: string;\n}\n\nexport interface GSCClient {\n request: <T>(path: string, options?: RequestOptions) => Promise<T>;\n siteUrl: string;\n}\n\nexport interface RequestOptions {\n method?: string;\n body?: unknown;\n signal?: globalThis.AbortSignal;\n}\n\n// --- Error ---\n\nexport class GSCApiError extends Error {\n readonly status: number;\n readonly code: string;\n readonly retryable: boolean;\n\n constructor(message: string, status: number, code: string) {\n super(message);\n this.name = 'GSCApiError';\n this.status = status;\n this.code = code;\n this.retryable = status === 429 || status >= 500;\n }\n}\n\n// --- Analytics Types ---\n\nexport type SearchType = 'web' | 'image' | 'video' | 'news' | 'discover' | 'googleNews';\nexport type Dimension = 'query' | 'page' | 'country' | 'device' | 'date' | 'searchAppearance';\nexport type AggregationType = 'auto' | 'byPage' | 'byProperty';\nexport type DataState = 'all' | 'final';\n\nexport interface DimensionFilter {\n dimension: Dimension;\n operator:\n | 'equals'\n | 'notEquals'\n | 'contains'\n | 'notContains'\n | 'includingRegex'\n | 'excludingRegex';\n expression: string;\n}\n\nexport interface DimensionFilterGroup {\n groupType?: 'and';\n filters: DimensionFilter[];\n}\n\nexport interface SearchAnalyticsRequest {\n startDate: string;\n endDate: string;\n dimensions?: Dimension[];\n searchType?: SearchType;\n dimensionFilterGroups?: DimensionFilterGroup[];\n aggregationType?: AggregationType;\n rowLimit?: number;\n startRow?: number;\n dataState?: DataState;\n}\n\nexport interface SearchAnalyticsRow {\n keys: string[];\n clicks: number;\n impressions: number;\n ctr: number;\n position: number;\n}\n\nexport interface SearchAnalyticsResponse {\n rows: SearchAnalyticsRow[];\n responseAggregationType: string;\n}\n\n// --- Inspection Types ---\n\nexport interface InspectionRequest {\n inspectionUrl: string;\n languageCode?: string;\n}\n\nexport type VerdictState = 'PASS' | 'PARTIAL' | 'FAIL' | 'VERDICT_UNSPECIFIED' | 'NEUTRAL';\nexport type CrawlState =\n | 'SUCCESSFUL'\n | 'NOT_FOUND'\n | 'SERVER_ERROR'\n | 'ROBOTS_BLOCKED'\n | 'REDIRECT_ERROR'\n | 'ACCESS_DENIED'\n | 'ACCESS_FORBIDDEN';\nexport type IndexState =\n | 'INDEXED'\n | 'NOT_INDEXED'\n | 'SUBMITTED_AND_INDEXED'\n | 'CRAWLED_NOT_INDEXED'\n | 'DISCOVERED_NOT_INDEXED'\n | 'PAGE_WITH_REDIRECT';\nexport type RobotsTxtState = 'ALLOWED' | 'DISALLOWED';\n\nexport interface IndexStatusResult {\n verdict: VerdictState;\n coverageState: IndexState;\n robotsTxtState: RobotsTxtState;\n indexingState: string;\n lastCrawlTime?: string;\n pageFetchState?: CrawlState;\n referringUrls?: string[];\n sitemap?: string[];\n}\n\nexport interface MobileUsabilityResult {\n verdict: VerdictState;\n issues?: Array<{ issueType: string; severity: string; message: string }>;\n}\n\nexport interface RichResultsResult {\n verdict: VerdictState;\n detectedItems?: Array<{\n richResultType: string;\n items: Array<{ name: string; issues?: Array<{ issueMessage: string; severity: string }> }>;\n }>;\n}\n\nexport interface InspectionResult {\n inspectionResultLink: string;\n indexStatusResult: IndexStatusResult;\n mobileUsabilityResult?: MobileUsabilityResult;\n richResultsResult?: RichResultsResult;\n}\n\n// --- Sitemap Types ---\n\nexport type SitemapType = 'sitemap' | 'atomFeed' | 'rssFeed' | 'notSitemap';\n\nexport interface SitemapEntry {\n path: string;\n lastSubmitted?: string;\n isPending: boolean;\n isSitemapsIndex: boolean;\n type: SitemapType;\n lastDownloaded?: string;\n warnings: number;\n errors: number;\n contents?: Array<{ type: string; submitted: number; indexed: number }>;\n}\n\nexport interface SitemapListResponse {\n sitemap: SitemapEntry[];\n}\n","// ============================================================================\n// @power-seo/search-console — Auth\n// ============================================================================\n\nimport type {\n OAuthCredentials,\n ServiceAccountCredentials,\n TokenResult,\n TokenManager,\n} from './types.js';\nimport { GSCApiError } from './types.js';\n\nconst TOKEN_URL = 'https://oauth2.googleapis.com/token';\nconst GSC_SCOPE = 'https://www.googleapis.com/auth/webmasters.readonly';\nconst TOKEN_BUFFER_MS = 60_000;\n\nexport async function exchangeRefreshToken(credentials: OAuthCredentials): Promise<TokenResult> {\n const body = new globalThis.URLSearchParams({\n client_id: credentials.clientId,\n client_secret: credentials.clientSecret,\n refresh_token: credentials.refreshToken,\n grant_type: 'refresh_token',\n });\n\n const response = await globalThis.fetch(TOKEN_URL, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: body.toString(),\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new GSCApiError(\n `Token exchange failed: ${text}`,\n response.status,\n 'TOKEN_EXCHANGE_ERROR',\n );\n }\n\n const data = (await response.json()) as { access_token: string; expires_in: number };\n return {\n accessToken: data.access_token,\n expiresAt: Date.now() + data.expires_in * 1000,\n };\n}\n\nexport async function getServiceAccountToken(\n credentials: ServiceAccountCredentials,\n): Promise<TokenResult> {\n const now = Math.floor(Date.now() / 1000);\n const payload = {\n iss: credentials.clientEmail,\n scope: GSC_SCOPE,\n aud: TOKEN_URL,\n exp: now + 3600,\n iat: now,\n };\n\n const assertion = await credentials.signJwt(payload);\n\n const body = new globalThis.URLSearchParams({\n grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',\n assertion,\n });\n\n const response = await globalThis.fetch(TOKEN_URL, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: body.toString(),\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new GSCApiError(\n `Service account token failed: ${text}`,\n response.status,\n 'SERVICE_ACCOUNT_ERROR',\n );\n }\n\n const data = (await response.json()) as { access_token: string; expires_in: number };\n return {\n accessToken: data.access_token,\n expiresAt: Date.now() + data.expires_in * 1000,\n };\n}\n\nexport function createTokenManager(fetchToken: () => Promise<TokenResult>): TokenManager {\n let cached: TokenResult | null = null;\n let pending: Promise<string> | null = null;\n\n return {\n async getToken(): Promise<string> {\n if (cached && cached.expiresAt > Date.now() + TOKEN_BUFFER_MS) {\n return cached.accessToken;\n }\n\n if (pending) {\n return pending;\n }\n\n pending = fetchToken().then((result) => {\n cached = result;\n pending = null;\n return result.accessToken;\n });\n\n return pending;\n },\n\n invalidate(): void {\n cached = null;\n pending = null;\n },\n };\n}\n","// ============================================================================\n// @power-seo/search-console — Client\n// ============================================================================\n\nimport type { GSCClientConfig, GSCClient, RequestOptions } from './types.js';\nimport { GSCApiError } from './types.js';\nimport { createTokenBucket, consumeToken, getWaitTime, sleep } from '@power-seo/core';\n\nconst DEFAULT_BASE_URL = 'https://searchconsole.googleapis.com/webmasters/v3';\nconst DEFAULT_RATE_LIMIT = 1200;\nconst DEFAULT_MAX_RETRIES = 3;\n\nexport function createGSCClient(config: GSCClientConfig): GSCClient {\n const baseUrl = config.baseUrl ?? DEFAULT_BASE_URL;\n const maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;\n const bucket = createTokenBucket(config.rateLimitPerMinute ?? DEFAULT_RATE_LIMIT);\n\n async function request<T>(path: string, options?: RequestOptions): Promise<T> {\n const waitTime = getWaitTime(bucket);\n if (waitTime > 0) {\n await sleep(waitTime);\n }\n consumeToken(bucket);\n\n const token = await config.auth.getToken();\n const url = `${baseUrl}${path}`;\n\n let lastError: Error | null = null;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n const response = await globalThis.fetch(url, {\n method: options?.method ?? 'GET',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: options?.body ? JSON.stringify(options.body) : undefined,\n signal: options?.signal,\n });\n\n if (response.ok) {\n return (await response.json()) as T;\n }\n\n const text = await response.text();\n const error = new GSCApiError(\n `GSC API error: ${response.status} ${text}`,\n response.status,\n `HTTP_${response.status}`,\n );\n\n if (!error.retryable || attempt === maxRetries) {\n throw error;\n }\n\n lastError = error;\n const backoff = Math.min(1000 * Math.pow(2, attempt), 30_000);\n await sleep(backoff);\n } catch (err) {\n if (err instanceof GSCApiError) {\n if (!err.retryable || attempt === maxRetries) {\n throw err;\n }\n lastError = err;\n const backoff = Math.min(1000 * Math.pow(2, attempt), 30_000);\n await sleep(backoff);\n } else {\n throw err;\n }\n }\n }\n\n throw lastError ?? new GSCApiError('Request failed', 500, 'UNKNOWN');\n }\n\n return {\n request,\n siteUrl: config.siteUrl,\n };\n}\n","// ============================================================================\n// @power-seo/search-console — Analytics\n// ============================================================================\n\nimport type {\n GSCClient,\n SearchAnalyticsRequest,\n SearchAnalyticsResponse,\n SearchAnalyticsRow,\n} from './types.js';\n\nconst MAX_ROW_LIMIT = 25000;\nconst DEFAULT_ROW_LIMIT = 1000;\n\nexport async function querySearchAnalytics(\n client: GSCClient,\n request: SearchAnalyticsRequest,\n): Promise<SearchAnalyticsResponse> {\n const siteUrl = encodeURIComponent(client.siteUrl);\n const body = {\n ...request,\n rowLimit: Math.min(request.rowLimit ?? DEFAULT_ROW_LIMIT, MAX_ROW_LIMIT),\n };\n\n return client.request<SearchAnalyticsResponse>(`/sites/${siteUrl}/searchAnalytics/query`, {\n method: 'POST',\n body,\n });\n}\n\nexport async function querySearchAnalyticsAll(\n client: GSCClient,\n request: Omit<SearchAnalyticsRequest, 'startRow' | 'rowLimit'>,\n): Promise<SearchAnalyticsRow[]> {\n const allRows: SearchAnalyticsRow[] = [];\n let startRow = 0;\n\n while (true) {\n const response = await querySearchAnalytics(client, {\n ...request,\n rowLimit: MAX_ROW_LIMIT,\n startRow,\n });\n\n const rows = response.rows ?? [];\n allRows.push(...rows);\n\n if (rows.length < MAX_ROW_LIMIT) {\n break;\n }\n\n startRow += rows.length;\n }\n\n return allRows;\n}\n","// ============================================================================\n// @power-seo/search-console — URL Inspection\n// ============================================================================\n\nimport type { GSCClient, InspectionRequest, InspectionResult } from './types.js';\n\nconst INSPECTION_BASE = 'https://searchconsole.googleapis.com/v1/urlInspection/index:inspect';\n\nexport async function inspectUrl(\n client: GSCClient,\n request: InspectionRequest,\n): Promise<InspectionResult> {\n const body = {\n inspectionUrl: request.inspectionUrl,\n siteUrl: client.siteUrl,\n languageCode: request.languageCode ?? 'en',\n };\n\n const token = await client.request<{ inspectionResult: InspectionResult }>('', {\n method: 'POST',\n body,\n });\n\n // The URL Inspection API has a different base URL, so we use a direct fetch.\n // However, since the client handles auth and retries, we override for the standard path approach.\n // The API endpoint doesn't follow the webmasters/v3 pattern.\n // We'll use the client's request method with a custom approach.\n\n // Actually, let's do a direct fetch since the base URL differs:\n return token.inspectionResult;\n}\n\nexport async function inspectUrlDirect(\n getToken: () => Promise<string>,\n siteUrl: string,\n inspectionUrl: string,\n languageCode?: string,\n): Promise<InspectionResult> {\n const token = await getToken();\n\n const response = await globalThis.fetch(INSPECTION_BASE, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n inspectionUrl,\n siteUrl,\n languageCode: languageCode ?? 'en',\n }),\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`URL Inspection API error: ${response.status} ${text}`);\n }\n\n const data = (await response.json()) as { inspectionResult: InspectionResult };\n return data.inspectionResult;\n}\n","// ============================================================================\n// @power-seo/search-console — Sitemaps\n// ============================================================================\n\nimport type { GSCClient, SitemapEntry, SitemapListResponse } from './types.js';\n\nexport async function listSitemaps(client: GSCClient): Promise<SitemapEntry[]> {\n const siteUrl = encodeURIComponent(client.siteUrl);\n const response = await client.request<SitemapListResponse>(`/sites/${siteUrl}/sitemaps`);\n return response.sitemap ?? [];\n}\n\nexport async function submitSitemap(client: GSCClient, feedpath: string): Promise<void> {\n const siteUrl = encodeURIComponent(client.siteUrl);\n const encodedFeed = encodeURIComponent(feedpath);\n await client.request<void>(`/sites/${siteUrl}/sitemaps/${encodedFeed}`, { method: 'PUT' });\n}\n\nexport async function deleteSitemap(client: GSCClient, feedpath: string): Promise<void> {\n const siteUrl = encodeURIComponent(client.siteUrl);\n const encodedFeed = encodeURIComponent(feedpath);\n await client.request<void>(`/sites/${siteUrl}/sitemaps/${encodedFeed}`, { method: 'DELETE' });\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@power-seo/search-console",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Google Search Console API client with OAuth2/service account auth, rate limiting, and retry",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
9
10
|
"import": "./dist/index.js",
|
|
10
|
-
"require": "./dist/index.cjs"
|
|
11
|
-
"types": "./dist/index.d.ts"
|
|
11
|
+
"require": "./dist/index.cjs"
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
14
|
"main": "./dist/index.cjs",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"author": "CyberCraft Bangladesh <info@ccbd.dev>",
|
|
48
48
|
"repository": {
|
|
49
49
|
"type": "git",
|
|
50
|
-
"url": "git+https://github.com/
|
|
50
|
+
"url": "git+https://github.com/CyberCraftBD/power-seo.git",
|
|
51
51
|
"directory": "packages/search-console"
|
|
52
52
|
},
|
|
53
53
|
"publishConfig": {
|