ebay-mcp 1.8.5 → 1.8.8
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/README.md +1 -2
- package/build/api/client-trading.js +12 -3
- package/build/api/communication/feedback.js +6 -31
- package/build/api/communication/message.js +21 -34
- package/build/api/communication/negotiation.js +17 -37
- package/build/api/communication/notification.js +14 -42
- package/build/api/communication/shared.js +71 -0
- package/build/api/other/vero.js +2 -7
- package/build/api/shared/query-params.js +35 -0
- package/build/api/trading/trading.js +20 -7
- package/build/auth/oauth.js +9 -8
- package/build/auth/scope-utils.js +7 -1
- package/build/auth/token-verifier.js +9 -4
- package/build/config/environment.js +1 -1
- package/build/index.js +1 -1
- package/build/scripts/auto-setup.js +43 -20
- package/build/scripts/diagnostics.js +5 -31
- package/build/scripts/interactive-setup.js +16 -27
- package/build/scripts/setup-shared.js +28 -0
- package/build/scripts/setup.js +36 -117
- package/build/server-http.js +4 -2
- package/build/utils/account-management/account.js +1 -7
- package/build/utils/communication/notification.js +1 -7
- package/build/utils/env-parser.js +30 -0
- package/build/utils/other/edelivery.js +1 -7
- package/build/utils/schema-helpers.js +13 -0
- package/build/utils/scope-helper.js +2 -2
- package/build/utils/setup-validator.js +1 -31
- package/build/utils/setup-wizard.js +172 -0
- package/build/utils/type-guards.js +6 -0
- package/build/utils/version.js +13 -1
- package/package.json +15 -3
- package/build/types/application-settings/developerAnalyticsV1BetaOas3.js +0 -5
- package/build/types/application-settings/developerClientRegistrationV1Oas3.js +0 -5
- package/build/types/application-settings/developerKeyManagementV1Oas3.js +0 -5
- package/build/types/sell-apps/account-management/sellAccountV1Oas3.js +0 -5
- package/build/types/sell-apps/analytics-and-report/sellAnalyticsV1Oas3.js +0 -5
- package/build/types/sell-apps/communication/commerceFeedbackV1BetaOas3.js +0 -5
- package/build/types/sell-apps/communication/commerceMessageV1Oas3.js +0 -5
- package/build/types/sell-apps/communication/commerceNotificationV1Oas3.js +0 -5
- package/build/types/sell-apps/communication/sellNegotiationV1Oas3.js +0 -5
- package/build/types/sell-apps/listing-management/sellInventoryV1Oas3.js +0 -5
- package/build/types/sell-apps/listing-metadata/sellMetadataV1Oas3.js +0 -5
- package/build/types/sell-apps/markeitng-and-promotions/sellMarketingV1Oas3.js +0 -5
- package/build/types/sell-apps/markeitng-and-promotions/sellRecommendationV1Oas3.js +0 -5
- package/build/types/sell-apps/order-management/sellFulfillmentV1Oas3.js +0 -5
- package/build/types/sell-apps/other-apis/commerceIdentityV1Oas3.js +0 -5
- package/build/types/sell-apps/other-apis/commerceTranslationV1BetaOas3.js +0 -5
- package/build/types/sell-apps/other-apis/commerceVeroV1Oas3.js +0 -5
- package/build/types/sell-apps/other-apis/sellComplianceV1Oas3.js +0 -5
- package/build/types/sell-apps/other-apis/sellEdeliveryInternationalShippingOas3.js +0 -5
- package/build/types/sell-apps/other-apis/sellMarketingV1Oas3.js +0 -5
- package/build/types/sell-apps/other-apis/sellRecommendationV1Oas3.js +0 -5
package/README.md
CHANGED
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
[](src/tools/)
|
|
9
9
|
[](LICENSE)
|
|
10
10
|
[](CONTRIBUTING.md)
|
|
11
|
-
[](https://github.com/YosefHayim/grimoire)
|
|
12
11
|
|
|
13
12
|
[](https://mseep.ai/app/yosefhayim-ebay-api-mcp-server)
|
|
14
13
|
<a href="https://www.buymeacoffee.com/yosefhayim" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>
|
|
@@ -158,7 +157,7 @@ The interactive setup wizard handles everything for you:
|
|
|
158
157
|
npm run setup
|
|
159
158
|
```
|
|
160
159
|
|
|
161
|
-
>
|
|
160
|
+
> Built with a standard Node CLI prompt stack for reliable interactive setup.
|
|
162
161
|
|
|
163
162
|
The wizard will:
|
|
164
163
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import axios from 'axios';
|
|
2
2
|
import { XMLBuilder, XMLParser } from 'fast-xml-parser';
|
|
3
3
|
import { apiLogger } from '../utils/logger.js';
|
|
4
|
+
import { isRecord } from '../utils/type-guards.js';
|
|
4
5
|
const COMPAT_LEVEL = '1451';
|
|
5
6
|
const SITE_ID = '0';
|
|
6
7
|
export class TradingApiClient {
|
|
@@ -42,7 +43,7 @@ export class TradingApiClient {
|
|
|
42
43
|
suppressEmptyNode: true,
|
|
43
44
|
});
|
|
44
45
|
}
|
|
45
|
-
|
|
46
|
+
getTradingBaseUrl() {
|
|
46
47
|
return this.baseUrl;
|
|
47
48
|
}
|
|
48
49
|
async execute(callName, params) {
|
|
@@ -75,12 +76,20 @@ export class TradingApiClient {
|
|
|
75
76
|
}
|
|
76
77
|
let parsed;
|
|
77
78
|
try {
|
|
78
|
-
|
|
79
|
+
const parsedValue = this.parser.parse(response.data);
|
|
80
|
+
if (!isRecord(parsedValue)) {
|
|
81
|
+
throw new Error('Trading API response must be an object');
|
|
82
|
+
}
|
|
83
|
+
parsed = parsedValue;
|
|
79
84
|
}
|
|
80
85
|
catch (e) {
|
|
81
86
|
throw new Error(`Failed to parse Trading API ${callName} response: ${e instanceof Error ? e.message : String(e)}`);
|
|
82
87
|
}
|
|
83
|
-
const
|
|
88
|
+
const resultValue = parsed[responseTag] || parsed;
|
|
89
|
+
if (!isRecord(resultValue)) {
|
|
90
|
+
throw new Error(`Trading API ${callName} response payload is not an object`);
|
|
91
|
+
}
|
|
92
|
+
const result = resultValue;
|
|
84
93
|
// Log warnings without failing
|
|
85
94
|
if (result.Ack === 'Warning') {
|
|
86
95
|
apiLogger.warn(`Trading API ${callName} returned warnings`, {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getPaginatedWithContextError, getPathWithContextError } from './shared.js';
|
|
1
2
|
/**
|
|
2
3
|
* Feedback API - Manage buyer and seller feedback
|
|
3
4
|
* Based on: docs/sell-apps/communication/commerce_feedback_v1_beta_oas3.json
|
|
@@ -11,34 +12,13 @@ export class FeedbackApi {
|
|
|
11
12
|
/**
|
|
12
13
|
* Get items awaiting feedback
|
|
13
14
|
* Endpoint: GET /awaiting_feedback
|
|
15
|
+
* @param filter API filter expression.
|
|
16
|
+
* @param limit Maximum number of records to return.
|
|
17
|
+
* @param offset Zero-based pagination offset.
|
|
14
18
|
* @throws Error if the request fails
|
|
15
19
|
*/
|
|
16
20
|
async getAwaitingFeedback(filter, limit, offset) {
|
|
17
|
-
|
|
18
|
-
if (filter !== undefined) {
|
|
19
|
-
if (typeof filter !== 'string') {
|
|
20
|
-
throw new Error('filter must be a string when provided');
|
|
21
|
-
}
|
|
22
|
-
params.filter = filter;
|
|
23
|
-
}
|
|
24
|
-
if (limit !== undefined) {
|
|
25
|
-
if (typeof limit !== 'number' || limit < 1) {
|
|
26
|
-
throw new Error('limit must be a positive number when provided');
|
|
27
|
-
}
|
|
28
|
-
params.limit = limit;
|
|
29
|
-
}
|
|
30
|
-
if (offset !== undefined) {
|
|
31
|
-
if (typeof offset !== 'number' || offset < 0) {
|
|
32
|
-
throw new Error('offset must be a non-negative number when provided');
|
|
33
|
-
}
|
|
34
|
-
params.offset = offset;
|
|
35
|
-
}
|
|
36
|
-
try {
|
|
37
|
-
return await this.client.get(`${this.basePath}/awaiting_feedback`, params);
|
|
38
|
-
}
|
|
39
|
-
catch (error) {
|
|
40
|
-
throw new Error(`Failed to get awaiting feedback: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
41
|
-
}
|
|
21
|
+
return await getPaginatedWithContextError(this.client, `${this.basePath}/awaiting_feedback`, 'Failed to get awaiting feedback', filter, limit, offset);
|
|
42
22
|
}
|
|
43
23
|
/**
|
|
44
24
|
* Get feedback for a transaction
|
|
@@ -64,12 +44,7 @@ export class FeedbackApi {
|
|
|
64
44
|
* @throws Error if the request fails
|
|
65
45
|
*/
|
|
66
46
|
async getFeedbackRatingSummary() {
|
|
67
|
-
|
|
68
|
-
return await this.client.get(`${this.basePath}/feedback_rating_summary`);
|
|
69
|
-
}
|
|
70
|
-
catch (error) {
|
|
71
|
-
throw new Error(`Failed to get feedback rating summary: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
72
|
-
}
|
|
47
|
+
return await getPathWithContextError(this.client, `${this.basePath}/feedback_rating_summary`, 'Failed to get feedback rating summary');
|
|
73
48
|
}
|
|
74
49
|
/**
|
|
75
50
|
* Leave feedback for a buyer
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { assertRequiredString, buildPaginatedQueryParams, getPathWithContextError, getWithContextError, } from './shared.js';
|
|
1
2
|
/**
|
|
2
3
|
* Message API - Buyer-seller messaging
|
|
3
4
|
* Based on: docs/sell-apps/communication/commerce_message_v1_oas3.json
|
|
@@ -27,50 +28,36 @@ export class MessageApi {
|
|
|
27
28
|
/**
|
|
28
29
|
* Get conversations
|
|
29
30
|
* Endpoint: GET /conversation
|
|
31
|
+
* @param filter API filter expression.
|
|
32
|
+
* @param limit Maximum number of records to return.
|
|
33
|
+
* @param offset Zero-based pagination offset.
|
|
30
34
|
* @throws Error if the request fails
|
|
31
35
|
*/
|
|
32
36
|
async getConversations(filter, limit, offset) {
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (typeof offset !== 'number' || offset < 0) {
|
|
48
|
-
throw new Error('offset must be a non-negative number when provided');
|
|
49
|
-
}
|
|
50
|
-
params.offset = offset;
|
|
51
|
-
}
|
|
52
|
-
try {
|
|
53
|
-
return await this.client.get(`${this.basePath}/conversation`, params);
|
|
54
|
-
}
|
|
55
|
-
catch (error) {
|
|
56
|
-
throw new Error(`Failed to get conversations: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
57
|
-
}
|
|
37
|
+
const conversationPath = `${this.basePath}/conversation`;
|
|
38
|
+
const queryParams = this.buildConversationQueryParams(filter, limit, offset);
|
|
39
|
+
return await getWithContextError(this.client, conversationPath, queryParams, 'Failed to get conversations');
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Build query params used by conversation listing endpoints.
|
|
43
|
+
*
|
|
44
|
+
* @param filter API filter expression.
|
|
45
|
+
* @param limit Maximum number of records to return.
|
|
46
|
+
* @param offset Zero-based pagination offset.
|
|
47
|
+
* @returns Validated conversation query params.
|
|
48
|
+
*/
|
|
49
|
+
buildConversationQueryParams(filter, limit, offset) {
|
|
50
|
+
return buildPaginatedQueryParams(filter, limit, offset);
|
|
58
51
|
}
|
|
59
52
|
/**
|
|
60
53
|
* Get a specific conversation
|
|
61
54
|
* Endpoint: GET /conversation/{conversation_id}
|
|
55
|
+
* @param conversationId Conversation identifier.
|
|
62
56
|
* @throws Error if required parameters are missing or invalid
|
|
63
57
|
*/
|
|
64
58
|
async getConversation(conversationId) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
try {
|
|
69
|
-
return await this.client.get(`${this.basePath}/conversation/${conversationId}`);
|
|
70
|
-
}
|
|
71
|
-
catch (error) {
|
|
72
|
-
throw new Error(`Failed to get conversation: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
73
|
-
}
|
|
59
|
+
assertRequiredString(conversationId, 'conversationId');
|
|
60
|
+
return await getPathWithContextError(this.client, `${this.basePath}/conversation/${conversationId}`, 'Failed to get conversation');
|
|
74
61
|
}
|
|
75
62
|
/**
|
|
76
63
|
* Send a message
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { assertRequiredString, buildPaginatedQueryParams, getPathWithContextError, getWithContextError, } from './shared.js';
|
|
2
|
+
import { buildTruthyPaginatedParams } from '../shared/query-params.js';
|
|
1
3
|
/**
|
|
2
4
|
* Negotiation API - Buyer-seller negotiations and offers
|
|
3
5
|
* Based on: docs/sell-apps/communication/sell_negotiation_v1_oas3.json
|
|
@@ -11,30 +13,17 @@ export class NegotiationApi {
|
|
|
11
13
|
/**
|
|
12
14
|
* Find eligible items for a seller-initiated offer
|
|
13
15
|
* Endpoint: GET /find_eligible_items
|
|
16
|
+
* @param filter API filter expression.
|
|
17
|
+
* @param limit Maximum number of records to return.
|
|
18
|
+
* @param offset Zero-based pagination offset.
|
|
14
19
|
* @throws Error if the request fails
|
|
15
20
|
*/
|
|
16
21
|
async findEligibleItems(filter, limit, offset) {
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
if (typeof filter !== 'string') {
|
|
20
|
-
throw new Error('filter must be a string when provided');
|
|
21
|
-
}
|
|
22
|
-
params.filter = filter;
|
|
23
|
-
}
|
|
24
|
-
if (limit !== undefined) {
|
|
25
|
-
if (typeof limit !== 'number' || limit < 1) {
|
|
26
|
-
throw new Error('limit must be a positive number when provided');
|
|
27
|
-
}
|
|
28
|
-
params.limit = limit;
|
|
29
|
-
}
|
|
30
|
-
if (offset !== undefined) {
|
|
31
|
-
if (typeof offset !== 'number' || offset < 0) {
|
|
32
|
-
throw new Error('offset must be a non-negative number when provided');
|
|
33
|
-
}
|
|
34
|
-
params.offset = offset;
|
|
35
|
-
}
|
|
22
|
+
const eligibleItemsPath = `${this.basePath}/find_eligible_items`;
|
|
23
|
+
const queryParams = buildPaginatedQueryParams(filter, limit, offset);
|
|
36
24
|
try {
|
|
37
|
-
|
|
25
|
+
const response = await this.client.get(eligibleItemsPath, queryParams);
|
|
26
|
+
return response;
|
|
38
27
|
}
|
|
39
28
|
catch (error) {
|
|
40
29
|
throw new Error(`Failed to find eligible items: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
@@ -58,17 +47,14 @@ export class NegotiationApi {
|
|
|
58
47
|
}
|
|
59
48
|
/**
|
|
60
49
|
* Get offers to buyers (Best Offers)
|
|
50
|
+
* @param filter API filter expression.
|
|
51
|
+
* @param limit Maximum number of records to return.
|
|
52
|
+
* @param offset Zero-based pagination offset.
|
|
61
53
|
* @deprecated This method does not match any endpoint in the OpenAPI spec
|
|
62
54
|
*/
|
|
63
55
|
async getOffersToBuyers(filter, limit, offset) {
|
|
64
|
-
const params =
|
|
65
|
-
|
|
66
|
-
params.filter = filter;
|
|
67
|
-
if (limit)
|
|
68
|
-
params.limit = limit;
|
|
69
|
-
if (offset)
|
|
70
|
-
params.offset = offset;
|
|
71
|
-
return await this.client.get(`${this.basePath}/offer`, params);
|
|
56
|
+
const params = buildTruthyPaginatedParams(filter, limit, offset);
|
|
57
|
+
return await getWithContextError(this.client, `${this.basePath}/offer`, params, 'Failed to get offers to buyers');
|
|
72
58
|
}
|
|
73
59
|
/**
|
|
74
60
|
* Get offers for listing (alias for getOffersToBuyers)
|
|
@@ -81,17 +67,11 @@ export class NegotiationApi {
|
|
|
81
67
|
/**
|
|
82
68
|
* Get a specific offer
|
|
83
69
|
* Endpoint: GET /offer/{offerId}
|
|
70
|
+
* @param offerId Offer identifier.
|
|
84
71
|
* @throws Error if required parameters are missing or invalid
|
|
85
72
|
*/
|
|
86
73
|
async getOffer(offerId) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
try {
|
|
91
|
-
return await this.client.get(`${this.basePath}/offer/${offerId}`);
|
|
92
|
-
}
|
|
93
|
-
catch (error) {
|
|
94
|
-
throw new Error(`Failed to get offer: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
95
|
-
}
|
|
74
|
+
assertRequiredString(offerId, 'offerId');
|
|
75
|
+
return await getPathWithContextError(this.client, `${this.basePath}/offer/${offerId}`, 'Failed to get offer');
|
|
96
76
|
}
|
|
97
77
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { assertRequiredString, getPathWithContextError } from './shared.js';
|
|
1
2
|
/**
|
|
2
3
|
* Notification API - Event notifications and subscriptions
|
|
3
4
|
* Based on: docs/sell-apps/communication/commerce_notification_v1_oas3.json
|
|
@@ -10,30 +11,19 @@ export class NotificationApi {
|
|
|
10
11
|
}
|
|
11
12
|
/**
|
|
12
13
|
* Get public key for validating notifications
|
|
14
|
+
* @param publicKeyId Public key identifier.
|
|
13
15
|
* @throws Error if required parameters are missing or invalid
|
|
14
16
|
*/
|
|
15
17
|
async getPublicKey(publicKeyId) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
try {
|
|
20
|
-
return await this.client.get(`${this.basePath}/public_key/${publicKeyId}`);
|
|
21
|
-
}
|
|
22
|
-
catch (error) {
|
|
23
|
-
throw new Error(`Failed to get public key: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
24
|
-
}
|
|
18
|
+
assertRequiredString(publicKeyId, 'publicKeyId');
|
|
19
|
+
return await getPathWithContextError(this.client, `${this.basePath}/public_key/${publicKeyId}`, 'Failed to get public key');
|
|
25
20
|
}
|
|
26
21
|
/**
|
|
27
22
|
* Get notification config
|
|
28
23
|
* @throws Error if the request fails
|
|
29
24
|
*/
|
|
30
25
|
async getConfig() {
|
|
31
|
-
|
|
32
|
-
return await this.client.get(`${this.basePath}/config`);
|
|
33
|
-
}
|
|
34
|
-
catch (error) {
|
|
35
|
-
throw new Error(`Failed to get config: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
36
|
-
}
|
|
26
|
+
return await getPathWithContextError(this.client, `${this.basePath}/config`, 'Failed to get config');
|
|
37
27
|
}
|
|
38
28
|
/**
|
|
39
29
|
* Update notification config
|
|
@@ -69,18 +59,12 @@ export class NotificationApi {
|
|
|
69
59
|
}
|
|
70
60
|
/**
|
|
71
61
|
* Get destination
|
|
62
|
+
* @param destinationId Destination identifier.
|
|
72
63
|
* @throws Error if required parameters are missing or invalid
|
|
73
64
|
*/
|
|
74
65
|
async getDestination(destinationId) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
try {
|
|
79
|
-
return await this.client.get(`${this.basePath}/destination/${destinationId}`);
|
|
80
|
-
}
|
|
81
|
-
catch (error) {
|
|
82
|
-
throw new Error(`Failed to get destination: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
83
|
-
}
|
|
66
|
+
assertRequiredString(destinationId, 'destinationId');
|
|
67
|
+
return await getPathWithContextError(this.client, `${this.basePath}/destination/${destinationId}`, 'Failed to get destination');
|
|
84
68
|
}
|
|
85
69
|
/**
|
|
86
70
|
* Create destination
|
|
@@ -175,18 +159,12 @@ export class NotificationApi {
|
|
|
175
159
|
/**
|
|
176
160
|
* Get a subscription
|
|
177
161
|
* Endpoint: GET /subscription/{subscription_id}
|
|
162
|
+
* @param subscriptionId Subscription identifier.
|
|
178
163
|
* @throws Error if required parameters are missing or invalid
|
|
179
164
|
*/
|
|
180
165
|
async getSubscription(subscriptionId) {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
}
|
|
184
|
-
try {
|
|
185
|
-
return await this.client.get(`${this.basePath}/subscription/${subscriptionId}`);
|
|
186
|
-
}
|
|
187
|
-
catch (error) {
|
|
188
|
-
throw new Error(`Failed to get subscription: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
189
|
-
}
|
|
166
|
+
assertRequiredString(subscriptionId, 'subscriptionId');
|
|
167
|
+
return await getPathWithContextError(this.client, `${this.basePath}/subscription/${subscriptionId}`, 'Failed to get subscription');
|
|
190
168
|
}
|
|
191
169
|
/**
|
|
192
170
|
* Update a subscription
|
|
@@ -274,18 +252,12 @@ export class NotificationApi {
|
|
|
274
252
|
/**
|
|
275
253
|
* Get a topic
|
|
276
254
|
* Endpoint: GET /topic/{topic_id}
|
|
255
|
+
* @param topicId Topic identifier.
|
|
277
256
|
* @throws Error if required parameters are missing or invalid
|
|
278
257
|
*/
|
|
279
258
|
async getTopic(topicId) {
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
}
|
|
283
|
-
try {
|
|
284
|
-
return await this.client.get(`${this.basePath}/topic/${topicId}`);
|
|
285
|
-
}
|
|
286
|
-
catch (error) {
|
|
287
|
-
throw new Error(`Failed to get topic: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
288
|
-
}
|
|
259
|
+
assertRequiredString(topicId, 'topicId');
|
|
260
|
+
return await getPathWithContextError(this.client, `${this.basePath}/topic/${topicId}`, 'Failed to get topic');
|
|
289
261
|
}
|
|
290
262
|
/**
|
|
291
263
|
* Get all topics
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { buildValidatedPaginatedParams } from '../shared/query-params.js';
|
|
2
|
+
/**
|
|
3
|
+
* Build validated pagination query params.
|
|
4
|
+
*
|
|
5
|
+
* @param filter API filter expression.
|
|
6
|
+
* @param limit Maximum number of records.
|
|
7
|
+
* @param offset Zero-based pagination offset.
|
|
8
|
+
* @returns Query parameter object for paginated GET calls.
|
|
9
|
+
*/
|
|
10
|
+
export function buildPaginatedQueryParams(filter, limit, offset) {
|
|
11
|
+
return buildValidatedPaginatedParams(filter, limit, offset);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Validate that a required parameter is a non-empty string.
|
|
15
|
+
*
|
|
16
|
+
* @param value Value to validate.
|
|
17
|
+
* @param paramName Parameter name used in error text.
|
|
18
|
+
*/
|
|
19
|
+
export function assertRequiredString(value, paramName) {
|
|
20
|
+
if (!value || typeof value !== 'string') {
|
|
21
|
+
throw new Error(`${paramName} is required and must be a string`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Perform a GET request and wrap errors with endpoint-specific context.
|
|
26
|
+
*
|
|
27
|
+
* @param client Configured eBay API client.
|
|
28
|
+
* @param path Endpoint path to request.
|
|
29
|
+
* @param failureMessage Prefix text for thrown error messages.
|
|
30
|
+
* @returns API response payload.
|
|
31
|
+
*/
|
|
32
|
+
export async function getPathWithContextError(client, path, failureMessage) {
|
|
33
|
+
try {
|
|
34
|
+
return await client.get(path);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
throw new Error(`${failureMessage}: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Perform a GET request with query params and wrap errors with endpoint-specific context.
|
|
42
|
+
*
|
|
43
|
+
* @param client Configured eBay API client.
|
|
44
|
+
* @param path Endpoint path to request.
|
|
45
|
+
* @param params Query parameters sent with the request.
|
|
46
|
+
* @param failureMessage Prefix text for thrown error messages.
|
|
47
|
+
* @returns API response payload.
|
|
48
|
+
*/
|
|
49
|
+
export async function getWithContextError(client, path, params, failureMessage) {
|
|
50
|
+
try {
|
|
51
|
+
return await client.get(path, params);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
throw new Error(`${failureMessage}: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Perform a paginated GET request with validated filter/limit/offset params.
|
|
59
|
+
*
|
|
60
|
+
* @param client Configured eBay API client.
|
|
61
|
+
* @param path Endpoint path to request.
|
|
62
|
+
* @param failureMessage Prefix text for thrown error messages.
|
|
63
|
+
* @param filter API filter expression.
|
|
64
|
+
* @param limit Maximum number of records.
|
|
65
|
+
* @param offset Zero-based pagination offset.
|
|
66
|
+
* @returns API response payload.
|
|
67
|
+
*/
|
|
68
|
+
export async function getPaginatedWithContextError(client, path, failureMessage, filter, limit, offset) {
|
|
69
|
+
const params = buildPaginatedQueryParams(filter, limit, offset);
|
|
70
|
+
return await getWithContextError(client, path, params, failureMessage);
|
|
71
|
+
}
|
package/build/api/other/vero.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { buildTruthyPaginatedParams } from '../shared/query-params.js';
|
|
1
2
|
/**
|
|
2
3
|
* VERO API - Verified Rights Owner program
|
|
3
4
|
* Based on: docs/sell-apps/other-apis/commerce_vero_v1_oas3.json
|
|
@@ -24,13 +25,7 @@ export class VeroApi {
|
|
|
24
25
|
* Get VERO report items (listings reported for infringement)
|
|
25
26
|
*/
|
|
26
27
|
async getVeroReportItems(filter, limit, offset) {
|
|
27
|
-
const params =
|
|
28
|
-
if (filter)
|
|
29
|
-
params.filter = filter;
|
|
30
|
-
if (limit)
|
|
31
|
-
params.limit = limit;
|
|
32
|
-
if (offset)
|
|
33
|
-
params.offset = offset;
|
|
28
|
+
const params = buildTruthyPaginatedParams(filter, limit, offset);
|
|
34
29
|
return await this.client.get(`${this.basePath}/vero_report_items`, params);
|
|
35
30
|
}
|
|
36
31
|
/**
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export function buildValidatedPaginatedParams(filter, limit, offset) {
|
|
2
|
+
const params = {};
|
|
3
|
+
if (filter !== undefined) {
|
|
4
|
+
if (typeof filter !== 'string') {
|
|
5
|
+
throw new Error('filter must be a string when provided');
|
|
6
|
+
}
|
|
7
|
+
params.filter = filter;
|
|
8
|
+
}
|
|
9
|
+
if (limit !== undefined) {
|
|
10
|
+
if (typeof limit !== 'number' || limit < 1) {
|
|
11
|
+
throw new Error('limit must be a positive number when provided');
|
|
12
|
+
}
|
|
13
|
+
params.limit = limit;
|
|
14
|
+
}
|
|
15
|
+
if (offset !== undefined) {
|
|
16
|
+
if (typeof offset !== 'number' || offset < 0) {
|
|
17
|
+
throw new Error('offset must be a non-negative number when provided');
|
|
18
|
+
}
|
|
19
|
+
params.offset = offset;
|
|
20
|
+
}
|
|
21
|
+
return params;
|
|
22
|
+
}
|
|
23
|
+
export function buildTruthyPaginatedParams(filter, limit, offset) {
|
|
24
|
+
const params = {};
|
|
25
|
+
if (filter) {
|
|
26
|
+
params.filter = filter;
|
|
27
|
+
}
|
|
28
|
+
if (limit) {
|
|
29
|
+
params.limit = limit;
|
|
30
|
+
}
|
|
31
|
+
if (offset) {
|
|
32
|
+
params.offset = offset;
|
|
33
|
+
}
|
|
34
|
+
return params;
|
|
35
|
+
}
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
import { isRecord } from '../../utils/type-guards.js';
|
|
2
|
+
function asRecord(value) {
|
|
3
|
+
return isRecord(value) ? value : undefined;
|
|
4
|
+
}
|
|
5
|
+
function asRecordArray(value) {
|
|
6
|
+
if (!Array.isArray(value)) {
|
|
7
|
+
return [];
|
|
8
|
+
}
|
|
9
|
+
return value.filter((entry) => typeof entry === 'object' && entry !== null && !Array.isArray(entry));
|
|
10
|
+
}
|
|
1
11
|
export class TradingApi {
|
|
2
12
|
client;
|
|
3
13
|
constructor(client) {
|
|
@@ -13,13 +23,16 @@ export class TradingApi {
|
|
|
13
23
|
},
|
|
14
24
|
},
|
|
15
25
|
});
|
|
16
|
-
const activeList = result.ActiveList;
|
|
17
|
-
const itemArray = activeList?.ItemArray;
|
|
18
|
-
const items = itemArray?.Item
|
|
19
|
-
const pagination = activeList?.PaginationResult;
|
|
26
|
+
const activeList = asRecord(result.ActiveList);
|
|
27
|
+
const itemArray = asRecord(activeList?.ItemArray);
|
|
28
|
+
const items = asRecordArray(itemArray?.Item);
|
|
29
|
+
const pagination = asRecord(activeList?.PaginationResult);
|
|
20
30
|
const listings = items.map((item) => {
|
|
21
|
-
const sellingStatus = item.SellingStatus;
|
|
22
|
-
const
|
|
31
|
+
const sellingStatus = asRecord(item.SellingStatus);
|
|
32
|
+
const currentPriceRaw = sellingStatus?.CurrentPrice;
|
|
33
|
+
const currentPrice = typeof currentPriceRaw === 'number' || isRecord(currentPriceRaw)
|
|
34
|
+
? currentPriceRaw
|
|
35
|
+
: undefined;
|
|
23
36
|
const priceValue = typeof currentPrice === 'object' && currentPrice !== null
|
|
24
37
|
? Number(currentPrice['#text'] || 0)
|
|
25
38
|
: Number(currentPrice || 0);
|
|
@@ -47,7 +60,7 @@ export class TradingApi {
|
|
|
47
60
|
ItemID: itemId,
|
|
48
61
|
DetailLevel: 'ReturnAll',
|
|
49
62
|
});
|
|
50
|
-
const items = result.Item;
|
|
63
|
+
const items = asRecordArray(result.Item);
|
|
51
64
|
return items?.[0] || result;
|
|
52
65
|
}
|
|
53
66
|
async createListing(item) {
|
package/build/auth/oauth.js
CHANGED
|
@@ -208,7 +208,7 @@ export class EbayOAuthClient {
|
|
|
208
208
|
}
|
|
209
209
|
/**
|
|
210
210
|
* Exchange authorization code for user access token
|
|
211
|
-
*
|
|
211
|
+
* Persists received tokens to .env automatically
|
|
212
212
|
*/
|
|
213
213
|
async exchangeCodeForToken(code) {
|
|
214
214
|
if (!this.config.redirectUri) {
|
|
@@ -241,8 +241,11 @@ export class EbayOAuthClient {
|
|
|
241
241
|
userRefreshTokenExpiry: now + tokenData.refresh_token_expires_in * 1000,
|
|
242
242
|
scope: tokenData.scope,
|
|
243
243
|
};
|
|
244
|
-
//
|
|
245
|
-
|
|
244
|
+
// Persist tokens to .env so they survive process restarts.
|
|
245
|
+
updateEnvFile({
|
|
246
|
+
EBAY_USER_ACCESS_TOKEN: tokenData.access_token,
|
|
247
|
+
EBAY_USER_REFRESH_TOKEN: tokenData.refresh_token,
|
|
248
|
+
});
|
|
246
249
|
return tokenData;
|
|
247
250
|
}
|
|
248
251
|
catch (error) {
|
|
@@ -298,11 +301,9 @@ export class EbayOAuthClient {
|
|
|
298
301
|
const envUpdates = {
|
|
299
302
|
EBAY_USER_ACCESS_TOKEN: tokenData.access_token,
|
|
300
303
|
};
|
|
301
|
-
//
|
|
302
|
-
if (
|
|
303
|
-
|
|
304
|
-
envUpdates.EBAY_USER_REFRESH_TOKEN = tokenData.refresh_token;
|
|
305
|
-
// New refresh token updated silently
|
|
304
|
+
// Reconcile .env with the authoritative in-memory refresh token.
|
|
305
|
+
if (this.userTokens.userRefreshToken !== process.env.EBAY_USER_REFRESH_TOKEN) {
|
|
306
|
+
envUpdates.EBAY_USER_REFRESH_TOKEN = this.userTokens.userRefreshToken;
|
|
306
307
|
}
|
|
307
308
|
// Write updates to .env file
|
|
308
309
|
updateEnvFile(envUpdates);
|
|
@@ -272,7 +272,7 @@ export function getReadonlyScope(writeScope) {
|
|
|
272
272
|
/**
|
|
273
273
|
* Get scope description from scope name
|
|
274
274
|
*/
|
|
275
|
-
export function
|
|
275
|
+
export function getScopeTypeDescription(scope) {
|
|
276
276
|
// Extract the last part of the scope
|
|
277
277
|
const parts = scope.split('/');
|
|
278
278
|
const scopeType = parts[parts.length - 1];
|
|
@@ -302,3 +302,9 @@ export function getScopeDescription(scope) {
|
|
|
302
302
|
};
|
|
303
303
|
return descriptions[scopeType] || `Access to ${scopeType}`;
|
|
304
304
|
}
|
|
305
|
+
/**
|
|
306
|
+
* Backward-compatible alias for scope description lookup.
|
|
307
|
+
*/
|
|
308
|
+
export function getScopeDescription(scope) {
|
|
309
|
+
return getScopeTypeDescription(scope);
|
|
310
|
+
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* and JWT validation
|
|
4
4
|
*/
|
|
5
5
|
import axios from 'axios';
|
|
6
|
-
import
|
|
6
|
+
import { createRemoteJWKSet, jwtVerify } from 'jose';
|
|
7
7
|
export class TokenVerifier {
|
|
8
8
|
config;
|
|
9
9
|
metadata = null;
|
|
@@ -121,9 +121,9 @@ export class TokenVerifier {
|
|
|
121
121
|
}
|
|
122
122
|
try {
|
|
123
123
|
// Get JWKS from authorization server
|
|
124
|
-
const JWKS =
|
|
124
|
+
const JWKS = createRemoteJWKSet(new URL(this.metadata.jwks_uri));
|
|
125
125
|
// Verify JWT
|
|
126
|
-
const { payload } = await
|
|
126
|
+
const { payload } = await jwtVerify(token, JWKS, {
|
|
127
127
|
issuer: this.metadata.issuer,
|
|
128
128
|
audience: this.config.expectedAudience,
|
|
129
129
|
});
|
|
@@ -140,9 +140,14 @@ export class TokenVerifier {
|
|
|
140
140
|
throw new Error(`Missing required scopes. Required: ${this.config.requiredScopes.join(', ')}, Got: ${scopes.join(', ')}`);
|
|
141
141
|
}
|
|
142
142
|
}
|
|
143
|
+
const clientId = typeof payload.client_id === 'string'
|
|
144
|
+
? payload.client_id
|
|
145
|
+
: typeof payload.azp === 'string'
|
|
146
|
+
? payload.azp
|
|
147
|
+
: 'unknown';
|
|
143
148
|
return {
|
|
144
149
|
token,
|
|
145
|
-
clientId
|
|
150
|
+
clientId,
|
|
146
151
|
scopes,
|
|
147
152
|
expiresAt: payload.exp,
|
|
148
153
|
audience: payload.aud,
|