@gpc-cli/api 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,972 @@
1
+ // src/http.ts
2
+ import { readFile } from "fs/promises";
3
+ import { resolve, isAbsolute } from "path";
4
+
5
+ // src/errors.ts
6
+ var ApiError = class extends Error {
7
+ constructor(message, code, statusCode, suggestion) {
8
+ super(message);
9
+ this.code = code;
10
+ this.statusCode = statusCode;
11
+ this.suggestion = suggestion;
12
+ this.name = "ApiError";
13
+ }
14
+ exitCode = 4;
15
+ toJSON() {
16
+ return {
17
+ success: false,
18
+ error: {
19
+ code: this.code,
20
+ message: this.message,
21
+ suggestion: this.suggestion
22
+ }
23
+ };
24
+ }
25
+ };
26
+
27
+ // src/http.ts
28
+ function sanitizeErrorBody(body) {
29
+ try {
30
+ const parsed = JSON.parse(body);
31
+ if (parsed?.error?.message) {
32
+ return `${parsed.error.code ?? "?"} ${parsed.error.status ?? ""}: ${parsed.error.message}`.trim();
33
+ }
34
+ } catch {
35
+ }
36
+ return body.length > 200 ? body.slice(0, 200) + "..." : body;
37
+ }
38
+ function validateFilePath(filePath) {
39
+ const resolved = resolve(filePath);
40
+ if (!isAbsolute(resolved)) {
41
+ throw new ApiError("Invalid file path", "API_INVALID_PATH", void 0, "File path must resolve to an absolute path.");
42
+ }
43
+ if (filePath.includes("\0")) {
44
+ throw new ApiError("Invalid file path: null bytes not allowed", "API_INVALID_PATH");
45
+ }
46
+ return resolved;
47
+ }
48
+ var BASE_URL = "https://androidpublisher.googleapis.com/androidpublisher/v3/applications";
49
+ var UPLOAD_BASE_URL = "https://androidpublisher.googleapis.com/upload/androidpublisher/v3/applications";
50
+ function envInt(name) {
51
+ const val = process.env[name];
52
+ if (val === void 0) return void 0;
53
+ const n = Number(val);
54
+ return Number.isFinite(n) ? n : void 0;
55
+ }
56
+ function resolveOption(explicit, envName, fallback) {
57
+ return explicit ?? envInt(envName) ?? fallback;
58
+ }
59
+ function mapStatusToError(status, body) {
60
+ switch (status) {
61
+ case 401:
62
+ return {
63
+ code: "API_UNAUTHORIZED",
64
+ suggestion: "Check that your access token is valid and not expired."
65
+ };
66
+ case 403:
67
+ return {
68
+ code: "API_FORBIDDEN",
69
+ suggestion: "Ensure the service account has the required permissions for this operation."
70
+ };
71
+ case 404:
72
+ return {
73
+ code: "API_NOT_FOUND",
74
+ suggestion: "Verify the package name and resource IDs are correct."
75
+ };
76
+ case 409:
77
+ return {
78
+ code: "API_EDIT_CONFLICT",
79
+ suggestion: "Another edit may be in progress. Delete the existing edit and retry."
80
+ };
81
+ case 429:
82
+ return {
83
+ code: "API_RATE_LIMITED",
84
+ suggestion: "Too many requests. The client will retry automatically."
85
+ };
86
+ default:
87
+ if (status >= 500) {
88
+ return {
89
+ code: "API_SERVER_ERROR",
90
+ suggestion: "Google Play API server error. The client will retry automatically."
91
+ };
92
+ }
93
+ return { code: `API_HTTP_${status}` };
94
+ }
95
+ }
96
+ function isRetryable(status) {
97
+ return status === 429 || status >= 500;
98
+ }
99
+ function jitteredDelay(base, attempt, max) {
100
+ const exponential = base * 2 ** attempt;
101
+ const capped = Math.min(exponential, max);
102
+ return capped * (0.5 + Math.random() * 0.5);
103
+ }
104
+ function createHttpClient(options) {
105
+ const maxRetries = resolveOption(options.maxRetries, "GPC_MAX_RETRIES", 3);
106
+ const timeout = resolveOption(options.timeout, "GPC_TIMEOUT", 3e4);
107
+ const baseDelay = resolveOption(options.baseDelay, "GPC_BASE_DELAY", 1e3);
108
+ const maxDelay = resolveOption(options.maxDelay, "GPC_MAX_DELAY", 6e4);
109
+ const onRetry = options.onRetry;
110
+ async function request(method, path, body, params) {
111
+ let url = `${options.baseUrl ?? BASE_URL}${path}`;
112
+ if (params) {
113
+ const search = new URLSearchParams(params);
114
+ url += `?${search.toString()}`;
115
+ }
116
+ let lastError;
117
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
118
+ if (attempt > 0) {
119
+ const delay = jitteredDelay(baseDelay, attempt - 1, maxDelay);
120
+ await new Promise((r) => setTimeout(r, delay));
121
+ }
122
+ const controller = new AbortController();
123
+ const timer = setTimeout(() => controller.abort(), timeout);
124
+ try {
125
+ const token = await options.auth.getAccessToken();
126
+ const headers = {
127
+ Authorization: `Bearer ${token}`,
128
+ "Content-Type": "application/json"
129
+ };
130
+ const init = {
131
+ method,
132
+ headers,
133
+ signal: controller.signal
134
+ };
135
+ if (body !== void 0) {
136
+ init.body = JSON.stringify(body);
137
+ }
138
+ const response = await fetch(url, init);
139
+ if (response.ok) {
140
+ const text = await response.text();
141
+ const data = text ? JSON.parse(text) : {};
142
+ return { data, status: response.status };
143
+ }
144
+ const errorBody = await response.text();
145
+ const { code, suggestion } = mapStatusToError(response.status, errorBody);
146
+ const err = new ApiError(
147
+ `${method} ${path} failed with status ${response.status}: ${sanitizeErrorBody(errorBody)}`,
148
+ code,
149
+ response.status,
150
+ suggestion
151
+ );
152
+ if (isRetryable(response.status) && attempt < maxRetries) {
153
+ lastError = err;
154
+ const delay = jitteredDelay(baseDelay, attempt, maxDelay);
155
+ onRetry?.({
156
+ attempt: attempt + 1,
157
+ method,
158
+ path,
159
+ status: response.status,
160
+ error: err.message,
161
+ delayMs: Math.round(delay),
162
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
163
+ });
164
+ continue;
165
+ }
166
+ throw err;
167
+ } catch (error) {
168
+ if (error instanceof ApiError) {
169
+ throw error;
170
+ }
171
+ if (error instanceof DOMException && error.name === "AbortError") {
172
+ const timeoutErr = new ApiError(
173
+ `${method} ${path} timed out after ${timeout}ms`,
174
+ "API_TIMEOUT",
175
+ void 0,
176
+ "The request exceeded the configured timeout. Consider increasing the timeout value."
177
+ );
178
+ if (attempt < maxRetries) {
179
+ lastError = timeoutErr;
180
+ onRetry?.({
181
+ attempt: attempt + 1,
182
+ method,
183
+ path,
184
+ error: timeoutErr.message,
185
+ delayMs: Math.round(jitteredDelay(baseDelay, attempt, maxDelay)),
186
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
187
+ });
188
+ continue;
189
+ }
190
+ throw timeoutErr;
191
+ }
192
+ const networkErr = new ApiError(
193
+ `${method} ${path} failed: ${error instanceof Error ? error.message : String(error)}`,
194
+ "API_NETWORK_ERROR",
195
+ void 0,
196
+ "A network error occurred. Check your internet connection."
197
+ );
198
+ if (attempt < maxRetries) {
199
+ lastError = networkErr;
200
+ onRetry?.({
201
+ attempt: attempt + 1,
202
+ method,
203
+ path,
204
+ error: networkErr.message,
205
+ delayMs: Math.round(jitteredDelay(baseDelay, attempt, maxDelay)),
206
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
207
+ });
208
+ continue;
209
+ }
210
+ throw networkErr;
211
+ } finally {
212
+ clearTimeout(timer);
213
+ }
214
+ }
215
+ throw lastError ?? new ApiError("Request failed", "API_NETWORK_ERROR");
216
+ }
217
+ async function uploadRequest(path, filePath, contentType) {
218
+ const url = `${UPLOAD_BASE_URL}${path}`;
219
+ const safeFilePath = validateFilePath(filePath);
220
+ const fileBuffer = await readFile(safeFilePath);
221
+ let lastError;
222
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
223
+ if (attempt > 0) {
224
+ const delay = jitteredDelay(baseDelay, attempt - 1, maxDelay);
225
+ await new Promise((r) => setTimeout(r, delay));
226
+ }
227
+ const controller = new AbortController();
228
+ const timer = setTimeout(() => controller.abort(), timeout);
229
+ try {
230
+ const token = await options.auth.getAccessToken();
231
+ const headers = {
232
+ Authorization: `Bearer ${token}`,
233
+ "Content-Type": contentType
234
+ };
235
+ const response = await fetch(url, {
236
+ method: "POST",
237
+ headers,
238
+ body: fileBuffer,
239
+ signal: controller.signal
240
+ });
241
+ if (response.ok) {
242
+ const text = await response.text();
243
+ const data = text ? JSON.parse(text) : {};
244
+ return { data, status: response.status };
245
+ }
246
+ const errorBody = await response.text();
247
+ const { code, suggestion } = mapStatusToError(response.status, errorBody);
248
+ const err = new ApiError(
249
+ `POST upload ${path} failed with status ${response.status}: ${sanitizeErrorBody(errorBody)}`,
250
+ code,
251
+ response.status,
252
+ suggestion
253
+ );
254
+ if (isRetryable(response.status) && attempt < maxRetries) {
255
+ lastError = err;
256
+ const delay = jitteredDelay(baseDelay, attempt, maxDelay);
257
+ onRetry?.({
258
+ attempt: attempt + 1,
259
+ method: "POST",
260
+ path: `upload ${path}`,
261
+ status: response.status,
262
+ error: err.message,
263
+ delayMs: Math.round(delay),
264
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
265
+ });
266
+ continue;
267
+ }
268
+ throw err;
269
+ } catch (error) {
270
+ if (error instanceof ApiError) {
271
+ throw error;
272
+ }
273
+ if (error instanceof DOMException && error.name === "AbortError") {
274
+ const timeoutErr = new ApiError(
275
+ `POST upload ${path} timed out after ${timeout}ms`,
276
+ "API_TIMEOUT",
277
+ void 0,
278
+ "The request exceeded the configured timeout. Consider increasing the timeout value."
279
+ );
280
+ if (attempt < maxRetries) {
281
+ lastError = timeoutErr;
282
+ onRetry?.({
283
+ attempt: attempt + 1,
284
+ method: "POST",
285
+ path: `upload ${path}`,
286
+ error: timeoutErr.message,
287
+ delayMs: Math.round(jitteredDelay(baseDelay, attempt, maxDelay)),
288
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
289
+ });
290
+ continue;
291
+ }
292
+ throw timeoutErr;
293
+ }
294
+ const networkErr = new ApiError(
295
+ `POST upload ${path} failed: ${error instanceof Error ? error.message : String(error)}`,
296
+ "API_NETWORK_ERROR",
297
+ void 0,
298
+ "A network error occurred. Check your internet connection."
299
+ );
300
+ if (attempt < maxRetries) {
301
+ lastError = networkErr;
302
+ onRetry?.({
303
+ attempt: attempt + 1,
304
+ method: "POST",
305
+ path: `upload ${path}`,
306
+ error: networkErr.message,
307
+ delayMs: Math.round(jitteredDelay(baseDelay, attempt, maxDelay)),
308
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
309
+ });
310
+ continue;
311
+ }
312
+ throw networkErr;
313
+ } finally {
314
+ clearTimeout(timer);
315
+ }
316
+ }
317
+ throw lastError ?? new ApiError("Upload request failed", "API_NETWORK_ERROR");
318
+ }
319
+ return {
320
+ get(path, params) {
321
+ return request("GET", path, void 0, params);
322
+ },
323
+ post(path, body) {
324
+ return request("POST", path, body);
325
+ },
326
+ put(path, body) {
327
+ return request("PUT", path, body);
328
+ },
329
+ patch(path, body) {
330
+ return request("PATCH", path, body);
331
+ },
332
+ delete(path) {
333
+ return request("DELETE", path);
334
+ },
335
+ upload(path, filePath, contentType) {
336
+ return uploadRequest(path, filePath, contentType);
337
+ }
338
+ };
339
+ }
340
+
341
+ // src/client.ts
342
+ async function rateLimit(limiter, bucket) {
343
+ if (limiter) await limiter.acquire(bucket);
344
+ }
345
+ function createApiClient(options) {
346
+ const http = createHttpClient(options);
347
+ const limiter = options.rateLimiter || void 0;
348
+ return {
349
+ edits: {
350
+ async insert(packageName) {
351
+ const { data } = await http.post(`/${packageName}/edits`);
352
+ return data;
353
+ },
354
+ async get(packageName, editId) {
355
+ const { data } = await http.get(
356
+ `/${packageName}/edits/${editId}`
357
+ );
358
+ return data;
359
+ },
360
+ async validate(packageName, editId) {
361
+ const { data } = await http.post(
362
+ `/${packageName}/edits/${editId}:validate`
363
+ );
364
+ return data;
365
+ },
366
+ async commit(packageName, editId) {
367
+ const { data } = await http.post(
368
+ `/${packageName}/edits/${editId}:commit`
369
+ );
370
+ return data;
371
+ },
372
+ async delete(packageName, editId) {
373
+ await http.delete(`/${packageName}/edits/${editId}`);
374
+ }
375
+ },
376
+ details: {
377
+ async get(packageName, editId) {
378
+ const { data } = await http.get(
379
+ `/${packageName}/edits/${editId}/details`
380
+ );
381
+ return data;
382
+ },
383
+ async update(packageName, editId, details) {
384
+ const { data } = await http.put(
385
+ `/${packageName}/edits/${editId}/details`,
386
+ details
387
+ );
388
+ return data;
389
+ },
390
+ async patch(packageName, editId, partial) {
391
+ const { data } = await http.patch(
392
+ `/${packageName}/edits/${editId}/details`,
393
+ partial
394
+ );
395
+ return data;
396
+ }
397
+ },
398
+ bundles: {
399
+ async list(packageName, editId) {
400
+ const { data } = await http.get(
401
+ `/${packageName}/edits/${editId}/bundles`
402
+ );
403
+ return data.bundles;
404
+ },
405
+ async upload(packageName, editId, filePath) {
406
+ const { data } = await http.upload(
407
+ `/${packageName}/edits/${editId}/bundles`,
408
+ filePath,
409
+ "application/octet-stream"
410
+ );
411
+ return data.bundle;
412
+ }
413
+ },
414
+ tracks: {
415
+ async list(packageName, editId) {
416
+ const { data } = await http.get(
417
+ `/${packageName}/edits/${editId}/tracks`
418
+ );
419
+ return data.tracks;
420
+ },
421
+ async get(packageName, editId, track) {
422
+ const { data } = await http.get(
423
+ `/${packageName}/edits/${editId}/tracks/${track}`
424
+ );
425
+ return data;
426
+ },
427
+ async update(packageName, editId, track, release) {
428
+ const { data } = await http.put(
429
+ `/${packageName}/edits/${editId}/tracks/${track}`,
430
+ { track, releases: [release] }
431
+ );
432
+ return data;
433
+ }
434
+ },
435
+ listings: {
436
+ async list(packageName, editId) {
437
+ const { data } = await http.get(
438
+ `/${packageName}/edits/${editId}/listings`
439
+ );
440
+ return data.listings || [];
441
+ },
442
+ async get(packageName, editId, language) {
443
+ const { data } = await http.get(
444
+ `/${packageName}/edits/${editId}/listings/${language}`
445
+ );
446
+ return data;
447
+ },
448
+ async update(packageName, editId, language, listing) {
449
+ const { data } = await http.put(
450
+ `/${packageName}/edits/${editId}/listings/${language}`,
451
+ listing
452
+ );
453
+ return data;
454
+ },
455
+ async patch(packageName, editId, language, partial) {
456
+ const { data } = await http.patch(
457
+ `/${packageName}/edits/${editId}/listings/${language}`,
458
+ partial
459
+ );
460
+ return data;
461
+ },
462
+ async delete(packageName, editId, language) {
463
+ await http.delete(`/${packageName}/edits/${editId}/listings/${language}`);
464
+ },
465
+ async deleteAll(packageName, editId) {
466
+ await http.delete(`/${packageName}/edits/${editId}/listings`);
467
+ }
468
+ },
469
+ images: {
470
+ async list(packageName, editId, language, imageType) {
471
+ const { data } = await http.get(
472
+ `/${packageName}/edits/${editId}/listings/${language}/${imageType}`
473
+ );
474
+ return data.images || [];
475
+ },
476
+ async upload(packageName, editId, language, imageType, filePath) {
477
+ const { data } = await http.upload(
478
+ `/${packageName}/edits/${editId}/listings/${language}/${imageType}`,
479
+ filePath,
480
+ filePath.endsWith(".png") ? "image/png" : "image/jpeg"
481
+ );
482
+ return data.image;
483
+ },
484
+ async delete(packageName, editId, language, imageType, imageId) {
485
+ await http.delete(
486
+ `/${packageName}/edits/${editId}/listings/${language}/${imageType}/${imageId}`
487
+ );
488
+ },
489
+ async deleteAll(packageName, editId, language, imageType) {
490
+ const { data } = await http.delete(
491
+ `/${packageName}/edits/${editId}/listings/${language}/${imageType}`
492
+ );
493
+ return data.deleted || [];
494
+ }
495
+ },
496
+ countryAvailability: {
497
+ async get(packageName, editId, track) {
498
+ const { data } = await http.get(
499
+ `/${packageName}/edits/${editId}/countryAvailability/${track}`
500
+ );
501
+ return data;
502
+ }
503
+ },
504
+ reviews: {
505
+ async list(packageName, options2) {
506
+ await rateLimit(limiter, "reviewsGet");
507
+ const params = {};
508
+ if (options2?.token) params["token"] = options2.token;
509
+ if (options2?.maxResults) params["maxResults"] = String(options2.maxResults);
510
+ if (options2?.translationLanguage) params["translationLanguage"] = options2.translationLanguage;
511
+ const hasParams = Object.keys(params).length > 0;
512
+ const { data } = await http.get(
513
+ `/${packageName}/reviews`,
514
+ hasParams ? params : void 0
515
+ );
516
+ return data;
517
+ },
518
+ async get(packageName, reviewId, translationLanguage) {
519
+ await rateLimit(limiter, "reviewsGet");
520
+ const params = {};
521
+ if (translationLanguage) params["translationLanguage"] = translationLanguage;
522
+ const hasParams = Object.keys(params).length > 0;
523
+ const { data } = await http.get(
524
+ `/${packageName}/reviews/${reviewId}`,
525
+ hasParams ? params : void 0
526
+ );
527
+ return data;
528
+ },
529
+ async reply(packageName, reviewId, replyText) {
530
+ await rateLimit(limiter, "reviewsPost");
531
+ const body = { replyText };
532
+ const { data } = await http.post(
533
+ `/${packageName}/reviews/${reviewId}:reply`,
534
+ body
535
+ );
536
+ return data;
537
+ }
538
+ },
539
+ subscriptions: {
540
+ async list(packageName, options2) {
541
+ const params = {};
542
+ if (options2?.pageToken) params["pageToken"] = options2.pageToken;
543
+ if (options2?.pageSize) params["pageSize"] = String(options2.pageSize);
544
+ const hasParams = Object.keys(params).length > 0;
545
+ const { data } = await http.get(
546
+ `/${packageName}/monetization/subscriptions`,
547
+ hasParams ? params : void 0
548
+ );
549
+ return data;
550
+ },
551
+ async get(packageName, productId) {
552
+ const { data } = await http.get(
553
+ `/${packageName}/monetization/subscriptions/${productId}`
554
+ );
555
+ return data;
556
+ },
557
+ async create(packageName, body) {
558
+ const { data } = await http.post(
559
+ `/${packageName}/monetization/subscriptions`,
560
+ body
561
+ );
562
+ return data;
563
+ },
564
+ async update(packageName, productId, body, updateMask) {
565
+ let path = `/${packageName}/monetization/subscriptions/${productId}`;
566
+ if (updateMask) {
567
+ path += `?updateMask=${encodeURIComponent(updateMask).replace(/%2C/gi, ",")}`;
568
+ }
569
+ const { data } = await http.patch(path, body);
570
+ return data;
571
+ },
572
+ async delete(packageName, productId) {
573
+ await http.delete(`/${packageName}/monetization/subscriptions/${productId}`);
574
+ },
575
+ async activateBasePlan(packageName, productId, basePlanId) {
576
+ const { data } = await http.post(
577
+ `/${packageName}/monetization/subscriptions/${productId}/basePlans/${basePlanId}:activate`
578
+ );
579
+ return data;
580
+ },
581
+ async deactivateBasePlan(packageName, productId, basePlanId) {
582
+ const { data } = await http.post(
583
+ `/${packageName}/monetization/subscriptions/${productId}/basePlans/${basePlanId}:deactivate`
584
+ );
585
+ return data;
586
+ },
587
+ async deleteBasePlan(packageName, productId, basePlanId) {
588
+ await http.delete(
589
+ `/${packageName}/monetization/subscriptions/${productId}/basePlans/${basePlanId}`
590
+ );
591
+ },
592
+ async migratePrices(packageName, productId, basePlanId, body) {
593
+ const { data } = await http.post(
594
+ `/${packageName}/monetization/subscriptions/${productId}/basePlans/${basePlanId}:migratePrices`,
595
+ body
596
+ );
597
+ return data;
598
+ },
599
+ async listOffers(packageName, productId, basePlanId) {
600
+ const { data } = await http.get(
601
+ `/${packageName}/monetization/subscriptions/${productId}/basePlans/${basePlanId}/offers`
602
+ );
603
+ return data;
604
+ },
605
+ async getOffer(packageName, productId, basePlanId, offerId) {
606
+ const { data } = await http.get(
607
+ `/${packageName}/monetization/subscriptions/${productId}/basePlans/${basePlanId}/offers/${offerId}`
608
+ );
609
+ return data;
610
+ },
611
+ async createOffer(packageName, productId, basePlanId, body) {
612
+ const { data } = await http.post(
613
+ `/${packageName}/monetization/subscriptions/${productId}/basePlans/${basePlanId}/offers`,
614
+ body
615
+ );
616
+ return data;
617
+ },
618
+ async updateOffer(packageName, productId, basePlanId, offerId, body, updateMask) {
619
+ let path = `/${packageName}/monetization/subscriptions/${productId}/basePlans/${basePlanId}/offers/${offerId}`;
620
+ if (updateMask) {
621
+ path += `?updateMask=${encodeURIComponent(updateMask).replace(/%2C/gi, ",")}`;
622
+ }
623
+ const { data } = await http.patch(path, body);
624
+ return data;
625
+ },
626
+ async deleteOffer(packageName, productId, basePlanId, offerId) {
627
+ await http.delete(
628
+ `/${packageName}/monetization/subscriptions/${productId}/basePlans/${basePlanId}/offers/${offerId}`
629
+ );
630
+ },
631
+ async activateOffer(packageName, productId, basePlanId, offerId) {
632
+ const { data } = await http.post(
633
+ `/${packageName}/monetization/subscriptions/${productId}/basePlans/${basePlanId}/offers/${offerId}:activate`
634
+ );
635
+ return data;
636
+ },
637
+ async deactivateOffer(packageName, productId, basePlanId, offerId) {
638
+ const { data } = await http.post(
639
+ `/${packageName}/monetization/subscriptions/${productId}/basePlans/${basePlanId}/offers/${offerId}:deactivate`
640
+ );
641
+ return data;
642
+ }
643
+ },
644
+ inappproducts: {
645
+ async list(packageName, options2) {
646
+ const params = {};
647
+ if (options2?.token) params["token"] = options2.token;
648
+ if (options2?.maxResults) params["maxResults"] = String(options2.maxResults);
649
+ const hasParams = Object.keys(params).length > 0;
650
+ const { data } = await http.get(
651
+ `/${packageName}/inappproducts`,
652
+ hasParams ? params : void 0
653
+ );
654
+ return data;
655
+ },
656
+ async get(packageName, sku) {
657
+ const { data } = await http.get(
658
+ `/${packageName}/inappproducts/${sku}`
659
+ );
660
+ return data;
661
+ },
662
+ async create(packageName, body) {
663
+ const { data } = await http.post(
664
+ `/${packageName}/inappproducts`,
665
+ body
666
+ );
667
+ return data;
668
+ },
669
+ async update(packageName, sku, body) {
670
+ const { data } = await http.put(
671
+ `/${packageName}/inappproducts/${sku}`,
672
+ body
673
+ );
674
+ return data;
675
+ },
676
+ async delete(packageName, sku) {
677
+ await http.delete(`/${packageName}/inappproducts/${sku}`);
678
+ }
679
+ },
680
+ purchases: {
681
+ async getProduct(packageName, productId, token) {
682
+ const { data } = await http.get(
683
+ `/${packageName}/purchases/products/${productId}/tokens/${token}`
684
+ );
685
+ return data;
686
+ },
687
+ async acknowledgeProduct(packageName, productId, token, body) {
688
+ await http.post(
689
+ `/${packageName}/purchases/products/${productId}/tokens/${token}:acknowledge`,
690
+ body
691
+ );
692
+ },
693
+ async consumeProduct(packageName, productId, token) {
694
+ await http.post(
695
+ `/${packageName}/purchases/products/${productId}/tokens/${token}:consume`
696
+ );
697
+ },
698
+ async getSubscriptionV2(packageName, token) {
699
+ const { data } = await http.get(
700
+ `/${packageName}/purchases/subscriptionsv2/tokens/${token}`
701
+ );
702
+ return data;
703
+ },
704
+ async getSubscriptionV1(packageName, subscriptionId, token) {
705
+ const { data } = await http.get(
706
+ `/${packageName}/purchases/subscriptions/${subscriptionId}/tokens/${token}`
707
+ );
708
+ return data;
709
+ },
710
+ async cancelSubscription(packageName, subscriptionId, token) {
711
+ await http.post(
712
+ `/${packageName}/purchases/subscriptions/${subscriptionId}/tokens/${token}:cancel`
713
+ );
714
+ },
715
+ async deferSubscription(packageName, subscriptionId, token, body) {
716
+ const { data } = await http.post(
717
+ `/${packageName}/purchases/subscriptions/${subscriptionId}/tokens/${token}:defer`,
718
+ body
719
+ );
720
+ return data;
721
+ },
722
+ async revokeSubscriptionV2(packageName, token) {
723
+ await http.post(
724
+ `/${packageName}/purchases/subscriptionsv2/tokens/${token}:revoke`
725
+ );
726
+ },
727
+ async listVoided(packageName, options2) {
728
+ await rateLimit(limiter, "voidedBurst");
729
+ await rateLimit(limiter, "voidedDaily");
730
+ const params = {};
731
+ if (options2?.startTime) params["startTime"] = options2.startTime;
732
+ if (options2?.endTime) params["endTime"] = options2.endTime;
733
+ if (options2?.maxResults) params["maxResults"] = String(options2.maxResults);
734
+ if (options2?.token) params["token"] = options2.token;
735
+ const hasParams = Object.keys(params).length > 0;
736
+ const { data } = await http.get(
737
+ `/${packageName}/purchases/voidedpurchases`,
738
+ hasParams ? params : void 0
739
+ );
740
+ return data;
741
+ }
742
+ },
743
+ orders: {
744
+ async refund(packageName, orderId, body) {
745
+ await http.post(
746
+ `/${packageName}/orders/${orderId}:refund`,
747
+ body
748
+ );
749
+ }
750
+ },
751
+ monetization: {
752
+ async convertRegionPrices(packageName, price) {
753
+ const { data } = await http.post(
754
+ `/${packageName}/monetization/convertRegionPrices`,
755
+ price
756
+ );
757
+ return data;
758
+ }
759
+ },
760
+ reports: {
761
+ async list(packageName, reportType, year, month) {
762
+ const monthStr = String(month).padStart(2, "0");
763
+ const { data } = await http.get(
764
+ `/${packageName}/reports/${reportType}/${year}/${monthStr}`
765
+ );
766
+ return data;
767
+ }
768
+ },
769
+ testers: {
770
+ async get(packageName, editId, track) {
771
+ const { data } = await http.get(
772
+ `/${packageName}/edits/${editId}/testers/${track}`
773
+ );
774
+ return data;
775
+ },
776
+ async update(packageName, editId, track, testersData) {
777
+ const { data } = await http.put(
778
+ `/${packageName}/edits/${editId}/testers/${track}`,
779
+ testersData
780
+ );
781
+ return data;
782
+ }
783
+ },
784
+ deobfuscation: {
785
+ async upload(packageName, editId, versionCode, filePath) {
786
+ const { data } = await http.upload(
787
+ `/${packageName}/edits/${editId}/apks/${versionCode}/deobfuscationFiles/proguard`,
788
+ filePath,
789
+ "application/octet-stream"
790
+ );
791
+ return data.deobfuscationFile;
792
+ }
793
+ }
794
+ };
795
+ }
796
+
797
+ // src/reporting-client.ts
798
+ var REPORTING_BASE_URL = "https://playdeveloperreporting.googleapis.com/v1beta1";
799
+ function createReportingClient(options) {
800
+ const http = createHttpClient({ ...options, baseUrl: REPORTING_BASE_URL });
801
+ return {
802
+ async queryMetricSet(packageName, metricSet, query) {
803
+ const { data } = await http.post(
804
+ `/apps/${packageName}/${metricSet}:query`,
805
+ query
806
+ );
807
+ return data;
808
+ },
809
+ async getAnomalies(packageName) {
810
+ const { data } = await http.get(
811
+ `/apps/${packageName}/anomalies`
812
+ );
813
+ return data;
814
+ },
815
+ async searchErrorIssues(packageName, filter, pageSize, pageToken) {
816
+ const params = {};
817
+ if (filter) params["filter"] = filter;
818
+ if (pageSize) params["pageSize"] = String(pageSize);
819
+ if (pageToken) params["pageToken"] = pageToken;
820
+ const { data } = await http.get(
821
+ `/apps/${packageName}/errorIssues:search`,
822
+ params
823
+ );
824
+ return data;
825
+ },
826
+ async searchErrorReports(packageName, issueName, pageSize, pageToken) {
827
+ const params = {};
828
+ if (pageSize) params["pageSize"] = String(pageSize);
829
+ if (pageToken) params["pageToken"] = pageToken;
830
+ const { data } = await http.get(
831
+ `/apps/${packageName}/errorIssues/${issueName}/reports`,
832
+ params
833
+ );
834
+ return data;
835
+ }
836
+ };
837
+ }
838
+
839
+ // src/users-client.ts
840
+ var USERS_BASE_URL = "https://androidpublisher.googleapis.com/androidpublisher/v3/developers";
841
+ function createUsersClient(options) {
842
+ const http = createHttpClient({ ...options, baseUrl: USERS_BASE_URL });
843
+ return {
844
+ async list(developerId, listOptions) {
845
+ const params = {};
846
+ if (listOptions?.pageToken) params["pageToken"] = listOptions.pageToken;
847
+ if (listOptions?.pageSize) params["pageSize"] = String(listOptions.pageSize);
848
+ const hasParams = Object.keys(params).length > 0;
849
+ const { data } = await http.get(
850
+ `/${developerId}/users`,
851
+ hasParams ? params : void 0
852
+ );
853
+ return data;
854
+ },
855
+ async get(developerId, userId) {
856
+ const { data } = await http.get(
857
+ `/${developerId}/users/${userId}`
858
+ );
859
+ return data;
860
+ },
861
+ async create(developerId, user) {
862
+ const { data } = await http.post(
863
+ `/${developerId}/users`,
864
+ user
865
+ );
866
+ return data;
867
+ },
868
+ async update(developerId, userId, user, updateMask) {
869
+ let path = `/${developerId}/users/${userId}`;
870
+ if (updateMask) {
871
+ path += `?updateMask=${encodeURIComponent(updateMask).replace(/%2C/gi, ",")}`;
872
+ }
873
+ const { data } = await http.patch(path, user);
874
+ return data;
875
+ },
876
+ async delete(developerId, userId) {
877
+ await http.delete(`/${developerId}/users/${userId}`);
878
+ }
879
+ };
880
+ }
881
+
882
+ // src/rate-limiter.ts
883
+ var RATE_LIMIT_BUCKETS = {
884
+ default: { name: "default", maxTokens: 200, refillRate: 200, refillIntervalMs: 1e3 },
885
+ reviewsGet: { name: "reviewsGet", maxTokens: 200, refillRate: 200, refillIntervalMs: 36e5 },
886
+ reviewsPost: { name: "reviewsPost", maxTokens: 2e3, refillRate: 2e3, refillIntervalMs: 864e5 },
887
+ voidedBurst: { name: "voidedBurst", maxTokens: 30, refillRate: 30, refillIntervalMs: 3e4 },
888
+ voidedDaily: { name: "voidedDaily", maxTokens: 6e3, refillRate: 6e3, refillIntervalMs: 864e5 }
889
+ };
890
+ function createRateLimiter(buckets) {
891
+ const states = /* @__PURE__ */ new Map();
892
+ if (buckets) {
893
+ for (const bucket of buckets) {
894
+ states.set(bucket.name, {
895
+ tokens: bucket.maxTokens,
896
+ lastRefillTime: Date.now(),
897
+ config: bucket
898
+ });
899
+ }
900
+ }
901
+ return {
902
+ async acquire(bucket) {
903
+ const state = states.get(bucket);
904
+ if (!state) return;
905
+ const now = Date.now();
906
+ const elapsed = now - state.lastRefillTime;
907
+ const refill = Math.floor(elapsed / state.config.refillIntervalMs * state.config.refillRate);
908
+ if (refill > 0) {
909
+ state.tokens = Math.min(state.config.maxTokens, state.tokens + refill);
910
+ state.lastRefillTime = now;
911
+ }
912
+ if (state.tokens > 0) {
913
+ state.tokens--;
914
+ return;
915
+ }
916
+ const tokensNeeded = 1;
917
+ const waitMs = Math.ceil(tokensNeeded / state.config.refillRate * state.config.refillIntervalMs);
918
+ await new Promise((r) => setTimeout(r, waitMs));
919
+ state.tokens = state.config.refillRate - 1;
920
+ state.lastRefillTime = Date.now();
921
+ }
922
+ };
923
+ }
924
+
925
+ // src/paginate.ts
926
+ async function* paginate(fetchPage, options) {
927
+ let pageToken = options?.startPageToken;
928
+ let collected = 0;
929
+ const limit = options?.limit;
930
+ for (; ; ) {
931
+ if (limit !== void 0 && collected >= limit) break;
932
+ const page = await fetchPage(pageToken);
933
+ const items = page.items;
934
+ if (items.length === 0) break;
935
+ if (limit !== void 0) {
936
+ const remaining = limit - collected;
937
+ if (items.length > remaining) {
938
+ yield items.slice(0, remaining);
939
+ return;
940
+ }
941
+ }
942
+ yield items;
943
+ collected += items.length;
944
+ pageToken = page.nextPageToken;
945
+ if (!pageToken) break;
946
+ }
947
+ }
948
+ async function paginateAll(fetchPage, options) {
949
+ const allItems = [];
950
+ let lastPageToken;
951
+ const limit = options?.limit;
952
+ for await (const items of paginate(fetchPage, options)) {
953
+ allItems.push(...items);
954
+ if (limit !== void 0 && allItems.length >= limit) break;
955
+ }
956
+ if (limit !== void 0 && allItems.length >= limit) {
957
+ lastPageToken = void 0;
958
+ }
959
+ return { items: allItems, nextPageToken: lastPageToken };
960
+ }
961
+ export {
962
+ ApiError,
963
+ RATE_LIMIT_BUCKETS,
964
+ createApiClient,
965
+ createHttpClient,
966
+ createRateLimiter,
967
+ createReportingClient,
968
+ createUsersClient,
969
+ paginate,
970
+ paginateAll
971
+ };
972
+ //# sourceMappingURL=index.js.map