@profplum700/etsy-v3-api-client 2.5.3 → 3.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.cjs CHANGED
@@ -677,6 +677,11 @@ class FileTokenStorage {
677
677
  }
678
678
  }
679
679
 
680
+ const ETSY_RATE_LIMITS = {
681
+ MAX_REQUESTS_PER_DAY: 5000,
682
+ MAX_REQUESTS_PER_SECOND: 5,
683
+ MIN_REQUEST_INTERVAL: 200,
684
+ };
680
685
  class EtsyRateLimiter {
681
686
  constructor(config) {
682
687
  this.requestCount = 0;
@@ -685,9 +690,9 @@ class EtsyRateLimiter {
685
690
  this.isHeaderBasedLimiting = false;
686
691
  this.currentRetryCount = 0;
687
692
  this.config = {
688
- maxRequestsPerDay: 10000,
689
- maxRequestsPerSecond: 10,
690
- minRequestInterval: 100,
693
+ maxRequestsPerDay: ETSY_RATE_LIMITS.MAX_REQUESTS_PER_DAY,
694
+ maxRequestsPerSecond: ETSY_RATE_LIMITS.MAX_REQUESTS_PER_SECOND,
695
+ minRequestInterval: ETSY_RATE_LIMITS.MIN_REQUEST_INTERVAL,
691
696
  maxRetries: 3,
692
697
  baseDelayMs: 1000,
693
698
  maxDelayMs: 30000,
@@ -742,7 +747,7 @@ class EtsyRateLimiter {
742
747
  if (value === null)
743
748
  return undefined;
744
749
  const num = parseInt(value, 10);
745
- return isNaN(num) ? undefined : num;
750
+ return Number.isNaN(num) ? undefined : num;
746
751
  };
747
752
  return {
748
753
  limitPerSecond: parseNumber(getHeader('x-limit-per-second')),
@@ -1083,7 +1088,7 @@ class FieldValidator {
1083
1088
  const value = data[this.field];
1084
1089
  if (value === undefined || value === null)
1085
1090
  return null;
1086
- if (typeof value !== 'number' || isNaN(value)) {
1091
+ if (typeof value !== 'number' || Number.isNaN(value)) {
1087
1092
  return {
1088
1093
  field: this.field,
1089
1094
  message: options.message || `${this.field} must be a number`,
@@ -1292,9 +1297,14 @@ function combineValidators(...validators) {
1292
1297
 
1293
1298
  class DefaultLogger {
1294
1299
  debug(message, ...args) {
1295
- const isDevelopment = isNode$1
1296
- ? process.env.NODE_ENV === 'development'
1297
- : window.location?.hostname === 'localhost' || window.location?.hostname === '127.0.0.1';
1300
+ let isDevelopment;
1301
+ if (isNode$1) {
1302
+ isDevelopment = process.env.NODE_ENV === 'development';
1303
+ }
1304
+ else {
1305
+ const host = window.location?.hostname;
1306
+ isDevelopment = host === 'localhost' || host === '127.0.0.1';
1307
+ }
1298
1308
  if (isDevelopment) {
1299
1309
  console.log(`[DEBUG] ${message}`, ...args);
1300
1310
  }
@@ -1336,6 +1346,9 @@ class MemoryCache {
1336
1346
  }
1337
1347
  class EtsyClient {
1338
1348
  constructor(config) {
1349
+ if (!config.sharedSecret) {
1350
+ throw new EtsyAuthError('sharedSecret is REQUIRED for Etsy API v3 application usage. See: https://github.com/profplum700/etsy-v3-api-client/issues/21');
1351
+ }
1339
1352
  this.tokenManager = new TokenManager(config);
1340
1353
  this.baseUrl = config.baseUrl || 'https://api.etsy.com/v3/application';
1341
1354
  this.logger = new DefaultLogger();
@@ -1343,9 +1356,9 @@ class EtsyClient {
1343
1356
  this.sharedSecret = config.sharedSecret;
1344
1357
  if (config.rateLimiting?.enabled !== false) {
1345
1358
  this.rateLimiter = new EtsyRateLimiter({
1346
- maxRequestsPerDay: config.rateLimiting?.maxRequestsPerDay || 10000,
1347
- maxRequestsPerSecond: config.rateLimiting?.maxRequestsPerSecond || 10,
1348
- minRequestInterval: config.rateLimiting?.minRequestInterval ?? 100,
1359
+ maxRequestsPerDay: config.rateLimiting?.maxRequestsPerDay || ETSY_RATE_LIMITS.MAX_REQUESTS_PER_DAY,
1360
+ maxRequestsPerSecond: config.rateLimiting?.maxRequestsPerSecond || ETSY_RATE_LIMITS.MAX_REQUESTS_PER_SECOND,
1361
+ minRequestInterval: config.rateLimiting?.minRequestInterval ?? ETSY_RATE_LIMITS.MIN_REQUEST_INTERVAL,
1349
1362
  maxRetries: config.rateLimiting?.maxRetries,
1350
1363
  baseDelayMs: config.rateLimiting?.baseDelayMs,
1351
1364
  maxDelayMs: config.rateLimiting?.maxDelayMs,
@@ -1376,7 +1389,12 @@ class EtsyClient {
1376
1389
  if (useCache && this.cache && requestOptions.method === 'GET') {
1377
1390
  const cached = await this.cache.get(cacheKey);
1378
1391
  if (cached) {
1379
- return JSON.parse(cached);
1392
+ try {
1393
+ return JSON.parse(cached);
1394
+ }
1395
+ catch {
1396
+ await this.cache.delete(cacheKey);
1397
+ }
1380
1398
  }
1381
1399
  }
1382
1400
  await this.rateLimiter.waitForRateLimit();
@@ -1450,10 +1468,7 @@ class EtsyClient {
1450
1468
  return body;
1451
1469
  }
1452
1470
  getApiKey() {
1453
- if (this.sharedSecret) {
1454
- return `${this.keystring}:${this.sharedSecret}`;
1455
- }
1456
- return this.keystring;
1471
+ return `${this.keystring}:${this.sharedSecret}`;
1457
1472
  }
1458
1473
  async getUser() {
1459
1474
  return this.makeRequest('/users/me');
@@ -2124,6 +2139,22 @@ class EtsyClient {
2124
2139
  async deleteListingVideo(shopId, listingId, videoId) {
2125
2140
  await this.makeRequest(`/shops/${shopId}/listings/${listingId}/videos/${videoId}`, { method: 'DELETE' }, false);
2126
2141
  }
2142
+ async getListingPersonalizations(listingId) {
2143
+ return this.makeRequest(`/listings/${listingId}/personalization`);
2144
+ }
2145
+ async updateListingPersonalization(shopId, listingId, params) {
2146
+ const { supports_multiple_personalization_questions, ...body } = params;
2147
+ const query = supports_multiple_personalization_questions
2148
+ ? '?supports_multiple_personalization_questions=true'
2149
+ : '';
2150
+ return this.makeRequest(`/shops/${shopId}/listings/${listingId}/personalization${query}`, {
2151
+ method: 'POST',
2152
+ body: JSON.stringify(body)
2153
+ }, false);
2154
+ }
2155
+ async deleteListingPersonalization(shopId, listingId) {
2156
+ await this.makeRequest(`/shops/${shopId}/listings/${listingId}/personalization`, { method: 'DELETE' }, false);
2157
+ }
2127
2158
  async createListingTranslation(shopId, listingId, language, params) {
2128
2159
  const body = this.buildFormBody(params);
2129
2160
  return this.makeRequest(`/shops/${shopId}/listings/${listingId}/translations/${language}`, {
@@ -2606,9 +2637,9 @@ class GlobalRequestQueue {
2606
2637
  this.requestCount = 0;
2607
2638
  this.dailyReset = new Date();
2608
2639
  this.lastRequestTime = 0;
2609
- this.maxRequestsPerDay = 10000;
2610
- this.maxRequestsPerSecond = 10;
2611
- this.minRequestInterval = 100;
2640
+ this.maxRequestsPerDay = ETSY_RATE_LIMITS.MAX_REQUESTS_PER_DAY;
2641
+ this.maxRequestsPerSecond = ETSY_RATE_LIMITS.MAX_REQUESTS_PER_SECOND;
2642
+ this.minRequestInterval = ETSY_RATE_LIMITS.MIN_REQUEST_INTERVAL;
2612
2643
  this.setNextDailyReset();
2613
2644
  }
2614
2645
  static getInstance() {
@@ -2693,11 +2724,12 @@ class GlobalRequestQueue {
2693
2724
  try {
2694
2725
  let result;
2695
2726
  if (item.timeout) {
2696
- const remainingTimeout = item.timeout - elapsed;
2727
+ const timeout = item.timeout;
2728
+ const remainingTimeout = timeout - elapsed;
2697
2729
  let timeoutId;
2698
2730
  const timeoutPromise = new Promise((_, reject) => {
2699
2731
  timeoutId = setTimeout(() => {
2700
- reject(new Error(`Request timeout after ${item.timeout}ms (exceeded during execution)`));
2732
+ reject(new Error(`Request timeout after ${timeout}ms (exceeded during execution)`));
2701
2733
  }, remainingTimeout);
2702
2734
  if (timeoutId && typeof timeoutId.unref === 'function') {
2703
2735
  timeoutId.unref();
@@ -3014,7 +3046,18 @@ class EtsyWebhookHandler {
3014
3046
  }
3015
3047
  }
3016
3048
  parseEvent(payload) {
3017
- const data = typeof payload === 'string' ? JSON.parse(payload) : payload;
3049
+ let data;
3050
+ if (typeof payload === 'string') {
3051
+ try {
3052
+ data = JSON.parse(payload);
3053
+ }
3054
+ catch {
3055
+ throw new Error('Invalid webhook payload: malformed JSON');
3056
+ }
3057
+ }
3058
+ else {
3059
+ data = payload;
3060
+ }
3018
3061
  if (!data.type || !data.data) {
3019
3062
  throw new Error('Invalid webhook event format');
3020
3063
  }
@@ -3404,7 +3447,6 @@ function createCacheStorage(config = {}) {
3404
3447
  case 'lfu':
3405
3448
  return new LFUCache(config);
3406
3449
  case 'ttl':
3407
- return new LRUCache(config);
3408
3450
  default:
3409
3451
  return new LRUCache(config);
3410
3452
  }
@@ -4219,7 +4261,7 @@ function createCachingPlugin(config = {}) {
4219
4261
  };
4220
4262
  }
4221
4263
  function createRateLimitPlugin(config = {}) {
4222
- const maxRequestsPerSecond = config.maxRequestsPerSecond || 10;
4264
+ const maxRequestsPerSecond = config.maxRequestsPerSecond || ETSY_RATE_LIMITS.MAX_REQUESTS_PER_SECOND;
4223
4265
  const requests = [];
4224
4266
  return {
4225
4267
  name: 'rateLimit',
@@ -4280,6 +4322,7 @@ exports.COMMON_SCOPE_COMBINATIONS = COMMON_SCOPE_COMBINATIONS;
4280
4322
  exports.CacheWithInvalidation = CacheWithInvalidation;
4281
4323
  exports.CreateListingSchema = CreateListingSchema;
4282
4324
  exports.DEFAULT_RETRY_CONFIG = DEFAULT_RETRY_CONFIG;
4325
+ exports.ETSY_RATE_LIMITS = ETSY_RATE_LIMITS;
4283
4326
  exports.ETSY_SCOPES = ETSY_SCOPES;
4284
4327
  exports.EncryptedFileTokenStorage = EncryptedFileTokenStorage;
4285
4328
  exports.EtsyApiError = EtsyApiError;