@domandigital/gbp 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -1,3 +1,44 @@
1
+ /**
2
+ * One place that decides how long to wait for Google and when to try again.
3
+ *
4
+ * Every request gets a deadline, because a stalled response would otherwise
5
+ * hold a Next.js render or build until the platform kills it. Rate limits
6
+ * (429) and transient server errors (500, 502, 503, 504) get a small, bounded
7
+ * number of retries with full-jitter backoff. Google documents the 429 for
8
+ * quota (developers.google.com/my-business/content/limits); whether the
9
+ * Business Profile API sends `Retry-After` is not documented, so it is honoured
10
+ * when present and not relied on.
11
+ *
12
+ * Nothing else is retried: a 400, 403 or 404 needs a person to change
13
+ * something, and repeating the request only delays the error that says so.
14
+ */
15
+ interface GbpRequestOptions {
16
+ /** Deadline for each attempt, in ms. Default 8000. */
17
+ timeoutMs?: number;
18
+ /** Attempts per request, counting the first. Default 3. */
19
+ maxAttempts?: number;
20
+ /** Base of the exponential backoff, in ms. Default 250. */
21
+ baseDelayMs?: number;
22
+ /** Cap on a single backoff, in ms. Default 5000. */
23
+ maxDelayMs?: number;
24
+ /** Longest `Retry-After` this will sleep through, in ms. Longer, and the error is returned instead. Default 30000. */
25
+ maxRetryAfterMs?: number;
26
+ /** Cancels everything, including a wait between attempts. */
27
+ signal?: AbortSignal;
28
+ }
29
+ declare const DEFAULT_REQUEST_POLICY: {
30
+ readonly timeoutMs: 8000;
31
+ readonly maxAttempts: 3;
32
+ readonly baseDelayMs: 250;
33
+ readonly maxDelayMs: 5000;
34
+ readonly maxRetryAfterMs: 30000;
35
+ };
36
+ /**
37
+ * `Retry-After` as milliseconds: either delay-seconds or an HTTP-date (RFC
38
+ * 9110 section 10.2.3). `null` when absent or unreadable.
39
+ */
40
+ declare function parseRetryAfter(value: string | null | undefined, nowMs?: number): number | null;
41
+
1
42
  /**
2
43
  * Google user OAuth (refresh-token) auth -- for APIs that reject service
3
44
  * accounts, notably the Business Profile API (all scopes) and Search Console
@@ -9,17 +50,39 @@
9
50
  * GBP_REFRESH_TOKEN values are set in the deploying app's environment -- not
10
51
  * a code-level distinction. Each app/deployment configures its own.
11
52
  *
53
+ * The access token is cached per process until a minute before it expires.
54
+ * Concurrent callers that miss the cache share one refresh rather than each
55
+ * starting their own. A caller that is told the token is no longer good (a
56
+ * 401 from the API) invalidates exactly that token and forces one refresh;
57
+ * see `invalidateAccessToken`.
58
+ *
12
59
  * Env: GBP_CLIENT_ID, GBP_CLIENT_SECRET, GBP_REFRESH_TOKEN.
13
60
  */
61
+
14
62
  /** True when the OAuth client + refresh token are present. */
15
63
  declare function hasGoogleOAuthCredentials(): boolean;
64
+ interface AccessTokenOptions {
65
+ /** Ignore the cache and refresh now, e.g. after the API rejected the cached token. */
66
+ forceRefresh?: boolean;
67
+ /** Deadline and retry policy for the token request. */
68
+ request?: GbpRequestOptions;
69
+ }
70
+ /**
71
+ * Drop the cached token, but only if it is still the one the caller used. A
72
+ * request that was rejected with an old token must not throw away a newer one
73
+ * another request has already fetched.
74
+ */
75
+ declare function invalidateAccessToken(accessToken: string): void;
16
76
  /**
17
77
  * Exchange the stored refresh token for a short-lived access token. Returns
18
78
  * `null` when credentials aren't configured so callers can degrade
19
79
  * gracefully. The token's actual scopes are whatever the refresh token was
20
80
  * granted with -- not scoped per call.
81
+ *
82
+ * Throws `GbpAuthError`. On `invalid_grant` the cache is cleared and
83
+ * `reauthorizationRequired` is true: retrying cannot fix it.
21
84
  */
22
- declare function getGoogleOAuthAccessToken(): Promise<string | null>;
85
+ declare function getGoogleOAuthAccessToken(options?: AccessTokenOptions): Promise<string | null>;
23
86
 
24
87
  /**
25
88
  * Google Business Profile -- fetch the business's own reviews, including the
@@ -43,10 +106,28 @@ declare function getGoogleOAuthAccessToken(): Promise<string | null>;
43
106
  * Env: GBP_CLIENT_ID, GBP_CLIENT_SECRET, GBP_REFRESH_TOKEN,
44
107
  * GOOGLE_BUSINESS_ACCOUNT_ID, GOOGLE_BUSINESS_LOCATION_ID.
45
108
  */
109
+
110
+ /** 50 pages of 50 is 2,500 reviews: far past any client's profile, well short of a runaway loop. */
111
+ declare const DEFAULT_MAX_PAGES = 50;
112
+ /** Google's moderation state for an owner reply. Replies from before moderation existed carry none. */
113
+ type ReviewReplyState = "PENDING" | "REJECTED" | "APPROVED";
46
114
  interface ReviewReply {
47
115
  text: string;
48
116
  /** ISO 8601, when the reply was last posted/edited. */
49
117
  updatedAt: string;
118
+ /** Moderation state, when Google reports one. */
119
+ state?: ReviewReplyState;
120
+ /** Why Google rejected the reply. Only set when `state` is `"REJECTED"`. New values may appear. */
121
+ policyViolation?: string;
122
+ }
123
+ /** A photo or video the reviewer attached. */
124
+ interface ReviewMedia {
125
+ /** The photo, or the video's thumbnail. */
126
+ thumbnailUrl: string;
127
+ /** The reviewer's own label for it, if any. */
128
+ label?: string;
129
+ /** Set for a video. */
130
+ videoUrl?: string;
50
131
  }
51
132
  interface BusinessReview {
52
133
  id: string;
@@ -56,16 +137,61 @@ interface BusinessReview {
56
137
  comment: string;
57
138
  createdAt: string;
58
139
  reply?: ReviewReply;
140
+ /** Photos and videos attached to the review, in Google's order. */
141
+ media?: ReviewMedia[];
142
+ /** Google's URL for replying to this review, for an owner-facing surface. */
143
+ replyUrl?: string;
59
144
  }
60
145
  interface BusinessReviewsResult {
146
+ /**
147
+ * Google's average for every review on the profile, rating-only ones
148
+ * included. `null` when Google did not report one (or the package is
149
+ * unconfigured): never computed from the returned list.
150
+ */
61
151
  averageRating: number | null;
62
- totalReviewCount: number;
152
+ /**
153
+ * Google's count of every review on the profile. `null` when Google did not
154
+ * report one: never the length of the returned (filtered, limited) list,
155
+ * which would publish a wrong number into anything built from it.
156
+ */
157
+ totalReviewCount: number | null;
63
158
  reviews: BusinessReview[];
64
159
  }
65
160
  /** True when account + location + OAuth credentials are all configured. */
66
161
  declare function isBusinessProfileConfigured(): boolean;
162
+ interface RawReview {
163
+ reviewId: string;
164
+ reviewer?: {
165
+ displayName?: string;
166
+ profilePhotoUrl?: string;
167
+ };
168
+ starRating?: string;
169
+ comment?: string;
170
+ createTime?: string;
171
+ reviewReply?: {
172
+ comment?: string;
173
+ updateTime?: string;
174
+ reviewReplyState?: string;
175
+ policyViolation?: string;
176
+ };
177
+ reviewMediaItems?: {
178
+ thumbnailUrl?: string;
179
+ thumbnailLabel?: string;
180
+ videoUrl?: string;
181
+ }[];
182
+ reviewReplyUrl?: string;
183
+ }
184
+ interface ReviewsPage {
185
+ reviews: RawReview[];
186
+ averageRating?: number;
187
+ totalReviewCount?: number;
188
+ nextPageToken?: string;
189
+ }
190
+ type ReviewOrder = "shuffle" | "api";
67
191
  interface GetBusinessReviewsOptions {
68
- /** Max reviews to return (default: all of them, paginated). */
192
+ /** Max reviews to return (default: all of them, paginated). Counts usable
193
+ * reviews only -- see `filterMinStars` -- so this is "give me N reviews you
194
+ * can show", not "give me N raw API results". */
69
195
  limit?: number;
70
196
  /**
71
197
  * When set, only reviews at or above this star rating are returned -- for
@@ -80,12 +206,102 @@ interface GetBusinessReviewsOptions {
80
206
  revalidate?: number;
81
207
  tags?: string[];
82
208
  };
209
+ /**
210
+ * `"shuffle"` (default): randomizes the returned order, applied after
211
+ * `limit`. `"api"`: preserves the Business Profile API's own ordering
212
+ * (`updateTime desc`, most recent first).
213
+ */
214
+ order?: ReviewOrder;
215
+ /**
216
+ * Keep owner replies Google has not approved (`PENDING` or `REJECTED`).
217
+ * Default `false`: a public page shows only replies Google shows, so a
218
+ * rejected reply never appears on the site as if it were live. Set `true`
219
+ * for an owner-facing surface, where `reply.state` and
220
+ * `reply.policyViolation` say what happened.
221
+ */
222
+ includeUnapprovedReplies?: boolean;
223
+ /** Stop after this many pages rather than loop without end. Default 50. */
224
+ maxPages?: number;
225
+ /** Deadline and retry policy for every request this call makes. */
226
+ request?: GbpRequestOptions;
83
227
  }
228
+ /** Validate a page of the reviews.list response. A malformed review is skipped; a malformed page throws. */
229
+ declare function parseListReviewsResponse(body: unknown): ReviewsPage;
84
230
  /**
85
231
  * Fetch reviews for the configured location, paginating through all pages
86
232
  * (GBP caps each page at 50). Returns empty results (never throws on missing
87
233
  * config) so UI can render unconditionally.
234
+ *
235
+ * Every request has a deadline and retries 429 and transient 5xx a bounded
236
+ * number of times. A 401 means the cached access token is no longer good: it
237
+ * is dropped, refreshed once, and the same page is asked for again. A second
238
+ * 401 throws. Throws `GbpApiError`, `GbpAuthError`, `GbpTimeoutError` or
239
+ * `GbpPaginationError`; catch and degrade in the calling route.
88
240
  */
89
241
  declare function getBusinessReviews(options?: GetBusinessReviewsOptions): Promise<BusinessReviewsResult>;
90
242
 
91
- export { type BusinessReview, type BusinessReviewsResult, type GetBusinessReviewsOptions, type ReviewReply, getBusinessReviews, getGoogleOAuthAccessToken, hasGoogleOAuthCredentials, isBusinessProfileConfigured };
243
+ /**
244
+ * Typed failures, so a site can tell "reauthorise the Google account" from
245
+ * "Google is having a bad minute" from "our code looped", and log the
246
+ * difference without ever logging a credential.
247
+ *
248
+ * No message here carries a client id, client secret, refresh token, access
249
+ * token or review text. Bodies from Google are cut to a short, bounded
250
+ * excerpt.
251
+ */
252
+ /** Base class: `instanceof GbpError` catches every failure this package throws. */
253
+ declare class GbpError extends Error {
254
+ constructor(message: string);
255
+ }
256
+ type GbpAuthErrorCode = "invalid_grant" | "invalid_client" | "token_request_failed";
257
+ /**
258
+ * The refresh-token exchange failed. `reauthorizationRequired` is true for
259
+ * `invalid_grant`: Google's documented remedy is to "authenticate the user
260
+ * again", and no amount of retrying fixes it. Common causes: the user revoked
261
+ * access, the token went unused for six months, the OAuth app is still in
262
+ * "Testing" status (refresh tokens expire after 7 days), or the account passed
263
+ * 100 live refresh tokens for this client and the oldest was dropped.
264
+ */
265
+ declare class GbpAuthError extends GbpError {
266
+ readonly code: GbpAuthErrorCode;
267
+ readonly reauthorizationRequired: boolean;
268
+ readonly status: number;
269
+ readonly description?: string;
270
+ constructor(code: GbpAuthErrorCode, status: number, description?: string);
271
+ }
272
+ /** A Business Profile API call returned an error status after its retry budget. */
273
+ declare class GbpApiError extends GbpError {
274
+ readonly operation: string;
275
+ readonly status: number;
276
+ readonly retryable: boolean;
277
+ readonly attempts: number;
278
+ /** Set when Google asked for a longer wait than this package will sleep through. */
279
+ readonly retryAfterMs?: number;
280
+ constructor(options: {
281
+ operation: string;
282
+ status: number;
283
+ retryable: boolean;
284
+ attempts: number;
285
+ body?: string;
286
+ retryAfterMs?: number;
287
+ });
288
+ }
289
+ /** One attempt took longer than its deadline. Distinct from a caller's own abort. */
290
+ declare class GbpTimeoutError extends GbpError {
291
+ readonly operation: string;
292
+ readonly timeoutMs: number;
293
+ constructor(operation: string, timeoutMs: number);
294
+ }
295
+ /**
296
+ * Pagination stopped for a reason that means something upstream is wrong:
297
+ * Google handed back a page token it had already given, or the page budget
298
+ * ran out. Thrown rather than returning a partial list, which would look like
299
+ * a complete one.
300
+ */
301
+ declare class GbpPaginationError extends GbpError {
302
+ readonly reason: "repeated_token" | "page_limit";
303
+ readonly pagesFetched: number;
304
+ constructor(reason: "repeated_token" | "page_limit", pagesFetched: number);
305
+ }
306
+
307
+ export { type AccessTokenOptions, type BusinessReview, type BusinessReviewsResult, DEFAULT_MAX_PAGES, DEFAULT_REQUEST_POLICY, GbpApiError, GbpAuthError, type GbpAuthErrorCode, GbpError, GbpPaginationError, type GbpRequestOptions, GbpTimeoutError, type GetBusinessReviewsOptions, type ReviewMedia, type ReviewOrder, type ReviewReply, type ReviewReplyState, getBusinessReviews, getGoogleOAuthAccessToken, hasGoogleOAuthCredentials, invalidateAccessToken, isBusinessProfileConfigured, parseListReviewsResponse, parseRetryAfter };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,44 @@
1
+ /**
2
+ * One place that decides how long to wait for Google and when to try again.
3
+ *
4
+ * Every request gets a deadline, because a stalled response would otherwise
5
+ * hold a Next.js render or build until the platform kills it. Rate limits
6
+ * (429) and transient server errors (500, 502, 503, 504) get a small, bounded
7
+ * number of retries with full-jitter backoff. Google documents the 429 for
8
+ * quota (developers.google.com/my-business/content/limits); whether the
9
+ * Business Profile API sends `Retry-After` is not documented, so it is honoured
10
+ * when present and not relied on.
11
+ *
12
+ * Nothing else is retried: a 400, 403 or 404 needs a person to change
13
+ * something, and repeating the request only delays the error that says so.
14
+ */
15
+ interface GbpRequestOptions {
16
+ /** Deadline for each attempt, in ms. Default 8000. */
17
+ timeoutMs?: number;
18
+ /** Attempts per request, counting the first. Default 3. */
19
+ maxAttempts?: number;
20
+ /** Base of the exponential backoff, in ms. Default 250. */
21
+ baseDelayMs?: number;
22
+ /** Cap on a single backoff, in ms. Default 5000. */
23
+ maxDelayMs?: number;
24
+ /** Longest `Retry-After` this will sleep through, in ms. Longer, and the error is returned instead. Default 30000. */
25
+ maxRetryAfterMs?: number;
26
+ /** Cancels everything, including a wait between attempts. */
27
+ signal?: AbortSignal;
28
+ }
29
+ declare const DEFAULT_REQUEST_POLICY: {
30
+ readonly timeoutMs: 8000;
31
+ readonly maxAttempts: 3;
32
+ readonly baseDelayMs: 250;
33
+ readonly maxDelayMs: 5000;
34
+ readonly maxRetryAfterMs: 30000;
35
+ };
36
+ /**
37
+ * `Retry-After` as milliseconds: either delay-seconds or an HTTP-date (RFC
38
+ * 9110 section 10.2.3). `null` when absent or unreadable.
39
+ */
40
+ declare function parseRetryAfter(value: string | null | undefined, nowMs?: number): number | null;
41
+
1
42
  /**
2
43
  * Google user OAuth (refresh-token) auth -- for APIs that reject service
3
44
  * accounts, notably the Business Profile API (all scopes) and Search Console
@@ -9,17 +50,39 @@
9
50
  * GBP_REFRESH_TOKEN values are set in the deploying app's environment -- not
10
51
  * a code-level distinction. Each app/deployment configures its own.
11
52
  *
53
+ * The access token is cached per process until a minute before it expires.
54
+ * Concurrent callers that miss the cache share one refresh rather than each
55
+ * starting their own. A caller that is told the token is no longer good (a
56
+ * 401 from the API) invalidates exactly that token and forces one refresh;
57
+ * see `invalidateAccessToken`.
58
+ *
12
59
  * Env: GBP_CLIENT_ID, GBP_CLIENT_SECRET, GBP_REFRESH_TOKEN.
13
60
  */
61
+
14
62
  /** True when the OAuth client + refresh token are present. */
15
63
  declare function hasGoogleOAuthCredentials(): boolean;
64
+ interface AccessTokenOptions {
65
+ /** Ignore the cache and refresh now, e.g. after the API rejected the cached token. */
66
+ forceRefresh?: boolean;
67
+ /** Deadline and retry policy for the token request. */
68
+ request?: GbpRequestOptions;
69
+ }
70
+ /**
71
+ * Drop the cached token, but only if it is still the one the caller used. A
72
+ * request that was rejected with an old token must not throw away a newer one
73
+ * another request has already fetched.
74
+ */
75
+ declare function invalidateAccessToken(accessToken: string): void;
16
76
  /**
17
77
  * Exchange the stored refresh token for a short-lived access token. Returns
18
78
  * `null` when credentials aren't configured so callers can degrade
19
79
  * gracefully. The token's actual scopes are whatever the refresh token was
20
80
  * granted with -- not scoped per call.
81
+ *
82
+ * Throws `GbpAuthError`. On `invalid_grant` the cache is cleared and
83
+ * `reauthorizationRequired` is true: retrying cannot fix it.
21
84
  */
22
- declare function getGoogleOAuthAccessToken(): Promise<string | null>;
85
+ declare function getGoogleOAuthAccessToken(options?: AccessTokenOptions): Promise<string | null>;
23
86
 
24
87
  /**
25
88
  * Google Business Profile -- fetch the business's own reviews, including the
@@ -43,10 +106,28 @@ declare function getGoogleOAuthAccessToken(): Promise<string | null>;
43
106
  * Env: GBP_CLIENT_ID, GBP_CLIENT_SECRET, GBP_REFRESH_TOKEN,
44
107
  * GOOGLE_BUSINESS_ACCOUNT_ID, GOOGLE_BUSINESS_LOCATION_ID.
45
108
  */
109
+
110
+ /** 50 pages of 50 is 2,500 reviews: far past any client's profile, well short of a runaway loop. */
111
+ declare const DEFAULT_MAX_PAGES = 50;
112
+ /** Google's moderation state for an owner reply. Replies from before moderation existed carry none. */
113
+ type ReviewReplyState = "PENDING" | "REJECTED" | "APPROVED";
46
114
  interface ReviewReply {
47
115
  text: string;
48
116
  /** ISO 8601, when the reply was last posted/edited. */
49
117
  updatedAt: string;
118
+ /** Moderation state, when Google reports one. */
119
+ state?: ReviewReplyState;
120
+ /** Why Google rejected the reply. Only set when `state` is `"REJECTED"`. New values may appear. */
121
+ policyViolation?: string;
122
+ }
123
+ /** A photo or video the reviewer attached. */
124
+ interface ReviewMedia {
125
+ /** The photo, or the video's thumbnail. */
126
+ thumbnailUrl: string;
127
+ /** The reviewer's own label for it, if any. */
128
+ label?: string;
129
+ /** Set for a video. */
130
+ videoUrl?: string;
50
131
  }
51
132
  interface BusinessReview {
52
133
  id: string;
@@ -56,16 +137,61 @@ interface BusinessReview {
56
137
  comment: string;
57
138
  createdAt: string;
58
139
  reply?: ReviewReply;
140
+ /** Photos and videos attached to the review, in Google's order. */
141
+ media?: ReviewMedia[];
142
+ /** Google's URL for replying to this review, for an owner-facing surface. */
143
+ replyUrl?: string;
59
144
  }
60
145
  interface BusinessReviewsResult {
146
+ /**
147
+ * Google's average for every review on the profile, rating-only ones
148
+ * included. `null` when Google did not report one (or the package is
149
+ * unconfigured): never computed from the returned list.
150
+ */
61
151
  averageRating: number | null;
62
- totalReviewCount: number;
152
+ /**
153
+ * Google's count of every review on the profile. `null` when Google did not
154
+ * report one: never the length of the returned (filtered, limited) list,
155
+ * which would publish a wrong number into anything built from it.
156
+ */
157
+ totalReviewCount: number | null;
63
158
  reviews: BusinessReview[];
64
159
  }
65
160
  /** True when account + location + OAuth credentials are all configured. */
66
161
  declare function isBusinessProfileConfigured(): boolean;
162
+ interface RawReview {
163
+ reviewId: string;
164
+ reviewer?: {
165
+ displayName?: string;
166
+ profilePhotoUrl?: string;
167
+ };
168
+ starRating?: string;
169
+ comment?: string;
170
+ createTime?: string;
171
+ reviewReply?: {
172
+ comment?: string;
173
+ updateTime?: string;
174
+ reviewReplyState?: string;
175
+ policyViolation?: string;
176
+ };
177
+ reviewMediaItems?: {
178
+ thumbnailUrl?: string;
179
+ thumbnailLabel?: string;
180
+ videoUrl?: string;
181
+ }[];
182
+ reviewReplyUrl?: string;
183
+ }
184
+ interface ReviewsPage {
185
+ reviews: RawReview[];
186
+ averageRating?: number;
187
+ totalReviewCount?: number;
188
+ nextPageToken?: string;
189
+ }
190
+ type ReviewOrder = "shuffle" | "api";
67
191
  interface GetBusinessReviewsOptions {
68
- /** Max reviews to return (default: all of them, paginated). */
192
+ /** Max reviews to return (default: all of them, paginated). Counts usable
193
+ * reviews only -- see `filterMinStars` -- so this is "give me N reviews you
194
+ * can show", not "give me N raw API results". */
69
195
  limit?: number;
70
196
  /**
71
197
  * When set, only reviews at or above this star rating are returned -- for
@@ -80,12 +206,102 @@ interface GetBusinessReviewsOptions {
80
206
  revalidate?: number;
81
207
  tags?: string[];
82
208
  };
209
+ /**
210
+ * `"shuffle"` (default): randomizes the returned order, applied after
211
+ * `limit`. `"api"`: preserves the Business Profile API's own ordering
212
+ * (`updateTime desc`, most recent first).
213
+ */
214
+ order?: ReviewOrder;
215
+ /**
216
+ * Keep owner replies Google has not approved (`PENDING` or `REJECTED`).
217
+ * Default `false`: a public page shows only replies Google shows, so a
218
+ * rejected reply never appears on the site as if it were live. Set `true`
219
+ * for an owner-facing surface, where `reply.state` and
220
+ * `reply.policyViolation` say what happened.
221
+ */
222
+ includeUnapprovedReplies?: boolean;
223
+ /** Stop after this many pages rather than loop without end. Default 50. */
224
+ maxPages?: number;
225
+ /** Deadline and retry policy for every request this call makes. */
226
+ request?: GbpRequestOptions;
83
227
  }
228
+ /** Validate a page of the reviews.list response. A malformed review is skipped; a malformed page throws. */
229
+ declare function parseListReviewsResponse(body: unknown): ReviewsPage;
84
230
  /**
85
231
  * Fetch reviews for the configured location, paginating through all pages
86
232
  * (GBP caps each page at 50). Returns empty results (never throws on missing
87
233
  * config) so UI can render unconditionally.
234
+ *
235
+ * Every request has a deadline and retries 429 and transient 5xx a bounded
236
+ * number of times. A 401 means the cached access token is no longer good: it
237
+ * is dropped, refreshed once, and the same page is asked for again. A second
238
+ * 401 throws. Throws `GbpApiError`, `GbpAuthError`, `GbpTimeoutError` or
239
+ * `GbpPaginationError`; catch and degrade in the calling route.
88
240
  */
89
241
  declare function getBusinessReviews(options?: GetBusinessReviewsOptions): Promise<BusinessReviewsResult>;
90
242
 
91
- export { type BusinessReview, type BusinessReviewsResult, type GetBusinessReviewsOptions, type ReviewReply, getBusinessReviews, getGoogleOAuthAccessToken, hasGoogleOAuthCredentials, isBusinessProfileConfigured };
243
+ /**
244
+ * Typed failures, so a site can tell "reauthorise the Google account" from
245
+ * "Google is having a bad minute" from "our code looped", and log the
246
+ * difference without ever logging a credential.
247
+ *
248
+ * No message here carries a client id, client secret, refresh token, access
249
+ * token or review text. Bodies from Google are cut to a short, bounded
250
+ * excerpt.
251
+ */
252
+ /** Base class: `instanceof GbpError` catches every failure this package throws. */
253
+ declare class GbpError extends Error {
254
+ constructor(message: string);
255
+ }
256
+ type GbpAuthErrorCode = "invalid_grant" | "invalid_client" | "token_request_failed";
257
+ /**
258
+ * The refresh-token exchange failed. `reauthorizationRequired` is true for
259
+ * `invalid_grant`: Google's documented remedy is to "authenticate the user
260
+ * again", and no amount of retrying fixes it. Common causes: the user revoked
261
+ * access, the token went unused for six months, the OAuth app is still in
262
+ * "Testing" status (refresh tokens expire after 7 days), or the account passed
263
+ * 100 live refresh tokens for this client and the oldest was dropped.
264
+ */
265
+ declare class GbpAuthError extends GbpError {
266
+ readonly code: GbpAuthErrorCode;
267
+ readonly reauthorizationRequired: boolean;
268
+ readonly status: number;
269
+ readonly description?: string;
270
+ constructor(code: GbpAuthErrorCode, status: number, description?: string);
271
+ }
272
+ /** A Business Profile API call returned an error status after its retry budget. */
273
+ declare class GbpApiError extends GbpError {
274
+ readonly operation: string;
275
+ readonly status: number;
276
+ readonly retryable: boolean;
277
+ readonly attempts: number;
278
+ /** Set when Google asked for a longer wait than this package will sleep through. */
279
+ readonly retryAfterMs?: number;
280
+ constructor(options: {
281
+ operation: string;
282
+ status: number;
283
+ retryable: boolean;
284
+ attempts: number;
285
+ body?: string;
286
+ retryAfterMs?: number;
287
+ });
288
+ }
289
+ /** One attempt took longer than its deadline. Distinct from a caller's own abort. */
290
+ declare class GbpTimeoutError extends GbpError {
291
+ readonly operation: string;
292
+ readonly timeoutMs: number;
293
+ constructor(operation: string, timeoutMs: number);
294
+ }
295
+ /**
296
+ * Pagination stopped for a reason that means something upstream is wrong:
297
+ * Google handed back a page token it had already given, or the page budget
298
+ * ran out. Thrown rather than returning a partial list, which would look like
299
+ * a complete one.
300
+ */
301
+ declare class GbpPaginationError extends GbpError {
302
+ readonly reason: "repeated_token" | "page_limit";
303
+ readonly pagesFetched: number;
304
+ constructor(reason: "repeated_token" | "page_limit", pagesFetched: number);
305
+ }
306
+
307
+ export { type AccessTokenOptions, type BusinessReview, type BusinessReviewsResult, DEFAULT_MAX_PAGES, DEFAULT_REQUEST_POLICY, GbpApiError, GbpAuthError, type GbpAuthErrorCode, GbpError, GbpPaginationError, type GbpRequestOptions, GbpTimeoutError, type GetBusinessReviewsOptions, type ReviewMedia, type ReviewOrder, type ReviewReply, type ReviewReplyState, getBusinessReviews, getGoogleOAuthAccessToken, hasGoogleOAuthCredentials, invalidateAccessToken, isBusinessProfileConfigured, parseListReviewsResponse, parseRetryAfter };