ebay-mcp-remote-edition 3.1.1 → 3.2.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/LICENSE +1 -1
- package/README.md +47 -2
- package/build/auth/multi-user-store.js +40 -8
- package/build/auth/oauth.js +31 -5
- package/build/config/environment.js +57 -14
- package/build/scripts/{run-with-local-env.js → env-check.js} +1 -1
- package/build/server-http.js +519 -95
- package/build/tools/chat-tools.js +50 -0
- package/build/tools/index.js +45 -4
- package/build/utils/version.js +1 -1
- package/build/validation/providers/chart.js +3 -0
- package/build/validation/providers/ebay-sold.js +171 -0
- package/build/validation/providers/ebay.js +112 -0
- package/build/validation/providers/social.js +7 -0
- package/build/validation/recommendation.js +84 -0
- package/build/validation/run-validation.js +140 -0
- package/build/validation/schemas.js +96 -0
- package/build/validation/types.js +1 -0
- package/package.json +10 -10
package/LICENSE
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c)
|
|
3
|
+
Copyright (c) 2026 Kenyatta Naji Johnson-Adams
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
package/README.md
CHANGED
|
@@ -101,6 +101,51 @@ EBAY_LOCAL_TLS_CERT_PATH=/path/to/ebay-local.test.pem
|
|
|
101
101
|
EBAY_LOCAL_TLS_KEY_PATH=/path/to/ebay-local.test-key.pem
|
|
102
102
|
```
|
|
103
103
|
|
|
104
|
+
#### ⚠️ Trust the mkcert CA in Node.js (required for MCP clients like Cline)
|
|
105
|
+
|
|
106
|
+
VS Code's extension host (where Cline runs) uses Node.js for outbound HTTPS requests. Node.js does **not** automatically read macOS's system keychain, so the `ebay-local.test` certificate is not trusted by default. This causes the OAuth token exchange (`POST /sandbox/token`) to fail silently — the browser flow completes, the "Open in VS Code" page appears, but Cline never receives a session token.
|
|
107
|
+
|
|
108
|
+
**Fix — run these two commands once, then fully quit and reopen VS Code:**
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
# 1. Set for the current macOS session (affects all Dock/Spotlight-launched apps):
|
|
112
|
+
launchctl setenv NODE_EXTRA_CA_CERTS "$(mkcert -CAROOT)/rootCA.pem"
|
|
113
|
+
|
|
114
|
+
# 2. Create a LaunchAgent so it persists across reboots:
|
|
115
|
+
cat > ~/Library/LaunchAgents/com.local.mkcert-node-trust.plist <<'EOF'
|
|
116
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
117
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
118
|
+
<plist version="1.0">
|
|
119
|
+
<dict>
|
|
120
|
+
<key>Label</key><string>com.local.mkcert-node-trust</string>
|
|
121
|
+
<key>ProgramArguments</key>
|
|
122
|
+
<array>
|
|
123
|
+
<string>launchctl</string><string>setenv</string>
|
|
124
|
+
<string>NODE_EXTRA_CA_CERTS</string>
|
|
125
|
+
<string>/Users/YOUR_USERNAME/Library/Application Support/mkcert/rootCA.pem</string>
|
|
126
|
+
</array>
|
|
127
|
+
<key>RunAtLoad</key><true/>
|
|
128
|
+
</dict>
|
|
129
|
+
</plist>
|
|
130
|
+
EOF
|
|
131
|
+
launchctl load ~/Library/LaunchAgents/com.local.mkcert-node-trust.plist
|
|
132
|
+
|
|
133
|
+
# 3. For terminal-launched VS Code — add to ~/.zshrc:
|
|
134
|
+
echo 'export NODE_EXTRA_CA_CERTS="$(mkcert -CAROOT)/rootCA.pem"' >> ~/.zshrc
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
> Replace `YOUR_USERNAME` with your actual macOS username in the plist, or use the full path printed by `mkcert -CAROOT`.
|
|
138
|
+
|
|
139
|
+
After running these commands and **fully quitting VS Code (Cmd+Q on macOS)** and reopening it, Cline's extension host will trust the `ebay-local.test` certificate and the MCP OAuth flow will complete successfully.
|
|
140
|
+
|
|
141
|
+
**Verify the fix works (without restarting VS Code):**
|
|
142
|
+
```bash
|
|
143
|
+
NODE_EXTRA_CA_CERTS="$(mkcert -CAROOT)/rootCA.pem" node -e "
|
|
144
|
+
require('https').get('https://ebay-local.test:3000/health', r => console.log('TLS OK — status:', r.statusCode)).on('error', e => console.error('TLS FAIL:', e.message));
|
|
145
|
+
"
|
|
146
|
+
# Expected: TLS OK — status: 200
|
|
147
|
+
```
|
|
148
|
+
|
|
104
149
|
For hosted deployments, register your server's public HTTPS URL instead (e.g. `https://your-server.com/oauth/callback`).
|
|
105
150
|
|
|
106
151
|
---
|
|
@@ -109,10 +154,10 @@ For hosted deployments, register your server's public HTTPS URL instead (e.g. `h
|
|
|
109
154
|
|
|
110
155
|
### Install
|
|
111
156
|
|
|
112
|
-
**Option A —
|
|
157
|
+
**Option A — pnpm global install (no build step):**
|
|
113
158
|
|
|
114
159
|
```bash
|
|
115
|
-
|
|
160
|
+
pnpm install -g ebay-mcp-remote-edition
|
|
116
161
|
```
|
|
117
162
|
|
|
118
163
|
**Option B — clone and build (for contributors or self-hosting):**
|
|
@@ -5,8 +5,20 @@ import { createKVStore } from '../auth/kv-store.js';
|
|
|
5
5
|
const OAUTH_STATE_TTL_S = 15 * 60;
|
|
6
6
|
/** 10 minutes — short-lived MCP authorization code. */
|
|
7
7
|
const AUTH_CODE_TTL_S = 10 * 60;
|
|
8
|
-
/** 30 days — configurable via SESSION_TTL_SECONDS env var. */
|
|
9
|
-
const
|
|
8
|
+
/** 30 days — default; configurable via SESSION_TTL_SECONDS env var. */
|
|
9
|
+
const SESSION_TTL_FALLBACK_S = 30 * 24 * 60 * 60;
|
|
10
|
+
const _rawSessionTtl = process.env.SESSION_TTL_SECONDS;
|
|
11
|
+
const SESSION_TTL_S = (() => {
|
|
12
|
+
if (_rawSessionTtl === undefined || _rawSessionTtl.trim() === '') {
|
|
13
|
+
return SESSION_TTL_FALLBACK_S;
|
|
14
|
+
}
|
|
15
|
+
const parsed = Number(_rawSessionTtl);
|
|
16
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
17
|
+
console.warn(`[multi-user-store] SESSION_TTL_SECONDS="${_rawSessionTtl}" is invalid; falling back to ${SESSION_TTL_FALLBACK_S}s (30 days)`);
|
|
18
|
+
return SESSION_TTL_FALLBACK_S;
|
|
19
|
+
}
|
|
20
|
+
return Math.floor(parsed);
|
|
21
|
+
})();
|
|
10
22
|
/** 18 months — default fallback when no refresh token expiry is available. */
|
|
11
23
|
const DEFAULT_REFRESH_TOKEN_TTL_S = 18 * 30 * 24 * 60 * 60;
|
|
12
24
|
function secondsFromNow(ttlSeconds) {
|
|
@@ -32,7 +44,7 @@ export class MultiUserAuthStore {
|
|
|
32
44
|
* `lastUsedAt` once per TOUCH_THROTTLE_MS (default: 1 hour).
|
|
33
45
|
*/
|
|
34
46
|
sessionTouchCache = new Map();
|
|
35
|
-
static
|
|
47
|
+
static touchThrottleMs = 60 * 60 * 1_000; // 1 hour
|
|
36
48
|
stateKey(state) {
|
|
37
49
|
return `oauth_state:${state}`;
|
|
38
50
|
}
|
|
@@ -55,6 +67,12 @@ export class MultiUserAuthStore {
|
|
|
55
67
|
await this.kv.put(this.stateKey(state), record, OAUTH_STATE_TTL_S);
|
|
56
68
|
return record;
|
|
57
69
|
}
|
|
70
|
+
async getOAuthState(state) {
|
|
71
|
+
return await this.kv.get(this.stateKey(state));
|
|
72
|
+
}
|
|
73
|
+
async deleteOAuthState(state) {
|
|
74
|
+
await this.kv.delete(this.stateKey(state));
|
|
75
|
+
}
|
|
58
76
|
async consumeOAuthState(state) {
|
|
59
77
|
const key = this.stateKey(state);
|
|
60
78
|
const record = await this.kv.get(key);
|
|
@@ -110,7 +128,7 @@ export class MultiUserAuthStore {
|
|
|
110
128
|
// Skip the KV write entirely if we touched this session recently.
|
|
111
129
|
// The in-memory cache in CloudflareKVStore already keeps reads free,
|
|
112
130
|
// so the only cost we're avoiding here is the unnecessary KV PUT.
|
|
113
|
-
if (lastTouched !== undefined && now - lastTouched < MultiUserAuthStore.
|
|
131
|
+
if (lastTouched !== undefined && now - lastTouched < MultiUserAuthStore.touchThrottleMs) {
|
|
114
132
|
return;
|
|
115
133
|
}
|
|
116
134
|
const record = await this.getSession(sessionToken);
|
|
@@ -139,13 +157,14 @@ export class MultiUserAuthStore {
|
|
|
139
157
|
await this.kv.delete(this.sessionKey(sessionToken));
|
|
140
158
|
}
|
|
141
159
|
// ── RFC 7591 Dynamic Client Registration ──────────────────────────────────
|
|
142
|
-
async registerClient(redirectUris, clientName) {
|
|
160
|
+
async registerClient(redirectUris, clientName, environment) {
|
|
143
161
|
const clientId = randomUUID();
|
|
144
162
|
const record = {
|
|
145
163
|
clientId,
|
|
146
164
|
redirectUris,
|
|
147
165
|
clientName,
|
|
148
166
|
createdAt: new Date().toISOString(),
|
|
167
|
+
...(environment ? { environment } : {}),
|
|
149
168
|
};
|
|
150
169
|
await this.kv.put(`client:${clientId}`, record);
|
|
151
170
|
return record;
|
|
@@ -162,13 +181,25 @@ export class MultiUserAuthStore {
|
|
|
162
181
|
* An existing record for `clientId` is overwritten only if the supplied
|
|
163
182
|
* `redirectUri` is not already listed (additive merge otherwise).
|
|
164
183
|
*/
|
|
165
|
-
|
|
184
|
+
/**
|
|
185
|
+
* @param environment Optional env to tag the client with. When provided,
|
|
186
|
+
* the environment is persisted on the record so that the root /authorize
|
|
187
|
+
* endpoint can use it as a fallback even when no ?env= query param is
|
|
188
|
+
* present. If the existing record already has a different environment
|
|
189
|
+
* the new value wins (e.g. re-registering via /sandbox/authorize should
|
|
190
|
+
* override a stale "production" tag).
|
|
191
|
+
*/
|
|
192
|
+
async registerClientWithId(clientId, redirectUris, clientName, environment) {
|
|
166
193
|
const existing = await this.kv.get(`client:${clientId}`);
|
|
167
194
|
const now = new Date().toISOString();
|
|
168
195
|
if (existing) {
|
|
169
|
-
// Merge any new redirect URIs
|
|
196
|
+
// Merge any new redirect URIs and update the env tag when provided.
|
|
170
197
|
const merged = Array.from(new Set([...existing.redirectUris, ...redirectUris]));
|
|
171
|
-
const updated = {
|
|
198
|
+
const updated = {
|
|
199
|
+
...existing,
|
|
200
|
+
redirectUris: merged,
|
|
201
|
+
...(environment ? { environment } : {}),
|
|
202
|
+
};
|
|
172
203
|
await this.kv.put(`client:${clientId}`, updated);
|
|
173
204
|
return updated;
|
|
174
205
|
}
|
|
@@ -177,6 +208,7 @@ export class MultiUserAuthStore {
|
|
|
177
208
|
redirectUris,
|
|
178
209
|
clientName,
|
|
179
210
|
createdAt: now,
|
|
211
|
+
...(environment ? { environment } : {}),
|
|
180
212
|
};
|
|
181
213
|
await this.kv.put(`client:${clientId}`, record);
|
|
182
214
|
return record;
|
package/build/auth/oauth.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import axios from 'axios';
|
|
2
|
-
import {
|
|
2
|
+
import { getOAuthTokenBaseUrl } from '../config/environment.js';
|
|
3
3
|
import { MultiUserAuthStore } from '../auth/multi-user-store.js';
|
|
4
4
|
export class EbayOAuthClient {
|
|
5
5
|
config;
|
|
@@ -8,15 +8,20 @@ export class EbayOAuthClient {
|
|
|
8
8
|
appAccessTokenExpiry = 0;
|
|
9
9
|
userTokens = null;
|
|
10
10
|
authStore = new MultiUserAuthStore();
|
|
11
|
+
authSource = null;
|
|
11
12
|
constructor(config, context) {
|
|
12
13
|
this.config = config;
|
|
13
14
|
this.context = context;
|
|
14
15
|
}
|
|
16
|
+
getTokenEndpoint() {
|
|
17
|
+
return `${getOAuthTokenBaseUrl(this.config.environment)}/identity/v1/oauth2/token`;
|
|
18
|
+
}
|
|
15
19
|
async initialize() {
|
|
16
20
|
if (this.context?.userId && this.context.environment) {
|
|
17
21
|
const stored = await this.authStore.getUserTokens(this.context.userId, this.context.environment);
|
|
18
22
|
if (stored?.tokenData) {
|
|
19
23
|
this.userTokens = stored.tokenData;
|
|
24
|
+
this.authSource = 'stored_user_tokens';
|
|
20
25
|
return;
|
|
21
26
|
}
|
|
22
27
|
}
|
|
@@ -24,7 +29,7 @@ export class EbayOAuthClient {
|
|
|
24
29
|
const envRefreshToken = process.env.EBAY_USER_REFRESH_TOKEN;
|
|
25
30
|
if (envRefreshToken) {
|
|
26
31
|
try {
|
|
27
|
-
const authUrl =
|
|
32
|
+
const authUrl = this.getTokenEndpoint();
|
|
28
33
|
const credentials = Buffer.from(`${this.config.clientId}:${this.config.clientSecret}`).toString('base64');
|
|
29
34
|
const response = await axios.post(authUrl, new URLSearchParams({
|
|
30
35
|
grant_type: 'refresh_token',
|
|
@@ -50,6 +55,7 @@ export class EbayOAuthClient {
|
|
|
50
55
|
: now + 18 * 30 * 24 * 60 * 60 * 1000,
|
|
51
56
|
scope: tokenData.scope,
|
|
52
57
|
};
|
|
58
|
+
this.authSource = 'env_refresh_token_fallback';
|
|
53
59
|
}
|
|
54
60
|
catch {
|
|
55
61
|
// If refresh fails, leave userTokens as null
|
|
@@ -99,13 +105,14 @@ export class EbayOAuthClient {
|
|
|
99
105
|
userAccessTokenExpiry: accessTokenExpiry ?? now + 7200 * 1000,
|
|
100
106
|
userRefreshTokenExpiry: refreshTokenExpiry ?? now + 18 * 30 * 24 * 60 * 60 * 1000,
|
|
101
107
|
};
|
|
108
|
+
this.authSource = 'manual_set_user_tokens';
|
|
102
109
|
await this.persistUserTokens();
|
|
103
110
|
}
|
|
104
111
|
async getOrRefreshAppAccessToken() {
|
|
105
112
|
if (this.appAccessToken && Date.now() < this.appAccessTokenExpiry) {
|
|
106
113
|
return this.appAccessToken;
|
|
107
114
|
}
|
|
108
|
-
const authUrl =
|
|
115
|
+
const authUrl = this.getTokenEndpoint();
|
|
109
116
|
const credentials = Buffer.from(`${this.config.clientId}:${this.config.clientSecret}`).toString('base64');
|
|
110
117
|
const response = await axios.post(authUrl, new URLSearchParams({
|
|
111
118
|
grant_type: 'client_credentials',
|
|
@@ -124,7 +131,7 @@ export class EbayOAuthClient {
|
|
|
124
131
|
if (!this.config.redirectUri) {
|
|
125
132
|
throw new Error('Redirect URI is required for authorization code exchange');
|
|
126
133
|
}
|
|
127
|
-
const tokenUrl =
|
|
134
|
+
const tokenUrl = this.getTokenEndpoint();
|
|
128
135
|
const credentials = Buffer.from(`${this.config.clientId}:${this.config.clientSecret}`).toString('base64');
|
|
129
136
|
try {
|
|
130
137
|
const response = await axios.post(tokenUrl, new URLSearchParams({
|
|
@@ -150,6 +157,7 @@ export class EbayOAuthClient {
|
|
|
150
157
|
userRefreshTokenExpiry: now + tokenData.refresh_token_expires_in * 1000,
|
|
151
158
|
scope: tokenData.scope,
|
|
152
159
|
};
|
|
160
|
+
this.authSource = 'authorization_code_exchange';
|
|
153
161
|
await this.persistUserTokens();
|
|
154
162
|
return tokenData;
|
|
155
163
|
}
|
|
@@ -170,7 +178,7 @@ export class EbayOAuthClient {
|
|
|
170
178
|
if (!this.userTokens) {
|
|
171
179
|
throw new Error('No user tokens available to refresh');
|
|
172
180
|
}
|
|
173
|
-
const authUrl =
|
|
181
|
+
const authUrl = this.getTokenEndpoint();
|
|
174
182
|
const credentials = Buffer.from(`${this.config.clientId}:${this.config.clientSecret}`).toString('base64');
|
|
175
183
|
const response = await axios.post(authUrl, new URLSearchParams({
|
|
176
184
|
grant_type: 'refresh_token',
|
|
@@ -198,6 +206,24 @@ export class EbayOAuthClient {
|
|
|
198
206
|
};
|
|
199
207
|
await this.persistUserTokens();
|
|
200
208
|
}
|
|
209
|
+
getAuthDebugInfo() {
|
|
210
|
+
return {
|
|
211
|
+
tokenEndpoint: this.getTokenEndpoint(),
|
|
212
|
+
environment: this.config.environment,
|
|
213
|
+
hasClientId: this.config.clientId.trim().length > 0,
|
|
214
|
+
hasClientSecret: this.config.clientSecret.trim().length > 0,
|
|
215
|
+
hasRefreshToken: !!this.userTokens?.userRefreshToken,
|
|
216
|
+
hasAccessToken: !!this.userTokens?.userAccessToken,
|
|
217
|
+
hasRedirectUri: !!this.config.redirectUri,
|
|
218
|
+
...(this.userTokens?.userRefreshTokenExpiry
|
|
219
|
+
? { refreshTokenExpiry: this.userTokens.userRefreshTokenExpiry }
|
|
220
|
+
: {}),
|
|
221
|
+
...(this.userTokens?.userAccessTokenExpiry
|
|
222
|
+
? { accessTokenExpiry: this.userTokens.userAccessTokenExpiry }
|
|
223
|
+
: {}),
|
|
224
|
+
...(this.authSource ? { source: this.authSource } : {}),
|
|
225
|
+
};
|
|
226
|
+
}
|
|
201
227
|
isAuthenticated() {
|
|
202
228
|
if (this.userTokens && !this.isUserAccessTokenExpired(this.userTokens)) {
|
|
203
229
|
return true;
|
|
@@ -130,26 +130,59 @@ export function getHostedOauthScopes(environment) {
|
|
|
130
130
|
'https://api.ebay.com/oauth/api_scope/commerce.message',
|
|
131
131
|
'https://api.ebay.com/oauth/api_scope/commerce.feedback',
|
|
132
132
|
'https://api.ebay.com/oauth/api_scope/commerce.shipping',
|
|
133
|
-
'https://api.ebay.com/oauth/api_scope/sell.order.read',
|
|
134
|
-
'https://api.ebay.com/oauth/api_scope/sell.order',
|
|
135
|
-
'https://api.ebay.com/oauth/api_scope/sell.auction.read',
|
|
136
|
-
'https://api.ebay.com/oauth/api_scope/sell.offer.read',
|
|
137
|
-
'https://api.ebay.com/oauth/api_scope/sell.offer',
|
|
138
|
-
'https://api.ebay.com/oauth/api_scope/sell.return.read',
|
|
139
|
-
'https://api.ebay.com/oauth/api_scope/sell.return',
|
|
140
|
-
'https://api.ebay.com/oauth/api_scope/sell.refund.read',
|
|
141
|
-
'https://api.ebay.com/oauth/api_scope/sell.resolution.read',
|
|
142
|
-
'https://api.ebay.com/oauth/api_scope/sell.inquiry.read',
|
|
143
|
-
'https://api.ebay.com/oauth/api_scope/sell.inquiry',
|
|
144
|
-
'https://api.ebay.com/oauth/api_scope/sell.cancellation.read',
|
|
145
|
-
'https://api.ebay.com/oauth/api_scope/sell.cancellation',
|
|
146
|
-
'https://api.ebay.com/oauth/api_scope/commerce.usernote',
|
|
147
133
|
];
|
|
148
134
|
}
|
|
149
135
|
export function getConfiguredEnvironment() {
|
|
150
136
|
const env = process.env.EBAY_ENVIRONMENT || process.env.EBAY_DEFAULT_ENVIRONMENT || 'production';
|
|
151
137
|
return env === 'sandbox' ? 'sandbox' : 'production';
|
|
152
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* Parse an eBay RuName string to detect whether it belongs to production or
|
|
141
|
+
* sandbox. eBay encodes the environment in a dedicated segment of the RuName:
|
|
142
|
+
*
|
|
143
|
+
* CompanyName-AppNickname-AppName-**PR**-ID → production
|
|
144
|
+
* CompanyName-AppNickname-AppName-**SB**-ID → sandbox
|
|
145
|
+
*
|
|
146
|
+
* @returns `'production'` | `'sandbox'` | `null` (unknown / not detectable)
|
|
147
|
+
*/
|
|
148
|
+
export function ruNameToEnvironment(ruName) {
|
|
149
|
+
if (!ruName)
|
|
150
|
+
return null;
|
|
151
|
+
// Look for a word boundary -PR- or -SB- anywhere in the string.
|
|
152
|
+
// Use exact dash-delimited segment matching to avoid false positives (e.g.
|
|
153
|
+
// "PROMO" or "SUBSCRIBE" being mis-detected).
|
|
154
|
+
if (/-PR-/i.test(ruName))
|
|
155
|
+
return 'production';
|
|
156
|
+
if (/-SB-/i.test(ruName))
|
|
157
|
+
return 'sandbox';
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Validate that the credentials configured for `environment` actually match
|
|
162
|
+
* the requested environment by inspecting the RuName segment.
|
|
163
|
+
*
|
|
164
|
+
* Returns an object describing whether the credentials look correct and any
|
|
165
|
+
* human-readable warning or error message.
|
|
166
|
+
*/
|
|
167
|
+
export function validateCredentialsForEnvironment(environment) {
|
|
168
|
+
const config = getEbayConfig(environment);
|
|
169
|
+
const ruName = config.redirectUri;
|
|
170
|
+
const detectedEnv = ruNameToEnvironment(ruName);
|
|
171
|
+
if (detectedEnv === null) {
|
|
172
|
+
// Can't tell from the RuName — treat as valid (no info to contradict it).
|
|
173
|
+
return { valid: true, detectedEnv: null };
|
|
174
|
+
}
|
|
175
|
+
if (detectedEnv !== environment) {
|
|
176
|
+
return {
|
|
177
|
+
valid: false,
|
|
178
|
+
detectedEnv,
|
|
179
|
+
error: `Credential mismatch: the RuName "${ruName}" belongs to ${detectedEnv} ` +
|
|
180
|
+
`but the request targets ${environment}. ` +
|
|
181
|
+
`Check EBAY_${environment.toUpperCase()}_RUNAME (or EBAY_RUNAME).`,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
return { valid: true, detectedEnv };
|
|
185
|
+
}
|
|
153
186
|
export function validateScopes(scopes, environment) {
|
|
154
187
|
const validScopes = getDefaultScopes(environment);
|
|
155
188
|
const validScopeSet = new Set(validScopes);
|
|
@@ -256,9 +289,19 @@ export function getEbayConfig(environmentOverride) {
|
|
|
256
289
|
appAccessToken: process.env.EBAY_APP_ACCESS_TOKEN ?? '',
|
|
257
290
|
};
|
|
258
291
|
}
|
|
292
|
+
export function getValidationRunnerUserId(environment) {
|
|
293
|
+
const envSpecific = environment === 'production'
|
|
294
|
+
? process.env.VALIDATION_RUNNER_USER_ID_PRODUCTION
|
|
295
|
+
: process.env.VALIDATION_RUNNER_USER_ID_SANDBOX;
|
|
296
|
+
const resolved = (envSpecific ?? process.env.VALIDATION_RUNNER_USER_ID ?? '').trim();
|
|
297
|
+
return resolved || null;
|
|
298
|
+
}
|
|
259
299
|
export function getBaseUrl(environment) {
|
|
260
300
|
return environment === 'production' ? 'https://api.ebay.com' : 'https://api.sandbox.ebay.com';
|
|
261
301
|
}
|
|
302
|
+
export function getOAuthTokenBaseUrl(environment) {
|
|
303
|
+
return environment === 'production' ? 'https://api.ebay.com' : 'https://api.sandbox.ebay.com';
|
|
304
|
+
}
|
|
262
305
|
export function getIdentityBaseUrl(environment) {
|
|
263
306
|
return environment === 'production' ? 'https://apiz.ebay.com' : 'https://apiz.sandbox.ebay.com';
|
|
264
307
|
}
|
|
@@ -11,7 +11,7 @@ function isHostedEnvironment() {
|
|
|
11
11
|
function main() {
|
|
12
12
|
const args = process.argv.slice(2);
|
|
13
13
|
if (args.length === 0) {
|
|
14
|
-
throw new Error('Usage: tsx src/scripts/
|
|
14
|
+
throw new Error('Usage: tsx src/scripts/env-check.ts <command> [args...]');
|
|
15
15
|
}
|
|
16
16
|
const hosted = isHostedEnvironment();
|
|
17
17
|
const [command, ...commandArgs] = args;
|