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

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