@profplum700/etsy-v3-api-client 2.3.1 → 2.3.5

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/node.esm.js CHANGED
@@ -1,20 +1,38 @@
1
1
  class EtsyApiError extends Error {
2
- constructor(message, _statusCode, _response, _retryAfter) {
2
+ constructor(message, _statusCode, _response, _retryAfter, endpoint) {
3
3
  super(message);
4
4
  this._statusCode = _statusCode;
5
5
  this._response = _response;
6
6
  this._retryAfter = _retryAfter;
7
7
  this.name = 'EtsyApiError';
8
+ this.endpoint = endpoint;
9
+ this.timestamp = new Date();
8
10
  this.details = {
9
11
  statusCode: _statusCode || 0,
10
- retryAfter: _retryAfter
12
+ retryAfter: _retryAfter,
13
+ endpoint,
14
+ timestamp: this.timestamp
11
15
  };
12
16
  if (_response && typeof _response === 'object') {
13
17
  const resp = _response;
14
18
  this.details.errorCode = (resp.error_code || resp.code);
15
19
  this.details.field = resp.field;
16
20
  this.details.suggestion = (resp.suggestion || resp.message);
21
+ if (Array.isArray(resp.errors)) {
22
+ this.details.validationErrors = resp.errors.map((err) => {
23
+ if (err && typeof err === 'object') {
24
+ const e = err;
25
+ return {
26
+ field: String(e.field || 'unknown'),
27
+ message: String(e.message || 'Validation error')
28
+ };
29
+ }
30
+ return { field: 'unknown', message: String(err) };
31
+ });
32
+ }
17
33
  }
34
+ this.suggestions = this.generateSuggestions();
35
+ this.docsUrl = this.generateDocsUrl();
18
36
  }
19
37
  get statusCode() {
20
38
  return this._statusCode;
@@ -47,6 +65,107 @@ class EtsyApiError extends Error {
47
65
  }
48
66
  return new Date(Date.now() + this._retryAfter * 1000);
49
67
  }
68
+ generateSuggestions() {
69
+ const suggestions = [];
70
+ if (!this._statusCode) {
71
+ return ['Check your network connection and try again'];
72
+ }
73
+ switch (this._statusCode) {
74
+ case 400:
75
+ suggestions.push('Review the Etsy API documentation for this endpoint');
76
+ suggestions.push('Check all required parameters are provided');
77
+ suggestions.push('Validate parameter formats and types');
78
+ if (this.details.validationErrors && this.details.validationErrors.length > 0) {
79
+ suggestions.push('\nValidation errors:');
80
+ this.details.validationErrors.forEach(err => {
81
+ suggestions.push(` • ${err.field}: ${err.message}`);
82
+ });
83
+ }
84
+ if (this.details.field) {
85
+ suggestions.push(`Field causing issue: ${this.details.field}`);
86
+ }
87
+ break;
88
+ case 401:
89
+ suggestions.push('Verify your access token is valid and not expired');
90
+ suggestions.push('Check if you need to refresh the token');
91
+ suggestions.push('Ensure you completed the OAuth flow correctly');
92
+ if (this.endpoint?.includes('/shops/')) {
93
+ suggestions.push('Verify the shop_id matches the authenticated user');
94
+ }
95
+ break;
96
+ case 403:
97
+ suggestions.push('Check if your OAuth app has the required scopes:');
98
+ if (this.endpoint?.includes('/listings')) {
99
+ suggestions.push(' • listings_r (for reading listings)');
100
+ suggestions.push(' • listings_w (for creating/updating listings)');
101
+ }
102
+ if (this.endpoint?.includes('/receipts') || this.endpoint?.includes('/transactions')) {
103
+ suggestions.push(' • transactions_r (for reading orders)');
104
+ }
105
+ if (this.endpoint?.includes('/shops')) {
106
+ suggestions.push(' • shops_r (for reading shop data)');
107
+ suggestions.push(' • shops_w (for updating shop data)');
108
+ }
109
+ suggestions.push('Verify your app is approved for production access');
110
+ suggestions.push('Check if the resource belongs to the authenticated user');
111
+ break;
112
+ case 404:
113
+ suggestions.push('Verify the resource ID exists and is spelled correctly');
114
+ if (this.endpoint?.includes('/listings/')) {
115
+ suggestions.push('Check if the listing is active and not deleted');
116
+ suggestions.push('Ensure the listing belongs to the authenticated shop');
117
+ }
118
+ if (this.endpoint?.includes('/shops/')) {
119
+ suggestions.push('Verify the shop ID is correct');
120
+ }
121
+ if (this.endpoint?.includes('/receipts/')) {
122
+ suggestions.push('Check if the receipt ID is valid');
123
+ suggestions.push('Ensure you have access to this shop\'s receipts');
124
+ }
125
+ break;
126
+ case 409:
127
+ suggestions.push('Resource state conflict detected');
128
+ suggestions.push('Check if the resource was modified by another process');
129
+ suggestions.push('Try fetching the latest resource state before updating');
130
+ break;
131
+ case 429: {
132
+ const resetTime = this.getRateLimitReset();
133
+ const resetTimeStr = resetTime
134
+ ? resetTime.toLocaleTimeString()
135
+ : 'shortly';
136
+ suggestions.push(`Rate limit exceeded. Resets at ${resetTimeStr}`);
137
+ suggestions.push('Implement exponential backoff retry logic');
138
+ suggestions.push('Consider caching responses to reduce API calls');
139
+ suggestions.push('Check if you can batch multiple operations');
140
+ if (this._retryAfter) {
141
+ suggestions.push(`Wait ${this._retryAfter} seconds before retrying`);
142
+ }
143
+ break;
144
+ }
145
+ case 500:
146
+ case 502:
147
+ case 503:
148
+ case 504:
149
+ suggestions.push('This is an Etsy server error, not your code');
150
+ suggestions.push('Retry the request after a short delay (exponential backoff)');
151
+ suggestions.push('Check Etsy API status: https://status.etsy.com');
152
+ if (this.isRetryable()) {
153
+ suggestions.push('This error is retryable - the request can be safely retried');
154
+ }
155
+ break;
156
+ default:
157
+ suggestions.push('Check the Etsy API documentation for this endpoint');
158
+ suggestions.push('Review your request parameters and format');
159
+ if (this.details.suggestion) {
160
+ suggestions.push(`Etsy suggestion: ${this.details.suggestion}`);
161
+ }
162
+ }
163
+ return suggestions;
164
+ }
165
+ generateDocsUrl() {
166
+ const errorCode = this.details.errorCode?.toLowerCase() || this._statusCode?.toString() || 'unknown';
167
+ return `https://github.com/profplum700/etsy-v3-api-client/blob/main/docs/troubleshooting/ERROR_CODES.md#${errorCode}`;
168
+ }
50
169
  getUserFriendlyMessage() {
51
170
  let message = this.message;
52
171
  if (this.details.suggestion) {
@@ -63,6 +182,46 @@ class EtsyApiError extends Error {
63
182
  }
64
183
  return message;
65
184
  }
185
+ toString() {
186
+ const parts = [
187
+ `EtsyApiError: ${this.message}`,
188
+ `Status Code: ${this._statusCode || 'Unknown'}`,
189
+ ];
190
+ if (this.details.errorCode) {
191
+ parts.push(`Error Code: ${this.details.errorCode}`);
192
+ }
193
+ if (this.endpoint) {
194
+ parts.push(`Endpoint: ${this.endpoint}`);
195
+ }
196
+ parts.push(`Timestamp: ${this.timestamp.toISOString()}`);
197
+ if (this.suggestions.length > 0) {
198
+ parts.push('\nSuggestions:');
199
+ this.suggestions.forEach(s => {
200
+ if (!s.startsWith(' •')) {
201
+ parts.push(` • ${s}`);
202
+ }
203
+ else {
204
+ parts.push(s);
205
+ }
206
+ });
207
+ }
208
+ parts.push(`\nDocumentation: ${this.docsUrl}`);
209
+ return parts.join('\n');
210
+ }
211
+ toJSON() {
212
+ return {
213
+ name: this.name,
214
+ message: this.message,
215
+ statusCode: this._statusCode,
216
+ errorCode: this.details.errorCode,
217
+ endpoint: this.endpoint,
218
+ suggestions: this.suggestions,
219
+ docsUrl: this.docsUrl,
220
+ timestamp: this.timestamp.toISOString(),
221
+ details: this.details,
222
+ isRetryable: this.isRetryable(),
223
+ };
224
+ }
66
225
  }
67
226
  class EtsyAuthError extends Error {
68
227
  constructor(message, _code) {
@@ -1730,6 +1889,174 @@ const COMMON_SCOPE_COMBINATIONS = {
1730
1889
  ]
1731
1890
  };
1732
1891
 
1892
+ class GlobalRequestQueue {
1893
+ constructor() {
1894
+ this.queue = [];
1895
+ this.processing = false;
1896
+ this.rateLimits = new Map();
1897
+ this.requestCount = 0;
1898
+ this.dailyReset = new Date();
1899
+ this.lastRequestTime = 0;
1900
+ this.maxRequestsPerDay = 10000;
1901
+ this.maxRequestsPerSecond = 10;
1902
+ this.minRequestInterval = 100;
1903
+ this.setNextDailyReset();
1904
+ }
1905
+ static getInstance() {
1906
+ if (!this.instance) {
1907
+ this.instance = new GlobalRequestQueue();
1908
+ }
1909
+ return this.instance;
1910
+ }
1911
+ static resetInstance() {
1912
+ this.instance = null;
1913
+ }
1914
+ async enqueue(request, options = {}) {
1915
+ return new Promise((resolve, reject) => {
1916
+ const queuedRequest = {
1917
+ id: this.generateId(),
1918
+ request,
1919
+ resolve: resolve,
1920
+ reject,
1921
+ priority: options.priority ?? 'normal',
1922
+ addedAt: Date.now(),
1923
+ timeout: options.timeout ?? 30000,
1924
+ endpoint: options.endpoint,
1925
+ };
1926
+ this.queue.push(queuedRequest);
1927
+ if (!this.processing) {
1928
+ this.processQueue().catch(error => {
1929
+ console.error('Queue processing error:', error);
1930
+ });
1931
+ }
1932
+ });
1933
+ }
1934
+ getStatus() {
1935
+ return {
1936
+ queueLength: this.queue.length,
1937
+ processing: this.processing,
1938
+ remainingRequests: Math.max(0, this.maxRequestsPerDay - this.requestCount),
1939
+ resetTime: this.dailyReset,
1940
+ };
1941
+ }
1942
+ clear() {
1943
+ this.queue.forEach(item => {
1944
+ item.reject(new Error('Queue cleared'));
1945
+ });
1946
+ this.queue = [];
1947
+ }
1948
+ async processQueue() {
1949
+ if (this.processing) {
1950
+ return;
1951
+ }
1952
+ this.processing = true;
1953
+ try {
1954
+ while (this.queue.length > 0) {
1955
+ if (Date.now() >= this.dailyReset.getTime()) {
1956
+ this.requestCount = 0;
1957
+ this.setNextDailyReset();
1958
+ }
1959
+ if (this.requestCount >= this.maxRequestsPerDay) {
1960
+ const timeUntilReset = this.dailyReset.getTime() - Date.now();
1961
+ console.warn(`Daily rate limit reached. Waiting ${Math.ceil(timeUntilReset / 1000 / 60)} minutes until reset.`);
1962
+ await this.delay(timeUntilReset);
1963
+ this.requestCount = 0;
1964
+ this.setNextDailyReset();
1965
+ }
1966
+ await this.waitForRateLimit();
1967
+ this.queue.sort((a, b) => {
1968
+ const priorityOrder = {
1969
+ high: 0,
1970
+ normal: 1,
1971
+ low: 2,
1972
+ };
1973
+ return priorityOrder[a.priority] - priorityOrder[b.priority];
1974
+ });
1975
+ const item = this.queue.shift();
1976
+ if (!item) {
1977
+ break;
1978
+ }
1979
+ const elapsed = Date.now() - item.addedAt;
1980
+ if (item.timeout && elapsed > item.timeout) {
1981
+ item.reject(new Error(`Request timeout after ${elapsed}ms (exceeded while waiting in queue)`));
1982
+ continue;
1983
+ }
1984
+ try {
1985
+ let result;
1986
+ if (item.timeout) {
1987
+ const remainingTimeout = item.timeout - elapsed;
1988
+ const timeoutPromise = new Promise((_, reject) => {
1989
+ setTimeout(() => {
1990
+ reject(new Error(`Request timeout after ${item.timeout}ms (exceeded during execution)`));
1991
+ }, remainingTimeout);
1992
+ });
1993
+ result = await Promise.race([item.request(), timeoutPromise]);
1994
+ }
1995
+ else {
1996
+ result = await item.request();
1997
+ }
1998
+ item.resolve(result);
1999
+ this.requestCount++;
2000
+ this.lastRequestTime = Date.now();
2001
+ }
2002
+ catch (error) {
2003
+ if (error instanceof EtsyRateLimitError) {
2004
+ this.updateRateLimitInfo(error);
2005
+ }
2006
+ item.reject(error);
2007
+ }
2008
+ await this.delay(this.minRequestInterval);
2009
+ }
2010
+ }
2011
+ finally {
2012
+ this.processing = false;
2013
+ }
2014
+ }
2015
+ async waitForRateLimit() {
2016
+ const now = Date.now();
2017
+ const globalRateLimit = this.rateLimits.get('global');
2018
+ if (globalRateLimit && now < globalRateLimit.resetAt) {
2019
+ const waitTime = globalRateLimit.resetAt - now;
2020
+ console.log(`Global rate limit active. Waiting ${waitTime}ms`);
2021
+ await this.delay(waitTime);
2022
+ }
2023
+ const timeSinceLastRequest = now - this.lastRequestTime;
2024
+ if (timeSinceLastRequest < this.minRequestInterval) {
2025
+ const waitTime = this.minRequestInterval - timeSinceLastRequest;
2026
+ await this.delay(waitTime);
2027
+ }
2028
+ }
2029
+ updateRateLimitInfo(error) {
2030
+ const retryAfter = error.retryAfter;
2031
+ if (retryAfter) {
2032
+ this.rateLimits.set('global', {
2033
+ remaining: 0,
2034
+ resetAt: Date.now() + retryAfter * 1000,
2035
+ });
2036
+ }
2037
+ }
2038
+ setNextDailyReset() {
2039
+ const now = new Date();
2040
+ this.dailyReset = new Date(now);
2041
+ this.dailyReset.setUTCDate(this.dailyReset.getUTCDate() + 1);
2042
+ this.dailyReset.setUTCHours(0, 0, 0, 0);
2043
+ }
2044
+ generateId() {
2045
+ return `${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
2046
+ }
2047
+ delay(ms) {
2048
+ return new Promise(resolve => setTimeout(resolve, ms));
2049
+ }
2050
+ }
2051
+ GlobalRequestQueue.instance = null;
2052
+ function getGlobalQueue() {
2053
+ return GlobalRequestQueue.getInstance();
2054
+ }
2055
+ function withQueue(request, options) {
2056
+ const queue = GlobalRequestQueue.getInstance();
2057
+ return queue.enqueue(request, options);
2058
+ }
2059
+
1733
2060
  class PaginatedResults {
1734
2061
  constructor(fetcher, options = {}) {
1735
2062
  this.currentPage = [];
@@ -2799,6 +3126,403 @@ function createWebhookSecurity(secret, algorithm = 'sha256') {
2799
3126
  return new WebhookSecurity({ secret, algorithm });
2800
3127
  }
2801
3128
 
3129
+ class SecureTokenStorage {
3130
+ constructor(config = {}) {
3131
+ this.encryptionKey = null;
3132
+ if (typeof window === 'undefined') {
3133
+ throw new Error('SecureTokenStorage is only available in browser environments. ' +
3134
+ 'For Node.js, use EncryptedFileTokenStorage instead.');
3135
+ }
3136
+ if (!window.crypto || !window.crypto.subtle) {
3137
+ throw new Error('Web Crypto API is not supported in this browser. ' +
3138
+ 'Please use a modern browser (Chrome 37+, Firefox 34+, Safari 11+, Edge 79+).');
3139
+ }
3140
+ this.keyPrefix = config.keyPrefix || 'etsy_token';
3141
+ this.derivationInput = config.derivationInput || this.getDefaultDerivationInput();
3142
+ this.storage = config.useSessionStorage ? sessionStorage : localStorage;
3143
+ }
3144
+ async save(tokens) {
3145
+ const key = await this.getEncryptionKey();
3146
+ const tokenJson = JSON.stringify(tokens);
3147
+ const tokenData = new TextEncoder().encode(tokenJson);
3148
+ const iv = window.crypto.getRandomValues(new Uint8Array(12));
3149
+ const encrypted = await window.crypto.subtle.encrypt({
3150
+ name: 'AES-GCM',
3151
+ iv,
3152
+ }, key, tokenData);
3153
+ const integrity = await this.generateIntegrity(encrypted, iv);
3154
+ const stored = {
3155
+ version: 1,
3156
+ encrypted: this.arrayBufferToBase64(encrypted),
3157
+ iv: this.arrayBufferToBase64(iv),
3158
+ integrity: this.arrayBufferToBase64(integrity),
3159
+ expiresAt: tokens.expires_at.getTime(),
3160
+ timestamp: Date.now(),
3161
+ };
3162
+ this.storage.setItem(this.keyPrefix, JSON.stringify(stored));
3163
+ }
3164
+ async load() {
3165
+ const stored = this.storage.getItem(this.keyPrefix);
3166
+ if (!stored) {
3167
+ return null;
3168
+ }
3169
+ try {
3170
+ const data = JSON.parse(stored);
3171
+ if (data.version !== 1) {
3172
+ console.warn('Unsupported token storage version. Clearing storage.');
3173
+ await this.clear();
3174
+ return null;
3175
+ }
3176
+ if (Date.now() > data.expiresAt) {
3177
+ console.info('Stored tokens have expired. Clearing storage.');
3178
+ await this.clear();
3179
+ return null;
3180
+ }
3181
+ const encrypted = this.base64ToArrayBuffer(data.encrypted);
3182
+ const iv = this.base64ToArrayBuffer(data.iv);
3183
+ const storedIntegrity = this.base64ToArrayBuffer(data.integrity);
3184
+ const computedIntegrity = await this.generateIntegrity(encrypted, iv);
3185
+ if (!this.compareArrayBuffers(storedIntegrity, computedIntegrity)) {
3186
+ console.warn('Token integrity check failed. Data may have been tampered with. Clearing storage.');
3187
+ await this.clear();
3188
+ return null;
3189
+ }
3190
+ const key = await this.getEncryptionKey();
3191
+ const decrypted = await window.crypto.subtle.decrypt({
3192
+ name: 'AES-GCM',
3193
+ iv,
3194
+ }, key, encrypted);
3195
+ const tokenJson = new TextDecoder().decode(decrypted);
3196
+ const tokens = JSON.parse(tokenJson);
3197
+ tokens.expires_at = new Date(tokens.expires_at);
3198
+ return tokens;
3199
+ }
3200
+ catch (error) {
3201
+ console.error('Failed to load tokens from storage:', error);
3202
+ await this.clear();
3203
+ return null;
3204
+ }
3205
+ }
3206
+ async clear() {
3207
+ this.storage.removeItem(this.keyPrefix);
3208
+ }
3209
+ async getEncryptionKey() {
3210
+ if (this.encryptionKey) {
3211
+ return this.encryptionKey;
3212
+ }
3213
+ const keyMaterial = await this.deriveKeyMaterial();
3214
+ this.encryptionKey = await window.crypto.subtle.deriveKey({
3215
+ name: 'PBKDF2',
3216
+ salt: this.stringToArrayBuffer('etsy-v3-api-client-salt'),
3217
+ iterations: 100000,
3218
+ hash: 'SHA-256',
3219
+ }, keyMaterial, {
3220
+ name: 'AES-GCM',
3221
+ length: 256,
3222
+ }, false, ['encrypt', 'decrypt']);
3223
+ return this.encryptionKey;
3224
+ }
3225
+ async deriveKeyMaterial() {
3226
+ const keyData = this.stringToArrayBuffer(this.derivationInput);
3227
+ return window.crypto.subtle.importKey('raw', keyData, 'PBKDF2', false, ['deriveKey']);
3228
+ }
3229
+ async generateIntegrity(encrypted, iv) {
3230
+ const combined = new Uint8Array(encrypted.byteLength + iv.byteLength);
3231
+ combined.set(new Uint8Array(encrypted), 0);
3232
+ combined.set(new Uint8Array(iv), encrypted.byteLength);
3233
+ const keyMaterial = await this.deriveKeyMaterial();
3234
+ const hmacKey = await window.crypto.subtle.deriveKey({
3235
+ name: 'PBKDF2',
3236
+ salt: this.stringToArrayBuffer('etsy-integrity-salt'),
3237
+ iterations: 100000,
3238
+ hash: 'SHA-256',
3239
+ }, keyMaterial, {
3240
+ name: 'HMAC',
3241
+ hash: 'SHA-256',
3242
+ }, false, ['sign']);
3243
+ return window.crypto.subtle.sign('HMAC', hmacKey, combined);
3244
+ }
3245
+ getDefaultDerivationInput() {
3246
+ const domain = window.location.hostname;
3247
+ const userAgent = navigator.userAgent;
3248
+ return `${domain}:${userAgent}`;
3249
+ }
3250
+ stringToArrayBuffer(str) {
3251
+ return new TextEncoder().encode(str);
3252
+ }
3253
+ arrayBufferToBase64(buffer) {
3254
+ const bytes = new Uint8Array(buffer);
3255
+ let binary = '';
3256
+ for (let i = 0; i < bytes.byteLength; i++) {
3257
+ const byte = bytes[i];
3258
+ if (byte !== undefined) {
3259
+ binary += String.fromCharCode(byte);
3260
+ }
3261
+ }
3262
+ return btoa(binary);
3263
+ }
3264
+ base64ToArrayBuffer(base64) {
3265
+ const binary = atob(base64);
3266
+ const bytes = new Uint8Array(binary.length);
3267
+ for (let i = 0; i < binary.length; i++) {
3268
+ bytes[i] = binary.charCodeAt(i);
3269
+ }
3270
+ return bytes.buffer;
3271
+ }
3272
+ compareArrayBuffers(a, b) {
3273
+ if (a.byteLength !== b.byteLength) {
3274
+ return false;
3275
+ }
3276
+ const viewA = new Uint8Array(a);
3277
+ const viewB = new Uint8Array(b);
3278
+ for (let i = 0; i < viewA.length; i++) {
3279
+ if (viewA[i] !== viewB[i]) {
3280
+ return false;
3281
+ }
3282
+ }
3283
+ return true;
3284
+ }
3285
+ }
3286
+ function isSecureStorageSupported() {
3287
+ if (typeof window === 'undefined') {
3288
+ return false;
3289
+ }
3290
+ return !!(window.crypto &&
3291
+ window.crypto.subtle &&
3292
+ typeof window.crypto.subtle.encrypt === 'function' &&
3293
+ typeof window.crypto.subtle.decrypt === 'function' &&
3294
+ typeof window.crypto.subtle.deriveKey === 'function');
3295
+ }
3296
+
3297
+ class PluginManager {
3298
+ constructor() {
3299
+ this.plugins = [];
3300
+ }
3301
+ async register(plugin) {
3302
+ if (this.plugins.some(p => p.name === plugin.name)) {
3303
+ throw new Error(`Plugin with name "${plugin.name}" is already registered`);
3304
+ }
3305
+ this.plugins.push(plugin);
3306
+ if (plugin.onInit) {
3307
+ await plugin.onInit();
3308
+ }
3309
+ }
3310
+ async unregister(pluginName) {
3311
+ const index = this.plugins.findIndex(p => p.name === pluginName);
3312
+ if (index === -1) {
3313
+ return false;
3314
+ }
3315
+ const plugin = this.plugins[index];
3316
+ if (!plugin) {
3317
+ return false;
3318
+ }
3319
+ if (plugin.onDestroy) {
3320
+ await plugin.onDestroy();
3321
+ }
3322
+ this.plugins.splice(index, 1);
3323
+ return true;
3324
+ }
3325
+ getPlugins() {
3326
+ return this.plugins;
3327
+ }
3328
+ getPlugin(name) {
3329
+ return this.plugins.find(p => p.name === name);
3330
+ }
3331
+ async executeBeforeRequest(config) {
3332
+ let currentConfig = config;
3333
+ for (const plugin of this.plugins) {
3334
+ if (plugin.onBeforeRequest) {
3335
+ currentConfig = await plugin.onBeforeRequest(currentConfig);
3336
+ }
3337
+ }
3338
+ return currentConfig;
3339
+ }
3340
+ async executeAfterResponse(response) {
3341
+ let currentResponse = response;
3342
+ for (const plugin of this.plugins) {
3343
+ if (plugin.onAfterResponse) {
3344
+ currentResponse = await plugin.onAfterResponse(currentResponse);
3345
+ }
3346
+ }
3347
+ return currentResponse;
3348
+ }
3349
+ async executeOnError(error) {
3350
+ for (const plugin of this.plugins) {
3351
+ if (plugin.onError) {
3352
+ await plugin.onError(error);
3353
+ }
3354
+ }
3355
+ }
3356
+ async clear() {
3357
+ for (const plugin of this.plugins) {
3358
+ if (plugin.onDestroy) {
3359
+ await plugin.onDestroy();
3360
+ }
3361
+ }
3362
+ this.plugins = [];
3363
+ }
3364
+ }
3365
+ function createAnalyticsPlugin(config) {
3366
+ const requestTimes = new Map();
3367
+ return {
3368
+ name: 'analytics',
3369
+ version: '1.0.0',
3370
+ onBeforeRequest(requestConfig) {
3371
+ const requestId = `${requestConfig.method}:${requestConfig.endpoint}:${Date.now()}`;
3372
+ requestTimes.set(requestId, Date.now());
3373
+ return requestConfig;
3374
+ },
3375
+ onAfterResponse(response) {
3376
+ if (config.trackEndpoint) {
3377
+ const requestId = Array.from(requestTimes.keys()).pop();
3378
+ if (requestId) {
3379
+ const startTime = requestTimes.get(requestId);
3380
+ if (startTime) {
3381
+ const duration = Date.now() - startTime;
3382
+ const parts = requestId.split(':');
3383
+ const method = parts[0];
3384
+ const endpoint = parts[1];
3385
+ if (endpoint && method) {
3386
+ config.trackEndpoint(endpoint, method, duration);
3387
+ }
3388
+ requestTimes.delete(requestId);
3389
+ }
3390
+ }
3391
+ }
3392
+ return response;
3393
+ },
3394
+ onError(error) {
3395
+ if (config.trackError) {
3396
+ config.trackError(error);
3397
+ }
3398
+ },
3399
+ };
3400
+ }
3401
+ function createRetryPlugin(config = {}) {
3402
+ const { retryableStatusCodes = [408, 429, 500, 502, 503, 504], } = config;
3403
+ return {
3404
+ name: 'retry',
3405
+ version: '1.0.0',
3406
+ async onError(error) {
3407
+ if (error instanceof EtsyApiError) {
3408
+ const statusCode = error.statusCode;
3409
+ if (statusCode && retryableStatusCodes.includes(statusCode)) {
3410
+ if (config.onRetry) {
3411
+ config.onRetry(1, error);
3412
+ }
3413
+ }
3414
+ }
3415
+ },
3416
+ };
3417
+ }
3418
+ function createLoggingPlugin(config = {}) {
3419
+ const logger = config.logger || console;
3420
+ const logLevel = config.logLevel || 'info';
3421
+ const shouldLog = (level) => {
3422
+ const levels = ['debug', 'info', 'warn', 'error'];
3423
+ const currentLevelIndex = levels.indexOf(logLevel);
3424
+ const messageLevelIndex = levels.indexOf(level);
3425
+ return messageLevelIndex >= currentLevelIndex;
3426
+ };
3427
+ return {
3428
+ name: 'logging',
3429
+ version: '1.0.0',
3430
+ onBeforeRequest(requestConfig) {
3431
+ if (shouldLog('debug')) {
3432
+ logger.debug('[Etsy API] Request:', {
3433
+ method: requestConfig.method,
3434
+ endpoint: requestConfig.endpoint,
3435
+ params: requestConfig.params,
3436
+ });
3437
+ }
3438
+ return requestConfig;
3439
+ },
3440
+ onAfterResponse(response) {
3441
+ if (shouldLog('debug')) {
3442
+ logger.debug('[Etsy API] Response:', {
3443
+ status: response.status,
3444
+ data: response.data,
3445
+ });
3446
+ }
3447
+ return response;
3448
+ },
3449
+ onError(error) {
3450
+ if (shouldLog('error')) {
3451
+ logger.error('[Etsy API] Error:', error);
3452
+ if (error instanceof EtsyApiError) {
3453
+ logger.error('[Etsy API] Error Details:', {
3454
+ statusCode: error.statusCode,
3455
+ endpoint: error.endpoint,
3456
+ suggestions: error.suggestions,
3457
+ });
3458
+ }
3459
+ }
3460
+ },
3461
+ };
3462
+ }
3463
+ function createCachingPlugin(config = {}) {
3464
+ const cache = new Map();
3465
+ const ttl = (config.ttl || 300) * 1000;
3466
+ const generateKey = (requestConfig) => {
3467
+ if (config.keyGenerator) {
3468
+ return config.keyGenerator(requestConfig);
3469
+ }
3470
+ return `${requestConfig.method}:${requestConfig.endpoint}:${JSON.stringify(requestConfig.params || {})}`;
3471
+ };
3472
+ return {
3473
+ name: 'caching',
3474
+ version: '1.0.0',
3475
+ onBeforeRequest(requestConfig) {
3476
+ if (requestConfig.method.toUpperCase() === 'GET') {
3477
+ const key = generateKey(requestConfig);
3478
+ const cached = cache.get(key);
3479
+ if (cached && Date.now() < cached.expiresAt) ;
3480
+ }
3481
+ return requestConfig;
3482
+ },
3483
+ onAfterResponse(response) {
3484
+ const key = `GET:${response.status}`;
3485
+ cache.set(key, {
3486
+ data: response.data,
3487
+ expiresAt: Date.now() + ttl,
3488
+ });
3489
+ return response;
3490
+ },
3491
+ onDestroy() {
3492
+ cache.clear();
3493
+ },
3494
+ };
3495
+ }
3496
+ function createRateLimitPlugin(config = {}) {
3497
+ const maxRequestsPerSecond = config.maxRequestsPerSecond || 10;
3498
+ const requests = [];
3499
+ return {
3500
+ name: 'rateLimit',
3501
+ version: '1.0.0',
3502
+ async onBeforeRequest(requestConfig) {
3503
+ const now = Date.now();
3504
+ const oneSecondAgo = now - 1000;
3505
+ while (requests.length > 0 && requests[0] !== undefined && requests[0] < oneSecondAgo) {
3506
+ requests.shift();
3507
+ }
3508
+ if (requests.length >= maxRequestsPerSecond) {
3509
+ if (config.onRateLimitExceeded) {
3510
+ config.onRateLimitExceeded();
3511
+ }
3512
+ const oldestRequest = requests[0];
3513
+ if (oldestRequest !== undefined) {
3514
+ const waitTime = oldestRequest + 1000 - now;
3515
+ if (waitTime > 0) {
3516
+ await new Promise(resolve => setTimeout(resolve, waitTime));
3517
+ }
3518
+ }
3519
+ }
3520
+ requests.push(now);
3521
+ return requestConfig;
3522
+ },
3523
+ };
3524
+ }
3525
+
2802
3526
  function createEtsyClient(config) {
2803
3527
  return new EtsyClient(config);
2804
3528
  }
@@ -2824,5 +3548,5 @@ function getLibraryInfo() {
2824
3548
  };
2825
3549
  }
2826
3550
 
2827
- export { AuthHelper, BatchQueryExecutor, BulkOperationManager, COMMON_SCOPE_COMBINATIONS, CacheWithInvalidation, CreateListingSchema, DEFAULT_RETRY_CONFIG, ETSY_SCOPES, EncryptedFileTokenStorage, EtsyApiError, EtsyAuthError, EtsyClient, EtsyRateLimitError, EtsyRateLimiter, EtsyWebhookHandler, FieldValidator, FileTokenStorage, LFUCache, LIBRARY_NAME, LRUCache, ListingQueryBuilder, LocalStorageTokenStorage, MemoryTokenStorage, PaginatedResults, ReceiptQueryBuilder, RedisCacheStorage, RetryManager, SessionStorageTokenStorage, TokenManager, UpdateListingSchema, UpdateShopSchema, VERSION, ValidationException, Validator, WebhookSecurity, combineValidators, createAuthHelper, createBatchQuery, createBulkOperationManager, createCacheStorage, createCacheWithInvalidation, createCodeChallenge, createDefaultTokenStorage, createEtsyClient, createListingQuery, createPaginatedResults, createRateLimiter, createReceiptQuery, createRedisCacheStorage, createTokenManager, createValidator, createWebhookHandler, createWebhookSecurity, decryptAES256GCM, EtsyClient as default, defaultRateLimiter, deriveKeyFromPassword, encryptAES256GCM, executeBulkOperation, field, generateCodeVerifier, generateEncryptionKey, generateRandomBase64Url, generateState, getAvailableStorage, getEnvironmentInfo, getLibraryInfo, hasLocalStorage, hasSessionStorage, isBrowser$1 as isBrowser, isNode$1 as isNode, sha256, sha256Base64Url, validate, validateEncryptionKey, validateOrThrow, withQueryBuilder, withRetry };
3551
+ export { AuthHelper, BatchQueryExecutor, BulkOperationManager, COMMON_SCOPE_COMBINATIONS, CacheWithInvalidation, CreateListingSchema, DEFAULT_RETRY_CONFIG, ETSY_SCOPES, EncryptedFileTokenStorage, EtsyApiError, EtsyAuthError, EtsyClient, EtsyRateLimitError, EtsyRateLimiter, EtsyWebhookHandler, FieldValidator, FileTokenStorage, GlobalRequestQueue, LFUCache, LIBRARY_NAME, LRUCache, ListingQueryBuilder, LocalStorageTokenStorage, MemoryTokenStorage, PaginatedResults, PluginManager, ReceiptQueryBuilder, RedisCacheStorage, RetryManager, SecureTokenStorage, SessionStorageTokenStorage, TokenManager, UpdateListingSchema, UpdateShopSchema, VERSION, ValidationException, Validator, WebhookSecurity, combineValidators, createAnalyticsPlugin, createAuthHelper, createBatchQuery, createBulkOperationManager, createCacheStorage, createCacheWithInvalidation, createCachingPlugin, createCodeChallenge, createDefaultTokenStorage, createEtsyClient, createListingQuery, createLoggingPlugin, createPaginatedResults, createRateLimitPlugin, createRateLimiter, createReceiptQuery, createRedisCacheStorage, createRetryPlugin, createTokenManager, createValidator, createWebhookHandler, createWebhookSecurity, decryptAES256GCM, EtsyClient as default, defaultRateLimiter, deriveKeyFromPassword, encryptAES256GCM, executeBulkOperation, field, generateCodeVerifier, generateEncryptionKey, generateRandomBase64Url, generateState, getAvailableStorage, getEnvironmentInfo, getGlobalQueue, getLibraryInfo, hasLocalStorage, hasSessionStorage, isBrowser$1 as isBrowser, isNode$1 as isNode, isSecureStorageSupported, sha256, sha256Base64Url, validate, validateEncryptionKey, validateOrThrow, withQueryBuilder, withQueue, withRetry };
2828
3552
  //# sourceMappingURL=node.esm.js.map