@rbillon59/vinted-mcp-server 0.1.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.
Files changed (62) hide show
  1. package/README.md +232 -0
  2. package/dist/api/browser-utils.d.ts +21 -0
  3. package/dist/api/browser-utils.d.ts.map +1 -0
  4. package/dist/api/browser-utils.js +64 -0
  5. package/dist/api/browser-utils.js.map +1 -0
  6. package/dist/api/client.d.ts +45 -0
  7. package/dist/api/client.d.ts.map +1 -0
  8. package/dist/api/client.js +220 -0
  9. package/dist/api/client.js.map +1 -0
  10. package/dist/api/session-provider.d.ts +30 -0
  11. package/dist/api/session-provider.d.ts.map +1 -0
  12. package/dist/api/session-provider.js +69 -0
  13. package/dist/api/session-provider.js.map +1 -0
  14. package/dist/api/types.d.ts +83 -0
  15. package/dist/api/types.d.ts.map +1 -0
  16. package/dist/api/types.js +12 -0
  17. package/dist/api/types.js.map +1 -0
  18. package/dist/index.d.ts +3 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +21 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/server.d.ts +3 -0
  23. package/dist/server.d.ts.map +1 -0
  24. package/dist/server.js +22 -0
  25. package/dist/server.js.map +1 -0
  26. package/dist/tools/brands.d.ts +3 -0
  27. package/dist/tools/brands.d.ts.map +1 -0
  28. package/dist/tools/brands.js +34 -0
  29. package/dist/tools/brands.js.map +1 -0
  30. package/dist/tools/categories.d.ts +3 -0
  31. package/dist/tools/categories.d.ts.map +1 -0
  32. package/dist/tools/categories.js +28 -0
  33. package/dist/tools/categories.js.map +1 -0
  34. package/dist/tools/item.d.ts +3 -0
  35. package/dist/tools/item.d.ts.map +1 -0
  36. package/dist/tools/item.js +155 -0
  37. package/dist/tools/item.js.map +1 -0
  38. package/dist/tools/search.d.ts +3 -0
  39. package/dist/tools/search.d.ts.map +1 -0
  40. package/dist/tools/search.js +73 -0
  41. package/dist/tools/search.js.map +1 -0
  42. package/dist/tools/user-items.d.ts +3 -0
  43. package/dist/tools/user-items.d.ts.map +1 -0
  44. package/dist/tools/user-items.js +50 -0
  45. package/dist/tools/user-items.js.map +1 -0
  46. package/dist/tools/user.d.ts +3 -0
  47. package/dist/tools/user.d.ts.map +1 -0
  48. package/dist/tools/user.js +38 -0
  49. package/dist/tools/user.js.map +1 -0
  50. package/dist/utils/cache.d.ts +17 -0
  51. package/dist/utils/cache.d.ts.map +1 -0
  52. package/dist/utils/cache.js +65 -0
  53. package/dist/utils/cache.js.map +1 -0
  54. package/dist/utils/mcp-error.d.ts +11 -0
  55. package/dist/utils/mcp-error.d.ts.map +1 -0
  56. package/dist/utils/mcp-error.js +11 -0
  57. package/dist/utils/mcp-error.js.map +1 -0
  58. package/dist/utils/rate-limiter.d.ts +22 -0
  59. package/dist/utils/rate-limiter.d.ts.map +1 -0
  60. package/dist/utils/rate-limiter.js +47 -0
  61. package/dist/utils/rate-limiter.js.map +1 -0
  62. package/package.json +45 -0
package/README.md ADDED
@@ -0,0 +1,232 @@
1
+ # vinted-mcp-server
2
+
3
+ [![CI](https://github.com/Rbillon59/vinted-mcp-server/actions/workflows/ci.yml/badge.svg)](https://github.com/Rbillon59/vinted-mcp-server/actions/workflows/ci.yml)
4
+
5
+ A [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server for interacting with the Vinted marketplace. Enables AI assistants to search, browse, and discover second-hand items on Vinted.
6
+
7
+ ## Features
8
+
9
+ - **Search items** — Full-text search with filters (price, brand, size, color, condition, sort)
10
+ - **Item details** — Full item info including description, photos, seller profile, and condition
11
+ - **User profiles** — Seller ratings, reviews, item counts, and activity
12
+ - **User items** — Browse all items listed by a specific user
13
+ - **Brand search** — Find brand IDs for use in search filters
14
+ - **Categories** — Browse the Vinted category tree
15
+ - **Rate limiting** — Token bucket rate limiter to avoid API bans
16
+ - **Caching** — In-memory LRU cache with TTL for fast repeated queries
17
+ - **Retry logic** — Exponential backoff for transient errors
18
+ - **Session management** — Puppeteer-based with Cloudflare bypass
19
+ - **Token-efficient** — Concise markdown responses optimized for LLM consumption
20
+
21
+ ## Quick Start
22
+
23
+ ### Prerequisites
24
+ - Node.js 20+
25
+
26
+ ### Using npx (zero-install)
27
+
28
+ This server communicates via **stdio** (JSON-RPC) and is designed to be launched by an MCP client, not run directly in a terminal. Configure it in your MCP client as shown below.
29
+
30
+ ### Install from source
31
+
32
+ ```bash
33
+ git clone https://github.com/Rbillon59/vinted-mcp-server.git
34
+ cd vinted-mcp-server
35
+ npm install
36
+ npm run build
37
+ ```
38
+
39
+ ### Usage with Claude Desktop
40
+
41
+ Add to your `claude_desktop_config.json`:
42
+
43
+ ```json
44
+ {
45
+ "mcpServers": {
46
+ "vinted": {
47
+ "command": "npx",
48
+ "args": ["vinted-mcp-server"],
49
+ "env": {
50
+ "VINTED_DOMAIN": "www.vinted.fr"
51
+ }
52
+ }
53
+ }
54
+ }
55
+ ```
56
+
57
+ <details>
58
+ <summary>Alternative: using a local build</summary>
59
+
60
+ ```json
61
+ {
62
+ "mcpServers": {
63
+ "vinted": {
64
+ "command": "node",
65
+ "args": ["/absolute/path/to/vinted-mcp-server/dist/index.js"],
66
+ "env": {
67
+ "VINTED_DOMAIN": "www.vinted.fr"
68
+ }
69
+ }
70
+ }
71
+ }
72
+ ```
73
+ </details>
74
+
75
+ ### Usage with Claude Code CLI
76
+
77
+ Add to your Claude Code settings (`~/.claude/settings.json` or project `.mcp.json`):
78
+
79
+ ```json
80
+ {
81
+ "mcpServers": {
82
+ "vinted": {
83
+ "command": "npx",
84
+ "args": ["vinted-mcp-server"],
85
+ "env": {
86
+ "VINTED_DOMAIN": "www.vinted.fr"
87
+ }
88
+ }
89
+ }
90
+ }
91
+ ```
92
+
93
+ ### Usage with Cursor
94
+
95
+ Add to `.cursor/mcp.json` in your project:
96
+
97
+ ```json
98
+ {
99
+ "mcpServers": {
100
+ "vinted": {
101
+ "command": "npx",
102
+ "args": ["vinted-mcp-server"]
103
+ }
104
+ }
105
+ }
106
+ ```
107
+
108
+ ### Usage with Docker
109
+
110
+ ```bash
111
+ docker build -t vinted-mcp-server .
112
+ docker run -i --rm -e VINTED_DOMAIN=www.vinted.fr vinted-mcp-server
113
+ ```
114
+
115
+ ## Available Tools
116
+
117
+ ### `search_items`
118
+ Search the Vinted catalog with filters.
119
+
120
+ | Parameter | Type | Default | Description |
121
+ |-----------|------|---------|-------------|
122
+ | `query` | string | *(required)* | Search text (e.g., "nike air max", "robe vintage") |
123
+ | `page` | number | 1 | Page number |
124
+ | `per_page` | number | 20 | Results per page (max: 96) |
125
+ | `order` | string | "relevance" | Sort: `relevance`, `price_low_to_high`, `price_high_to_low`, `newest_first` |
126
+ | `price_from` | number | — | Minimum price filter |
127
+ | `price_to` | number | — | Maximum price filter |
128
+ | `brand_ids` | string | — | Brand IDs (comma-separated) |
129
+ | `size_ids` | string | — | Size IDs (comma-separated) |
130
+ | `color_ids` | string | — | Color IDs (comma-separated) |
131
+ | `catalog_ids` | string | — | Category IDs (comma-separated) |
132
+ | `status_ids` | string | — | Condition: `6`=New with tags, `1`=New, `2`=Very good, `3`=Good, `4`=Satisfactory |
133
+
134
+ ### `get_item_details`
135
+ Get detailed information about a specific item.
136
+
137
+ | Parameter | Type | Description |
138
+ |-----------|------|-------------|
139
+ | `item_id` | number | The Vinted item ID (from search results) |
140
+
141
+ Returns: title, price, description, brand, size, condition, colors, seller info (rating, location), photos, stats (views, favorites).
142
+
143
+ ### `get_user_profile`
144
+ Get a seller's profile information.
145
+
146
+ | Parameter | Type | Description |
147
+ |-----------|------|-------------|
148
+ | `user_id` | number | The Vinted user ID (from search results or item details) |
149
+
150
+ Returns: username, rating, review breakdown, items listed/sold, location, member since, last active.
151
+
152
+ ### `get_user_items`
153
+ Browse all items listed by a specific user.
154
+
155
+ | Parameter | Type | Default | Description |
156
+ |-----------|------|---------|-------------|
157
+ | `user_id` | number | *(required)* | The Vinted user ID |
158
+ | `page` | number | 1 | Page number |
159
+ | `per_page` | number | 20 | Results per page |
160
+
161
+ ### `search_brands`
162
+ Search for brand names and get their IDs for use in search filters.
163
+
164
+ | Parameter | Type | Description |
165
+ |-----------|------|-------------|
166
+ | `query` | string | Brand name to search (e.g., "Nike", "Zara") |
167
+
168
+ ### `get_categories`
169
+ Browse the Vinted category tree.
170
+
171
+ | Parameter | Type | Description |
172
+ |-----------|------|-------------|
173
+ | `parent_id` | number | *(optional)* Parent category ID to list children of |
174
+
175
+ ## Configuration
176
+
177
+ | Environment Variable | Default | Description |
178
+ |---------------------|---------|-------------|
179
+ | `VINTED_DOMAIN` | `www.vinted.fr` | Vinted domain (e.g., `www.vinted.de`, `www.vinted.es`, `www.vinted.it`) |
180
+ | `PUPPETEER_EXECUTABLE_PATH` | — | Custom Chrome/Chromium path for Puppeteer |
181
+ | `BROWSER_TIMEOUT_MS` | `30000` | Timeout for Cloudflare challenge resolution (ms) |
182
+
183
+ ### Supported Domains
184
+ | Domain | Country |
185
+ |--------|---------|
186
+ | `www.vinted.fr` | France |
187
+ | `www.vinted.de` | Germany |
188
+ | `www.vinted.es` | Spain |
189
+ | `www.vinted.it` | Italy |
190
+ | `www.vinted.nl` | Netherlands |
191
+ | `www.vinted.be` | Belgium |
192
+ | `www.vinted.pl` | Poland |
193
+ | `www.vinted.pt` | Portugal |
194
+ | `www.vinted.lt` | Lithuania |
195
+ | `www.vinted.cz` | Czech Republic |
196
+ | `www.vinted.co.uk` | United Kingdom |
197
+
198
+ ## Architecture
199
+
200
+ ```
201
+ src/
202
+ index.ts # Entry point, stdio transport
203
+ server.ts # MCP server config & tool registration
204
+ tools/
205
+ search.ts # search_items tool
206
+ item.ts # get_item_details tool
207
+ user.ts # get_user_profile tool
208
+ user-items.ts # get_user_items tool
209
+ brands.ts # search_brands tool
210
+ categories.ts # get_categories tool
211
+ api/
212
+ client.ts # HTTP client (session, cache, rate limit, retry)
213
+ session-provider.ts # Browser-based session (Cloudflare bypass)
214
+ browser-utils.ts # Shared Puppeteer/stealth utilities
215
+ types.ts # Vinted API response types
216
+ utils/
217
+ cache.ts # TTL cache with LRU eviction
218
+ rate-limiter.ts # Token bucket rate limiter
219
+ mcp-error.ts # Shared MCP error response builder
220
+ ```
221
+
222
+ ### Reliability Features
223
+ - **Rate limiting**: Token bucket (10 req/10s) prevents API bans
224
+ - **Caching**: LRU cache (3min TTL, 200 entries max) reduces redundant requests
225
+ - **Retry**: Exponential backoff (1s, 2s) on 429/5xx errors
226
+ - **Session recovery**: Automatic cookie refresh on 401/403 with request coalescing
227
+ - **Request deduplication**: Concurrent identical requests share a single API call
228
+ - **Cloudflare bypass**: Puppeteer with stealth plugin for session acquisition
229
+
230
+ ## License
231
+
232
+ MIT
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Shared browser constants and utilities for session providers.
3
+ */
4
+ import type { Browser } from "puppeteer";
5
+ export declare const BROWSER_ARGS: readonly ["--no-sandbox", "--disable-setuid-sandbox", "--disable-dev-shm-usage", "--disable-gpu", "--disable-extensions", "--disable-background-networking", "--disable-sync", "--no-first-run"];
6
+ /** Cookie names that indicate a successful Vinted session. */
7
+ export declare const SESSION_COOKIE_REGEX: RegExp;
8
+ export declare const ACCESS_TOKEN_COOKIE = "access_token_web";
9
+ /** Additional cookies to include in API requests. */
10
+ export declare const EXTRA_COOKIE_NAMES: readonly ["anon_id", "anonymous-locale", "v_udt", "__cf_bm"];
11
+ export declare function buildCookieString(cookies: ReadonlyArray<{
12
+ name: string;
13
+ value: string;
14
+ }>): string;
15
+ export declare function hasSessionCookie(cookies: ReadonlyArray<{
16
+ name: string;
17
+ value: string;
18
+ }>): boolean;
19
+ export declare function launchStealthBrowser(executablePath: string): Promise<Browser>;
20
+ export declare function parseTimeoutEnv(fallback: number): number;
21
+ //# sourceMappingURL=browser-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-utils.d.ts","sourceRoot":"","sources":["../../src/api/browser-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,eAAO,MAAM,YAAY,kMASf,CAAC;AAEX,8DAA8D;AAC9D,eAAO,MAAM,oBAAoB,QAA0B,CAAC;AAC5D,eAAO,MAAM,mBAAmB,qBAAqB,CAAC;AAEtD,qDAAqD;AACrD,eAAO,MAAM,kBAAkB,8DAKrB,CAAC;AAEX,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,aAAa,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,GACtD,MAAM,CAoBR;AAED,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,aAAa,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,GACtD,OAAO,CAIT;AAED,wBAAsB,oBAAoB,CACxC,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,OAAO,CAAC,CAuBlB;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAKxD"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Shared browser constants and utilities for session providers.
3
+ */
4
+ export const BROWSER_ARGS = [
5
+ "--no-sandbox",
6
+ "--disable-setuid-sandbox",
7
+ "--disable-dev-shm-usage",
8
+ "--disable-gpu",
9
+ "--disable-extensions",
10
+ "--disable-background-networking",
11
+ "--disable-sync",
12
+ "--no-first-run",
13
+ ];
14
+ /** Cookie names that indicate a successful Vinted session. */
15
+ export const SESSION_COOKIE_REGEX = /^_vinted_\w+_session$/;
16
+ export const ACCESS_TOKEN_COOKIE = "access_token_web";
17
+ /** Additional cookies to include in API requests. */
18
+ export const EXTRA_COOKIE_NAMES = [
19
+ "anon_id",
20
+ "anonymous-locale",
21
+ "v_udt",
22
+ "__cf_bm",
23
+ ];
24
+ export function buildCookieString(cookies) {
25
+ const extraSet = new Set(EXTRA_COOKIE_NAMES);
26
+ const parts = [];
27
+ for (const cookie of cookies) {
28
+ const isSession = cookie.name === ACCESS_TOKEN_COOKIE
29
+ || SESSION_COOKIE_REGEX.test(cookie.name);
30
+ const isExtra = extraSet.has(cookie.name);
31
+ if (isSession || isExtra) {
32
+ parts.push(`${cookie.name}=${cookie.value}`);
33
+ }
34
+ }
35
+ if (parts.length === 0) {
36
+ throw new Error("No usable cookies found after session resolution");
37
+ }
38
+ return parts.join("; ");
39
+ }
40
+ export function hasSessionCookie(cookies) {
41
+ return cookies.some((c) => c.name === ACCESS_TOKEN_COOKIE || SESSION_COOKIE_REGEX.test(c.name));
42
+ }
43
+ export async function launchStealthBrowser(executablePath) {
44
+ const puppeteerExtraMod = (await import("puppeteer-extra"));
45
+ const stealthMod = (await import("puppeteer-extra-plugin-stealth"));
46
+ const puppeteer = puppeteerExtraMod.default;
47
+ puppeteer.use(stealthMod.default());
48
+ const launchOptions = {
49
+ headless: true,
50
+ args: [...BROWSER_ARGS],
51
+ };
52
+ if (executablePath) {
53
+ launchOptions["executablePath"] = executablePath;
54
+ }
55
+ return puppeteer.launch(launchOptions);
56
+ }
57
+ export function parseTimeoutEnv(fallback) {
58
+ const envTimeout = process.env["BROWSER_TIMEOUT_MS"];
59
+ if (!envTimeout)
60
+ return fallback;
61
+ const parsed = parseInt(envTimeout, 10);
62
+ return Number.isFinite(parsed) ? parsed : fallback;
63
+ }
64
+ //# sourceMappingURL=browser-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-utils.js","sourceRoot":"","sources":["../../src/api/browser-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,cAAc;IACd,0BAA0B;IAC1B,yBAAyB;IACzB,eAAe;IACf,sBAAsB;IACtB,iCAAiC;IACjC,gBAAgB;IAChB,gBAAgB;CACR,CAAC;AAEX,8DAA8D;AAC9D,MAAM,CAAC,MAAM,oBAAoB,GAAG,uBAAuB,CAAC;AAC5D,MAAM,CAAC,MAAM,mBAAmB,GAAG,kBAAkB,CAAC;AAEtD,qDAAqD;AACrD,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,SAAS;IACT,kBAAkB;IAClB,OAAO;IACP,SAAS;CACD,CAAC;AAEX,MAAM,UAAU,iBAAiB,CAC/B,OAAuD;IAEvD,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAS,kBAAkB,CAAC,CAAC;IACrD,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,SAAS,GACb,MAAM,CAAC,IAAI,KAAK,mBAAmB;eAChC,oBAAoB,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAC5C,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAE1C,IAAI,SAAS,IAAI,OAAO,EAAE,CAAC;YACzB,KAAK,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC;IAED,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAC;IACtE,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC9B,OAAuD;IAEvD,OAAO,OAAO,CAAC,IAAI,CACjB,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,mBAAmB,IAAI,oBAAoB,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAC3E,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,cAAsB;IAEtB,MAAM,iBAAiB,GAAG,CAAC,MAAM,MAAM,CAAC,iBAAiB,CAAC,CAKzD,CAAC;IACF,MAAM,UAAU,GAAG,CAAC,MAAM,MAAM,CAAC,gCAAgC,CAAC,CAEjE,CAAC;IAEF,MAAM,SAAS,GAAG,iBAAiB,CAAC,OAAO,CAAC;IAC5C,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC;IAEpC,MAAM,aAAa,GAA4B;QAC7C,QAAQ,EAAE,IAAI;QACd,IAAI,EAAE,CAAC,GAAG,YAAY,CAAC;KACxB,CAAC;IACF,IAAI,cAAc,EAAE,CAAC;QACnB,aAAa,CAAC,gBAAgB,CAAC,GAAG,cAAc,CAAC;IACnD,CAAC;IAED,OAAO,SAAS,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;AACzC,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,QAAgB;IAC9C,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;IACrD,IAAI,CAAC,UAAU;QAAE,OAAO,QAAQ,CAAC;IACjC,MAAM,MAAM,GAAG,QAAQ,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IACxC,OAAO,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC;AACrD,CAAC"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Vinted API HTTP client with session management, rate limiting, and caching.
3
+ */
4
+ import { type SessionProvider } from "./session-provider.js";
5
+ export declare const USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36";
6
+ export declare class VintedClient {
7
+ private readonly domain;
8
+ private session;
9
+ private refreshPromise;
10
+ private readonly cache;
11
+ private readonly rateLimiter;
12
+ private readonly inflight;
13
+ private readonly sessionProvider;
14
+ constructor(domain?: string, sessionProvider?: SessionProvider);
15
+ get baseUrl(): string;
16
+ private doRefreshSession;
17
+ /**
18
+ * Coalesce concurrent session refreshes into a single request.
19
+ */
20
+ private refreshSession;
21
+ private isSessionValid;
22
+ private ensureSession;
23
+ /**
24
+ * Fetch with retry logic for transient errors.
25
+ */
26
+ private fetchWithRetry;
27
+ /**
28
+ * Make an authenticated, cached, rate-limited GET request to the Vinted API.
29
+ * Deduplicates concurrent requests for the same URL.
30
+ */
31
+ get<T>(path: string, params?: Record<string, string>): Promise<T>;
32
+ private fetchAndCache;
33
+ /**
34
+ * Fetch an HTML page with session, rate limiting, and retry.
35
+ * Used for scraping item detail pages where the JSON API is unavailable.
36
+ */
37
+ getHtml(url: string): Promise<string>;
38
+ /**
39
+ * Release browser resources held by session providers.
40
+ */
41
+ destroy(): Promise<void>;
42
+ }
43
+ export declare function getClient(): VintedClient;
44
+ export declare function destroyClient(): Promise<void>;
45
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,EAAE,KAAK,eAAe,EAAyB,MAAM,uBAAuB,CAAC;AAcpF,eAAO,MAAM,UAAU,oHAC4F,CAAC;AASpH,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,cAAc,CAA8B;IACpD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAuC;IAC7D,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA8D;IAC1F,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAuC;IAChE,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAkB;gBAEtC,MAAM,CAAC,EAAE,MAAM,EAAE,eAAe,CAAC,EAAE,eAAe;IAK9D,IAAI,OAAO,IAAI,MAAM,CAEpB;YAEa,gBAAgB;IAI9B;;OAEG;YACW,cAAc;IAS5B,OAAO,CAAC,cAAc;YAKR,aAAa;IAU3B;;OAEG;YACW,cAAc;IAsE5B;;;OAGG;IACG,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;YAkCzD,aAAa;IAyB3B;;;OAGG;IACG,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IA6B3C;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAI/B;AAKD,wBAAgB,SAAS,IAAI,YAAY,CAKxC;AAED,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAKnD"}
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Vinted API HTTP client with session management, rate limiting, and caching.
3
+ */
4
+ import { TtlCache } from "../utils/cache.js";
5
+ import { RateLimiter } from "../utils/rate-limiter.js";
6
+ import { createSessionProvider } from "./session-provider.js";
7
+ const DEFAULT_DOMAIN = "www.vinted.fr";
8
+ const SESSION_TTL_MS = 10 * 60 * 1000; // 10 minutes
9
+ const REQUEST_TIMEOUT_MS = 15_000;
10
+ const CACHE_TTL_MS = 3 * 60 * 1000; // 3 minutes
11
+ const MAX_RETRIES = 2;
12
+ const RETRY_BASE_DELAY_MS = 1000;
13
+ // Rate limit: 10 requests per 10 seconds (conservative to avoid bans)
14
+ const RATE_LIMIT_REQUESTS = 10;
15
+ const RATE_LIMIT_WINDOW_MS = 10_000;
16
+ export const USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36";
17
+ export class VintedClient {
18
+ domain;
19
+ session = null;
20
+ refreshPromise = null;
21
+ cache = new TtlCache(CACHE_TTL_MS);
22
+ rateLimiter = new RateLimiter(RATE_LIMIT_REQUESTS, RATE_LIMIT_WINDOW_MS);
23
+ inflight = new Map();
24
+ sessionProvider;
25
+ constructor(domain, sessionProvider) {
26
+ this.domain = domain ?? process.env["VINTED_DOMAIN"] ?? DEFAULT_DOMAIN;
27
+ this.sessionProvider = sessionProvider ?? createSessionProvider();
28
+ }
29
+ get baseUrl() {
30
+ return `https://${this.domain}`;
31
+ }
32
+ async doRefreshSession() {
33
+ this.session = await this.sessionProvider.refreshSession(this.domain);
34
+ }
35
+ /**
36
+ * Coalesce concurrent session refreshes into a single request.
37
+ */
38
+ async refreshSession() {
39
+ if (!this.refreshPromise) {
40
+ this.refreshPromise = this.doRefreshSession().finally(() => {
41
+ this.refreshPromise = null;
42
+ });
43
+ }
44
+ return this.refreshPromise;
45
+ }
46
+ isSessionValid() {
47
+ if (!this.session)
48
+ return false;
49
+ return Date.now() - this.session.fetchedAt < SESSION_TTL_MS;
50
+ }
51
+ async ensureSession() {
52
+ if (!this.isSessionValid()) {
53
+ await this.refreshSession();
54
+ }
55
+ if (!this.session) {
56
+ throw new Error("Failed to establish Vinted session");
57
+ }
58
+ return this.session.cookie;
59
+ }
60
+ /**
61
+ * Fetch with retry logic for transient errors.
62
+ */
63
+ async fetchWithRetry(url, cookie, method = "GET", body) {
64
+ let lastError = null;
65
+ let retryDelay = RETRY_BASE_DELAY_MS;
66
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
67
+ if (attempt > 0) {
68
+ await new Promise((resolve) => setTimeout(resolve, retryDelay));
69
+ }
70
+ try {
71
+ const controller = new AbortController();
72
+ const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
73
+ try {
74
+ const headers = {
75
+ "User-Agent": USER_AGENT,
76
+ Accept: "application/json, text/plain, */*",
77
+ Cookie: cookie,
78
+ Referer: `https://${this.domain}/`,
79
+ Origin: `https://${this.domain}`,
80
+ };
81
+ if (body !== undefined) {
82
+ headers["Content-Type"] = "application/json";
83
+ }
84
+ const response = await fetch(url, {
85
+ method,
86
+ headers,
87
+ body: body !== undefined ? JSON.stringify(body) : undefined,
88
+ signal: controller.signal,
89
+ });
90
+ // Return 2xx/3xx immediately
91
+ if (response.ok) {
92
+ return response;
93
+ }
94
+ // Don't retry client errors (except 429; auth handled by caller)
95
+ if (response.status >= 400 && response.status < 500 && response.status !== 429) {
96
+ return response;
97
+ }
98
+ // Retry on 429 (rate limited) or 5xx - consume body to free connection
99
+ const retryAfter = response.headers.get("retry-after");
100
+ retryDelay = retryAfter
101
+ ? Math.min(parseInt(retryAfter, 10) * 1000, 30_000)
102
+ : RETRY_BASE_DELAY_MS * Math.pow(2, attempt);
103
+ await response.body?.cancel();
104
+ lastError = new Error(`Vinted API error: ${response.status} ${response.statusText}`);
105
+ continue;
106
+ }
107
+ finally {
108
+ clearTimeout(timeout);
109
+ }
110
+ }
111
+ catch (error) {
112
+ lastError = error instanceof Error ? error : new Error(String(error));
113
+ if (error instanceof DOMException && error.name === "AbortError") {
114
+ lastError = new Error("Request timed out");
115
+ }
116
+ }
117
+ }
118
+ throw lastError ?? new Error("Request failed after retries");
119
+ }
120
+ /**
121
+ * Make an authenticated, cached, rate-limited GET request to the Vinted API.
122
+ * Deduplicates concurrent requests for the same URL.
123
+ */
124
+ async get(path, params) {
125
+ const url = new URL(`/api/v2${path}`, this.baseUrl);
126
+ if (params) {
127
+ for (const [key, value] of Object.entries(params)) {
128
+ if (value !== undefined && value !== "") {
129
+ url.searchParams.set(key, value);
130
+ }
131
+ }
132
+ }
133
+ const cacheKey = url.toString();
134
+ // Check cache first
135
+ const cached = this.cache.get(cacheKey);
136
+ if (cached !== undefined) {
137
+ return cached;
138
+ }
139
+ // Deduplicate concurrent requests for the same URL
140
+ const existing = this.inflight.get(cacheKey);
141
+ if (existing) {
142
+ return existing;
143
+ }
144
+ const promise = this.fetchAndCache(cacheKey);
145
+ this.inflight.set(cacheKey, promise);
146
+ try {
147
+ return await promise;
148
+ }
149
+ finally {
150
+ this.inflight.delete(cacheKey);
151
+ }
152
+ }
153
+ async fetchAndCache(cacheKey) {
154
+ // Ensure session before consuming a rate limit token
155
+ const cookie = await this.ensureSession();
156
+ await this.rateLimiter.acquire();
157
+ let response = await this.fetchWithRetry(cacheKey, cookie);
158
+ // Handle expired session
159
+ if (response.status === 401 || response.status === 403) {
160
+ await response.body?.cancel();
161
+ this.session = null;
162
+ const newCookie = await this.ensureSession();
163
+ response = await this.fetchWithRetry(cacheKey, newCookie);
164
+ }
165
+ if (!response.ok) {
166
+ throw new Error(`Vinted API error: ${response.status} ${response.statusText}`);
167
+ }
168
+ const data = (await response.json());
169
+ this.cache.set(cacheKey, data);
170
+ return data;
171
+ }
172
+ /**
173
+ * Fetch an HTML page with session, rate limiting, and retry.
174
+ * Used for scraping item detail pages where the JSON API is unavailable.
175
+ */
176
+ async getHtml(url) {
177
+ const cacheKey = `html:${url}`;
178
+ const cached = this.cache.get(cacheKey);
179
+ if (cached !== undefined) {
180
+ return cached;
181
+ }
182
+ const cookie = await this.ensureSession();
183
+ await this.rateLimiter.acquire();
184
+ let response = await this.fetchWithRetry(url, cookie);
185
+ if (response.status === 401 || response.status === 403) {
186
+ await response.body?.cancel();
187
+ this.session = null;
188
+ const newCookie = await this.ensureSession();
189
+ response = await this.fetchWithRetry(url, newCookie);
190
+ }
191
+ if (!response.ok) {
192
+ throw new Error(`Vinted page error: ${response.status} ${response.statusText}`);
193
+ }
194
+ const html = await response.text();
195
+ this.cache.set(cacheKey, html);
196
+ return html;
197
+ }
198
+ /**
199
+ * Release browser resources held by session providers.
200
+ */
201
+ async destroy() {
202
+ this.cache.destroy();
203
+ await this.sessionProvider.destroy();
204
+ }
205
+ }
206
+ /** Singleton client instance */
207
+ let clientInstance = null;
208
+ export function getClient() {
209
+ if (!clientInstance) {
210
+ clientInstance = new VintedClient();
211
+ }
212
+ return clientInstance;
213
+ }
214
+ export async function destroyClient() {
215
+ if (clientInstance) {
216
+ await clientInstance.destroy();
217
+ clientInstance = null;
218
+ }
219
+ }
220
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAwB,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAGpF,MAAM,cAAc,GAAG,eAAe,CAAC;AACvC,MAAM,cAAc,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,aAAa;AACpD,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAClC,MAAM,YAAY,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,YAAY;AAChD,MAAM,WAAW,GAAG,CAAC,CAAC;AACtB,MAAM,mBAAmB,GAAG,IAAI,CAAC;AAEjC,sEAAsE;AACtE,MAAM,mBAAmB,GAAG,EAAE,CAAC;AAC/B,MAAM,oBAAoB,GAAG,MAAM,CAAC;AAEpC,MAAM,CAAC,MAAM,UAAU,GACrB,iHAAiH,CAAC;AASpH,MAAM,OAAO,YAAY;IACN,MAAM,CAAS;IACxB,OAAO,GAAmB,IAAI,CAAC;IAC/B,cAAc,GAAyB,IAAI,CAAC;IACnC,KAAK,GAAG,IAAI,QAAQ,CAAU,YAAY,CAAC,CAAC;IAC5C,WAAW,GAAG,IAAI,WAAW,CAAC,mBAAmB,EAAE,oBAAoB,CAAC,CAAC;IACzE,QAAQ,GAAG,IAAI,GAAG,EAA4B,CAAC;IAC/C,eAAe,CAAkB;IAElD,YAAY,MAAe,EAAE,eAAiC;QAC5D,IAAI,CAAC,MAAM,GAAG,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,IAAI,cAAc,CAAC;QACvE,IAAI,CAAC,eAAe,GAAG,eAAe,IAAI,qBAAqB,EAAE,CAAC;IACpE,CAAC;IAED,IAAI,OAAO;QACT,OAAO,WAAW,IAAI,CAAC,MAAM,EAAE,CAAC;IAClC,CAAC;IAEO,KAAK,CAAC,gBAAgB;QAC5B,IAAI,CAAC,OAAO,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACxE,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,cAAc;QAC1B,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,CAAC;YACzB,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,gBAAgB,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE;gBACzD,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;YAC7B,CAAC,CAAC,CAAC;QACL,CAAC;QACD,OAAO,IAAI,CAAC,cAAc,CAAC;IAC7B,CAAC;IAEO,cAAc;QACpB,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO,KAAK,CAAC;QAChC,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,GAAG,cAAc,CAAC;IAC9D,CAAC;IAEO,KAAK,CAAC,aAAa;QACzB,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,CAAC;YAC3B,MAAM,IAAI,CAAC,cAAc,EAAE,CAAC;QAC9B,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YAClB,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;QACxD,CAAC;QACD,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC;IAC7B,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,cAAc,CAC1B,GAAW,EACX,MAAc,EACd,SAAqB,KAAK,EAC1B,IAA8B;QAE9B,IAAI,SAAS,GAAiB,IAAI,CAAC;QACnC,IAAI,UAAU,GAAG,mBAAmB,CAAC;QAErC,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,WAAW,EAAE,OAAO,EAAE,EAAE,CAAC;YACxD,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;gBAChB,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC;YAClE,CAAC;YAED,IAAI,CAAC;gBACH,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;gBACzC,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,kBAAkB,CAAC,CAAC;gBAEzE,IAAI,CAAC;oBACH,MAAM,OAAO,GAA2B;wBACtC,YAAY,EAAE,UAAU;wBACxB,MAAM,EAAE,mCAAmC;wBAC3C,MAAM,EAAE,MAAM;wBACd,OAAO,EAAE,WAAW,IAAI,CAAC,MAAM,GAAG;wBAClC,MAAM,EAAE,WAAW,IAAI,CAAC,MAAM,EAAE;qBACjC,CAAC;oBAEF,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;wBACvB,OAAO,CAAC,cAAc,CAAC,GAAG,kBAAkB,CAAC;oBAC/C,CAAC;oBAED,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;wBAChC,MAAM;wBACN,OAAO;wBACP,IAAI,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS;wBAC3D,MAAM,EAAE,UAAU,CAAC,MAAM;qBAC1B,CAAC,CAAC;oBAEH,6BAA6B;oBAC7B,IAAI,QAAQ,CAAC,EAAE,EAAE,CAAC;wBAChB,OAAO,QAAQ,CAAC;oBAClB,CAAC;oBAED,iEAAiE;oBACjE,IAAI,QAAQ,CAAC,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,MAAM,GAAG,GAAG,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;wBAC/E,OAAO,QAAQ,CAAC;oBAClB,CAAC;oBAED,uEAAuE;oBACvE,MAAM,UAAU,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;oBACvD,UAAU,GAAG,UAAU;wBACrB,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,UAAU,EAAE,EAAE,CAAC,GAAG,IAAI,EAAE,MAAM,CAAC;wBACnD,CAAC,CAAC,mBAAmB,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;oBAC/C,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC;oBAC9B,SAAS,GAAG,IAAI,KAAK,CAAC,qBAAqB,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;oBACrF,SAAS;gBACX,CAAC;wBAAS,CAAC;oBACT,YAAY,CAAC,OAAO,CAAC,CAAC;gBACxB,CAAC;YACH,CAAC;YAAC,OAAO,KAAc,EAAE,CAAC;gBACxB,SAAS,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;gBACtE,IAAI,KAAK,YAAY,YAAY,IAAI,KAAK,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;oBACjE,SAAS,GAAG,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC;gBAC7C,CAAC;YACH,CAAC;QACH,CAAC;QAED,MAAM,SAAS,IAAI,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;IAC/D,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,GAAG,CAAI,IAAY,EAAE,MAA+B;QACxD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,UAAU,IAAI,EAAE,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QACpD,IAAI,MAAM,EAAE,CAAC;YACX,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;gBAClD,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;oBACxC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;gBACnC,CAAC;YACH,CAAC;QACH,CAAC;QAED,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,EAAE,CAAC;QAEhC,oBAAoB;QACpB,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACxC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,OAAO,MAAW,CAAC;QACrB,CAAC;QAED,mDAAmD;QACnD,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC7C,IAAI,QAAQ,EAAE,CAAC;YACb,OAAO,QAAsB,CAAC;QAChC,CAAC;QAED,MAAM,OAAO,GAAG,IAAI,CAAC,aAAa,CAAI,QAAQ,CAAC,CAAC;QAChD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAErC,IAAI,CAAC;YACH,OAAO,MAAM,OAAO,CAAC;QACvB,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QACjC,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,aAAa,CAAI,QAAgB;QAC7C,qDAAqD;QACrD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC;QAE1C,MAAM,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC;QAEjC,IAAI,QAAQ,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAE3D,yBAAyB;QACzB,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YACvD,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC;YAC9B,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;YACpB,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC;YAC7C,QAAQ,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;QAC5D,CAAC;QAED,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,qBAAqB,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;QACjF,CAAC;QAED,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAM,CAAC;QAC1C,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QAC/B,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,OAAO,CAAC,GAAW;QACvB,MAAM,QAAQ,GAAG,QAAQ,GAAG,EAAE,CAAC;QAE/B,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACxC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,OAAO,MAAgB,CAAC;QAC1B,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC;QAC1C,MAAM,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC;QAEjC,IAAI,QAAQ,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QAEtD,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YACvD,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC;YAC9B,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;YACpB,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC;YAC7C,QAAQ,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QACvD,CAAC;QAED,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,sBAAsB,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;QAClF,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QACnC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QAC/B,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,OAAO;QACX,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;QACrB,MAAM,IAAI,CAAC,eAAe,CAAC,OAAO,EAAE,CAAC;IACvC,CAAC;CACF;AAED,gCAAgC;AAChC,IAAI,cAAc,GAAwB,IAAI,CAAC;AAE/C,MAAM,UAAU,SAAS;IACvB,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,cAAc,GAAG,IAAI,YAAY,EAAE,CAAC;IACtC,CAAC;IACD,OAAO,cAAc,CAAC;AACxB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa;IACjC,IAAI,cAAc,EAAE,CAAC;QACnB,MAAM,cAAc,CAAC,OAAO,EAAE,CAAC;QAC/B,cAAc,GAAG,IAAI,CAAC;IACxB,CAAC;AACH,CAAC"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Browser-based session provider for Vinted.
3
+ *
4
+ * Uses puppeteer-extra with the stealth plugin to solve Cloudflare challenges,
5
+ * extract cookies, and return them for use with regular fetch calls.
6
+ * The browser is launched only during session refresh and closed immediately after.
7
+ */
8
+ export interface SessionCookies {
9
+ readonly cookie: string;
10
+ readonly fetchedAt: number;
11
+ }
12
+ export interface SessionProviderConfig {
13
+ readonly timeoutMs?: number;
14
+ readonly executablePath?: string;
15
+ }
16
+ export interface SessionProvider {
17
+ refreshSession(domain: string): Promise<SessionCookies>;
18
+ destroy(): Promise<void>;
19
+ }
20
+ export declare class BrowserSessionProvider implements SessionProvider {
21
+ private readonly config;
22
+ private browser;
23
+ constructor(config?: SessionProviderConfig);
24
+ refreshSession(domain: string): Promise<SessionCookies>;
25
+ destroy(): Promise<void>;
26
+ private solveChallenge;
27
+ private closeBrowser;
28
+ }
29
+ export declare function createSessionProvider(config?: SessionProviderConfig): SessionProvider;
30
+ //# sourceMappingURL=session-provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-provider.d.ts","sourceRoot":"","sources":["../../src/api/session-provider.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAaH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CAClC;AAED,MAAM,WAAW,eAAe;IAC9B,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IACxD,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAWD,qBAAa,sBAAuB,YAAW,eAAe;IAC5D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkC;IACzD,OAAO,CAAC,OAAO,CAAwB;gBAE3B,MAAM,CAAC,EAAE,qBAAqB;IAIpC,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAavD,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;YAIhB,cAAc;YAsBd,YAAY;CAW3B;AAED,wBAAgB,qBAAqB,CAAC,MAAM,CAAC,EAAE,qBAAqB,GAAG,eAAe,CAErF"}