@lofder/dsers-mcp-product 1.3.7 → 1.3.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 +121 -55
- package/dist/auth/browser-finder.d.ts.map +1 -1
- package/dist/auth/browser-finder.js +7 -2
- package/dist/auth/browser-finder.js.map +1 -1
- package/dist/auth/cdp-session.d.ts.map +1 -1
- package/dist/auth/cdp-session.js +89 -38
- package/dist/auth/cdp-session.js.map +1 -1
- package/dist/auth/safari-fallback.d.ts.map +1 -1
- package/dist/auth/safari-fallback.js +7 -2
- package/dist/auth/safari-fallback.js.map +1 -1
- package/dist/auth/terminal-prompt.d.ts.map +1 -1
- package/dist/auth/terminal-prompt.js +6 -3
- package/dist/auth/terminal-prompt.js.map +1 -1
- package/dist/auth/token-store.d.ts.map +1 -1
- package/dist/auth/token-store.js +5 -3
- package/dist/auth/token-store.js.map +1 -1
- package/dist/cli.js +14 -6
- package/dist/cli.js.map +1 -1
- package/dist/dsers/account.d.ts.map +1 -1
- package/dist/dsers/account.js.map +1 -1
- package/dist/dsers/auth.d.ts.map +1 -1
- package/dist/dsers/auth.js +6 -2
- package/dist/dsers/auth.js.map +1 -1
- package/dist/dsers/client.d.ts.map +1 -1
- package/dist/dsers/client.js +1 -6
- package/dist/dsers/client.js.map +1 -1
- package/dist/dsers/config.d.ts.map +1 -1
- package/dist/dsers/config.js +4 -3
- package/dist/dsers/config.js.map +1 -1
- package/dist/dsers/product.d.ts.map +1 -1
- package/dist/dsers/product.js +5 -10
- package/dist/dsers/product.js.map +1 -1
- package/dist/dsers/settings.d.ts.map +1 -1
- package/dist/dsers/settings.js.map +1 -1
- package/dist/error-map.d.ts.map +1 -1
- package/dist/error-map.js +4 -6
- package/dist/error-map.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -5
- package/dist/index.js.map +1 -1
- package/dist/instructions.d.ts.map +1 -1
- package/dist/instructions.js +12 -1
- package/dist/instructions.js.map +1 -1
- package/dist/job-store.d.ts.map +1 -1
- package/dist/job-store.js.map +1 -1
- package/dist/oauth/crypto.d.ts.map +1 -1
- package/dist/oauth/crypto.js.map +1 -1
- package/dist/provider/helpers.d.ts +28 -0
- package/dist/provider/helpers.d.ts.map +1 -0
- package/dist/provider/helpers.js +265 -0
- package/dist/provider/helpers.js.map +1 -0
- package/dist/provider/import-ops.d.ts +23 -0
- package/dist/provider/import-ops.d.ts.map +1 -0
- package/dist/provider/import-ops.js +350 -0
- package/dist/provider/import-ops.js.map +1 -0
- package/dist/provider/index.d.ts +38 -0
- package/dist/provider/index.d.ts.map +1 -0
- package/dist/provider/index.js +172 -0
- package/dist/provider/index.js.map +1 -0
- package/dist/provider/normalize.d.ts +7 -0
- package/dist/provider/normalize.d.ts.map +1 -0
- package/dist/provider/normalize.js +239 -0
- package/dist/provider/normalize.js.map +1 -0
- package/dist/provider/push.d.ts +15 -0
- package/dist/provider/push.d.ts.map +1 -0
- package/dist/provider/push.js +420 -0
- package/dist/provider/push.js.map +1 -0
- package/dist/provider/store.d.ts +7 -0
- package/dist/provider/store.d.ts.map +1 -0
- package/dist/provider/store.js +152 -0
- package/dist/provider/store.js.map +1 -0
- package/dist/provider.d.ts.map +1 -1
- package/dist/provider.js +82 -241
- package/dist/provider.js.map +1 -1
- package/dist/push-guard.d.ts.map +1 -1
- package/dist/push-guard.js +3 -7
- package/dist/push-guard.js.map +1 -1
- package/dist/push-options.js.map +1 -1
- package/dist/resolver.d.ts.map +1 -1
- package/dist/resolver.js +1 -2
- package/dist/resolver.js.map +1 -1
- package/dist/rules.d.ts.map +1 -1
- package/dist/rules.js +39 -13
- package/dist/rules.js.map +1 -1
- package/dist/service/browse-shared.d.ts +6 -0
- package/dist/service/browse-shared.d.ts.map +1 -0
- package/dist/service/browse-shared.js +26 -0
- package/dist/service/browse-shared.js.map +1 -0
- package/dist/service/browse.d.ts +20 -0
- package/dist/service/browse.d.ts.map +1 -0
- package/dist/service/browse.js +159 -0
- package/dist/service/browse.js.map +1 -0
- package/dist/service/find-product.d.ts +12 -0
- package/dist/service/find-product.d.ts.map +1 -0
- package/dist/service/find-product.js +51 -0
- package/dist/service/find-product.js.map +1 -0
- package/dist/service/helpers.d.ts +20 -0
- package/dist/service/helpers.d.ts.map +1 -0
- package/dist/service/helpers.js +163 -0
- package/dist/service/helpers.js.map +1 -0
- package/dist/service/import-flow.d.ts +6 -0
- package/dist/service/import-flow.d.ts.map +1 -0
- package/dist/service/import-flow.js +141 -0
- package/dist/service/import-flow.js.map +1 -0
- package/dist/service/import-list.d.ts +6 -0
- package/dist/service/import-list.d.ts.map +1 -0
- package/dist/service/import-list.js +103 -0
- package/dist/service/import-list.js.map +1 -0
- package/dist/service/index.d.ts +37 -0
- package/dist/service/index.d.ts.map +1 -0
- package/dist/service/index.js +132 -0
- package/dist/service/index.js.map +1 -0
- package/dist/service/my-products.d.ts +7 -0
- package/dist/service/my-products.d.ts.map +1 -0
- package/dist/service/my-products.js +51 -0
- package/dist/service/my-products.js.map +1 -0
- package/dist/service/preview.d.ts +7 -0
- package/dist/service/preview.d.ts.map +1 -0
- package/dist/service/preview.js +235 -0
- package/dist/service/preview.js.map +1 -0
- package/dist/service/push-flow.d.ts +12 -0
- package/dist/service/push-flow.d.ts.map +1 -0
- package/dist/service/push-flow.js +251 -0
- package/dist/service/push-flow.js.map +1 -0
- package/dist/service/status.d.ts +6 -0
- package/dist/service/status.d.ts.map +1 -0
- package/dist/service/status.js +90 -0
- package/dist/service/status.js.map +1 -0
- package/dist/service.d.ts +1 -1
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +23 -32
- package/dist/service.js.map +1 -1
- package/dist/tools.d.ts +1 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +162 -114
- package/dist/tools.js.map +1 -1
- package/dist/types/dsers-api.d.ts +133 -0
- package/dist/types/dsers-api.d.ts.map +1 -0
- package/dist/types/dsers-api.js +2 -0
- package/dist/types/dsers-api.js.map +1 -0
- package/package.json +15 -5
package/README.md
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
# DSers MCP Product —
|
|
1
|
+
# DSers MCP Product — Dropshipping Automation: AliExpress to Shopify & Wix with AI
|
|
2
2
|
|
|
3
|
+
[](https://safeskill.dev/scan/@lofder/dsers-mcp-product)
|
|
3
4
|
[](https://smithery.ai/server/@dsersx/product-mcp)
|
|
4
5
|
[](https://www.npmjs.com/package/@lofder/dsers-mcp-product)
|
|
5
6
|
[](https://registry.modelcontextprotocol.io/servers/io.github.lofder/dsers-mcp-product)
|
|
6
7
|
[](https://glama.ai/mcp/servers/lofder/dsers-mcp-product)
|
|
7
8
|
|
|
8
|
-
> An open-source MCP server to automate DSers product import, bulk edit
|
|
9
|
+
> An open-source MCP server to automate DSers product sourcing, import, bulk edit, and push to Shopify or Wix using AI.
|
|
9
10
|
|
|
10
11
|
> [English](#english) | [中文](#中文)
|
|
11
12
|
|
|
@@ -15,13 +16,13 @@
|
|
|
15
16
|
|
|
16
17
|
## English
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
**DSers MCP Product** is an open-source [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) server that lets AI Agents automate the entire DSers import workflow — from AliExpress / Alibaba / [Accio.com](https://www.accio.com/) product URL to Shopify or Wix store listing. Bulk import, batch edit variants, clean AliExpress titles, apply pricing rules, and push to multiple stores — all with a single sentence to your AI agent.
|
|
19
|
+
**DSers MCP Product** is an open-source [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) server that lets AI Agents automate the entire DSers import workflow — from product sourcing to Shopify or Wix store listing. Search the DSers product pool, import from AliExpress / Alibaba / [Accio.com](https://www.accio.com/), bulk edit variants, apply pricing rules, and push to multiple stores — all with a single sentence to your AI agent.
|
|
21
20
|
|
|
22
21
|
#### What can it do?
|
|
23
22
|
|
|
23
|
+
- **Product sourcing** — search the DSers product pool by keyword or image, find products to sell
|
|
24
24
|
- **One-click import** — paste a product link, your AI agent imports it into DSers automatically
|
|
25
|
+
- **Browse your catalog** — view your import staging list and products already pushed to stores
|
|
25
26
|
- **Clean up titles** — strips the messy keyword-stuffed AliExpress titles into something readable
|
|
26
27
|
- **Pricing rules** — markup multiplier (e.g. 2.5x), fixed markup (e.g. +$5), compare-at / sale prices
|
|
27
28
|
- **Batch import** — import multiple products at once with a list of URLs
|
|
@@ -44,6 +45,7 @@ The server is hosted on [Vercel](https://dsers-mcp-product.vercel.app/api/mcp) a
|
|
|
44
45
|
| MCP Marketplace | [mcp-marketplace.io](https://mcp-marketplace.io/server/io-github-lofder-dsers-mcp-product) |
|
|
45
46
|
| awesome-mcp-servers | [punkpeye/awesome-mcp-servers](https://github.com/punkpeye/awesome-mcp-servers) |
|
|
46
47
|
| Dev.to | [Article: I Built an MCP Server to Automate Dropshipping](https://dev.to/_95a3e57463e6442feacd0/i-built-an-mcp-server-to-automate-dropshipping-product-imports-3m5b) |
|
|
48
|
+
| Dev.to | [Tutorial: How to Automate AliExpress to Shopify with AI](https://dev.to/_95a3e57463e6442feacd0/how-to-automate-aliexpress-to-shopify-product-import-with-ai-step-by-step-guide-3f5a) |
|
|
47
49
|
|
|
48
50
|
### Supported product sources
|
|
49
51
|
|
|
@@ -71,7 +73,6 @@ This works for both AliExpress and Alibaba products found on Accio.
|
|
|
71
73
|
| [ARCHITECTURE.md](ARCHITECTURE.md) | Three-layer architecture, directory structure, data flow |
|
|
72
74
|
| [USAGE.md](USAGE.md) | Installation, client config (Cursor, Claude Desktop), scenario examples |
|
|
73
75
|
| [SKILL.md](SKILL.md) | AI agent instruction file — workflow, rules, push options, error handling |
|
|
74
|
-
| [SKILL-CN.md](SKILL-CN.md) | Chinese human-readable guide for SKILL.md |
|
|
75
76
|
|
|
76
77
|
### What You Need
|
|
77
78
|
|
|
@@ -139,6 +140,12 @@ Once set up, just talk to your AI agent in plain language:
|
|
|
139
140
|
|
|
140
141
|
> "Batch import these 3 products and push them all to my store: [URL1] [URL2] [URL3]"
|
|
141
142
|
|
|
143
|
+
> "Search for phone cases under $5 on the DSers product pool"
|
|
144
|
+
|
|
145
|
+
> "Show me what's in my import list"
|
|
146
|
+
|
|
147
|
+
> "What products have I pushed to my store?"
|
|
148
|
+
|
|
142
149
|
> "Push this product to all my connected stores"
|
|
143
150
|
|
|
144
151
|
> "Rewrite the title and description for SEO, then push to my store"
|
|
@@ -156,21 +163,11 @@ Or browse at [smithery.ai/server/@dsersx/product-mcp](https://smithery.ai/server
|
|
|
156
163
|
### Install from Source
|
|
157
164
|
|
|
158
165
|
```bash
|
|
159
|
-
# Clone
|
|
160
166
|
git clone https://github.com/lofder/dsers-mcp-product.git
|
|
161
167
|
cd dsers-mcp-product
|
|
162
|
-
|
|
163
|
-
# Install
|
|
164
168
|
npm install
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
cp .env.example .env
|
|
168
|
-
|
|
169
|
-
# Type check
|
|
170
|
-
npx tsc --noEmit
|
|
171
|
-
|
|
172
|
-
# Run with Smithery dev
|
|
173
|
-
npx @smithery/cli dev ./src/index.ts
|
|
169
|
+
npm run build
|
|
170
|
+
npm test
|
|
174
171
|
```
|
|
175
172
|
|
|
176
173
|
### Project Structure
|
|
@@ -178,32 +175,48 @@ npx @smithery/cli dev ./src/index.ts
|
|
|
178
175
|
```
|
|
179
176
|
dsers-mcp-product/
|
|
180
177
|
├── src/
|
|
181
|
-
│ ├──
|
|
182
|
-
│ ├──
|
|
183
|
-
│ ├──
|
|
178
|
+
│ ├── cli.ts # npx entry — stdio transport, login/logout
|
|
179
|
+
│ ├── index.ts # MCP server init, tool registration
|
|
180
|
+
│ ├── instructions.ts # Server-level prompts (agent instructions)
|
|
181
|
+
│ ├── tools.ts # 12 MCP tools — schema + handler
|
|
184
182
|
│ ├── rules.ts # Rule validation & application engine
|
|
183
|
+
│ ├── push-guard.ts # Pre-push safety checks
|
|
185
184
|
│ ├── push-options.ts # Push option normalization
|
|
186
185
|
│ ├── resolver.ts # URL normalization (AliExpress/Alibaba/Accio)
|
|
187
|
-
│ ├──
|
|
188
|
-
│
|
|
189
|
-
│
|
|
190
|
-
│
|
|
191
|
-
│
|
|
192
|
-
│
|
|
193
|
-
│
|
|
194
|
-
│
|
|
195
|
-
├──
|
|
196
|
-
├──
|
|
186
|
+
│ ├── provider/ # DSers API adapter (split by concern)
|
|
187
|
+
│ │ ├── index.ts # Provider class + interface
|
|
188
|
+
│ │ ├── store.ts # Store discovery, platform detection
|
|
189
|
+
│ │ ├── import-ops.ts # Import, save draft, delete
|
|
190
|
+
│ │ ├── push.ts # Push execution, shipping logistics
|
|
191
|
+
│ │ ├── normalize.ts # Data normalization
|
|
192
|
+
│ │ └── helpers.ts # Shared utilities
|
|
193
|
+
│ ├── service/ # Business logic orchestration
|
|
194
|
+
│ │ ├── index.ts # ImportFlowService class
|
|
195
|
+
│ │ ├── import-flow.ts # Import workflows
|
|
196
|
+
│ │ ├── push-flow.ts # Push workflows
|
|
197
|
+
│ │ ├── preview.ts # Preview & visibility
|
|
198
|
+
│ │ ├── status.ts # Job status & deletion
|
|
199
|
+
│ │ ├── import-list.ts # Import list browsing (with enrich)
|
|
200
|
+
│ │ ├── my-products.ts # Pushed products browsing
|
|
201
|
+
│ │ └── find-product.ts # Product pool search
|
|
202
|
+
│ ├── dsers/ # Low-level DSers API wrappers
|
|
203
|
+
│ │ ├── config.ts # Configuration
|
|
204
|
+
│ │ ├── auth.ts # Session management
|
|
205
|
+
│ │ ├── client.ts # Authenticated HTTP client
|
|
206
|
+
│ │ ├── account.ts # Store & user APIs
|
|
207
|
+
│ │ ├── product.ts # Product & import APIs
|
|
208
|
+
│ │ └── settings.ts # Shipping, pricing, billing APIs
|
|
209
|
+
│ └── auth/ # Browser login (CDP)
|
|
210
|
+
├── test/ # Vitest unit tests (298 tests)
|
|
197
211
|
├── package.json
|
|
198
|
-
|
|
199
|
-
└── .env.example
|
|
212
|
+
└── tsconfig.json
|
|
200
213
|
```
|
|
201
214
|
|
|
202
|
-
###
|
|
215
|
+
### Twelve Tools
|
|
203
216
|
|
|
204
217
|
| # | Tool | What it does |
|
|
205
218
|
|---|------|-------------|
|
|
206
|
-
| 1 | `dsers_store_discover` | See your connected stores,
|
|
219
|
+
| 1 | `dsers_store_discover` | See your connected stores, shipping methods, pricing rules, and capabilities |
|
|
207
220
|
| 2 | `dsers_rules_validate` | Test your pricing or title rules before applying — catches mistakes early |
|
|
208
221
|
| 3 | `dsers_product_import` | Paste a product URL, optionally apply pricing/title rules, and get a preview before pushing |
|
|
209
222
|
| 4 | `dsers_product_preview` | Review a product you already imported — title, price, variants, stock at a glance |
|
|
@@ -212,8 +225,11 @@ dsers-mcp-product/
|
|
|
212
225
|
| 7 | `dsers_store_push` | Send products to your Shopify or Wix store — one at a time, in bulk, or to all stores at once |
|
|
213
226
|
| 8 | `dsers_job_status` | Check if a push finished and whether it succeeded |
|
|
214
227
|
| 9 | `dsers_product_delete` | Delete a product from the DSers import list (irreversible, requires confirmation) |
|
|
228
|
+
| 10 | `dsers_import_list` | Browse your import staging list with cost & sell price, stock, markup status |
|
|
229
|
+
| 11 | `dsers_my_products` | See products already pushed to a store, with supplier links for re-import |
|
|
230
|
+
| 12 | `dsers_find_product` | Search the DSers product pool by keyword or image — results link directly to import |
|
|
215
231
|
|
|
216
|
-
All tools return clear error messages so your AI agent knows what went wrong and what to do next
|
|
232
|
+
All tools return clear error messages so your AI agent knows what went wrong and what to do next.
|
|
217
233
|
|
|
218
234
|
### Pre-Push Safety Checks
|
|
219
235
|
|
|
@@ -252,11 +268,38 @@ Ready-made workflows your AI client can use directly:
|
|
|
252
268
|
|
|
253
269
|
### What's Next
|
|
254
270
|
|
|
271
|
+
- Product pool search enhancements (category filters, URL-based search, product detail view)
|
|
255
272
|
- Support more store platforms that DSers already connects to (eBay, Wish, etc.)
|
|
256
273
|
- Smarter pricing rule templates
|
|
257
274
|
- More granular inventory sync options
|
|
258
275
|
|
|
259
|
-
Got an idea or feature request? [Open an issue](https://github.com/lofder/dsers-mcp-product/issues) —
|
|
276
|
+
Got an idea or feature request? [Open an issue](https://github.com/lofder/dsers-mcp-product/issues) — contributions are very welcome.
|
|
277
|
+
|
|
278
|
+
### FAQ
|
|
279
|
+
|
|
280
|
+
**What is DSers MCP Product?**
|
|
281
|
+
DSers MCP Product is a free, open-source MCP server that automates dropshipping product imports from AliExpress, Alibaba, and 1688 to Shopify and Wix stores using AI agents like Claude and Cursor. Instead of manually copying product data, you tell your AI agent what to do in plain English.
|
|
282
|
+
|
|
283
|
+
**How does it work?**
|
|
284
|
+
You type a command like "Import this product, mark up 2.5x, push to my store" in Cursor or Claude Desktop. The MCP server handles the entire workflow: fetching product data, applying pricing rules, editing variants, and pushing to your connected stores.
|
|
285
|
+
|
|
286
|
+
**Is it free?**
|
|
287
|
+
Yes. The tool is open-source (MIT license) and completely free to use. You only need a free DSers account and a Shopify or Wix store connected in DSers.
|
|
288
|
+
|
|
289
|
+
**Is it safe? Do I need to share my password?**
|
|
290
|
+
No passwords are stored or transmitted. Authentication uses a zero-password browser login — you log in on DSers's own website, and the tool picks up the session token. Your credentials never touch the MCP server. The project scored 92/100 on [SafeSkill](https://safeskill.dev/scan/@lofder/dsers-mcp-product) security scanning.
|
|
291
|
+
|
|
292
|
+
**What AI clients does it support?**
|
|
293
|
+
Cursor, Claude Desktop, Claude Code, Windsurf, and any MCP-compatible client that supports stdio transport.
|
|
294
|
+
|
|
295
|
+
**How is this different from AliDropify, AutoDS, or other dropshipping tools?**
|
|
296
|
+
Most dropshipping tools have their own UI and require you to click through web interfaces. DSers MCP Product takes a fundamentally different approach — it connects directly to your AI agent, so you automate workflows through conversation instead of clicking buttons. It's also open-source and free, with no subscription tiers.
|
|
297
|
+
|
|
298
|
+
**What product sources does it support?**
|
|
299
|
+
AliExpress, Alibaba.com, Accio.com (Alibaba's AI sourcing tool), and 1688 (requires DSers authorization).
|
|
300
|
+
|
|
301
|
+
**Can I push to multiple stores at once?**
|
|
302
|
+
Yes. The `dsers_store_push` tool supports pushing a single product to all your connected Shopify and Wix stores in one command, with per-store pricing and visibility options.
|
|
260
303
|
|
|
261
304
|
### Also Available
|
|
262
305
|
|
|
@@ -272,13 +315,13 @@ MIT
|
|
|
272
315
|
|
|
273
316
|
## 中文
|
|
274
317
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
**DSers MCP Product** 是一个开源的 [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) 服务器,让 AI Agent 自动完成 DSers 的整个商品导入流程 —— 从速卖通 / Alibaba / [Accio.com](https://www.accio.com/) 商品链接到 Shopify 或 Wix 店铺上架。批量导入、批量编辑变体、清理速卖通标题、应用定价规则、推送到多个店铺 —— 只需一句话给你的 AI agent。
|
|
318
|
+
**DSers MCP Product** 是一个开源的 [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) 服务器,让 AI Agent 自动完成 DSers 的整个商品流程 —— 从选品搜索到 Shopify 或 Wix 店铺上架。搜索 DSers 商品池、从速卖通 / Alibaba / [Accio.com](https://www.accio.com/) 导入商品、批量编辑变体、应用定价规则、推送到多个店铺 —— 只需一句话给你的 AI agent。
|
|
278
319
|
|
|
279
320
|
#### 能做什么?
|
|
280
321
|
|
|
322
|
+
- **选品搜索** — 在 DSers 商品池里按关键词或图片搜索,找到值得卖的商品
|
|
281
323
|
- **一句话导入** — 贴个商品链接,AI 助手自动导入到 DSers
|
|
324
|
+
- **浏览商品库** — 查看导入待推送列表和已上架商品,包含成本价、售价、加价状态
|
|
282
325
|
- **标题清理** — 把速卖通那些关键词堆砌的乱标题整理成人话
|
|
283
326
|
- **定价规则** — 加价倍率(比如 2.5 倍)、固定加价(比如 +5 美金)、划线价
|
|
284
327
|
- **批量导入** — 一次丢一堆链接,全部导入
|
|
@@ -301,6 +344,7 @@ MIT
|
|
|
301
344
|
| MCP Marketplace | [mcp-marketplace.io](https://mcp-marketplace.io/server/io-github-lofder-dsers-mcp-product) |
|
|
302
345
|
| awesome-mcp-servers | [punkpeye/awesome-mcp-servers](https://github.com/punkpeye/awesome-mcp-servers) |
|
|
303
346
|
| Dev.to | [技术文章:I Built an MCP Server to Automate Dropshipping](https://dev.to/_95a3e57463e6442feacd0/i-built-an-mcp-server-to-automate-dropshipping-product-imports-3m5b) |
|
|
347
|
+
| Dev.to | [教程:How to Automate AliExpress to Shopify with AI](https://dev.to/_95a3e57463e6442feacd0/how-to-automate-aliexpress-to-shopify-product-import-with-ai-step-by-step-guide-3f5a) |
|
|
304
348
|
|
|
305
349
|
### 支持的商品来源
|
|
306
350
|
|
|
@@ -328,7 +372,6 @@ Accio 上搜出来的速卖通和阿里巴巴商品都能用。
|
|
|
328
372
|
| [ARCHITECTURE.md](ARCHITECTURE.md) | 三层架构、目录结构、数据流 |
|
|
329
373
|
| [USAGE.md](USAGE.md) | 安装、客户端配置(Cursor、Claude Desktop)、使用场景 |
|
|
330
374
|
| [SKILL.md](SKILL.md) | AI agent 指令文件 — 工作流、规则、推送选项、错误处理 |
|
|
331
|
-
| [SKILL-CN.md](SKILL-CN.md) | SKILL.md 的中文说明 |
|
|
332
375
|
|
|
333
376
|
### 使用前提
|
|
334
377
|
|
|
@@ -396,6 +439,12 @@ npx @lofder/dsers-mcp-product login
|
|
|
396
439
|
|
|
397
440
|
> "批量导入这 3 个商品,全部推到店铺:[链接1] [链接2] [链接3]"
|
|
398
441
|
|
|
442
|
+
> "帮我搜一下 5 美金以下的手机壳"
|
|
443
|
+
|
|
444
|
+
> "看下我的导入列表里有什么"
|
|
445
|
+
|
|
446
|
+
> "我已经推送了哪些商品到店铺?"
|
|
447
|
+
|
|
399
448
|
> "把这个商品推到我所有店铺"
|
|
400
449
|
|
|
401
450
|
> "帮我把标题和描述重写一下做 SEO 优化,然后推送"
|
|
@@ -413,24 +462,14 @@ npx @smithery/cli mcp add @dsersx/product-mcp --client cursor
|
|
|
413
462
|
### 从源码安装
|
|
414
463
|
|
|
415
464
|
```bash
|
|
416
|
-
# 克隆
|
|
417
465
|
git clone https://github.com/lofder/dsers-mcp-product.git
|
|
418
466
|
cd dsers-mcp-product
|
|
419
|
-
|
|
420
|
-
# 安装
|
|
421
467
|
npm install
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
cp .env.example .env
|
|
425
|
-
|
|
426
|
-
# 类型检查
|
|
427
|
-
npx tsc --noEmit
|
|
428
|
-
|
|
429
|
-
# 开发运行
|
|
430
|
-
npx @smithery/cli dev ./src/index.ts
|
|
468
|
+
npm run build
|
|
469
|
+
npm test
|
|
431
470
|
```
|
|
432
471
|
|
|
433
|
-
###
|
|
472
|
+
### 十二个工具
|
|
434
473
|
|
|
435
474
|
| # | 工具 | 干什么的 |
|
|
436
475
|
|---|------|---------|
|
|
@@ -443,8 +482,11 @@ npx @smithery/cli dev ./src/index.ts
|
|
|
443
482
|
| 7 | `dsers_store_push` | 把商品推到你的 Shopify 或 Wix 店铺 — 单个推、批量推、或一次推到所有店铺 |
|
|
444
483
|
| 8 | `dsers_job_status` | 看看推送完了没、成功了没 |
|
|
445
484
|
| 9 | `dsers_product_delete` | 从 DSers 导入列表中删除商品(不可恢复,需确认) |
|
|
485
|
+
| 10 | `dsers_import_list` | 浏览导入待推送列表,含成本价、售价、库存、加价状态 |
|
|
486
|
+
| 11 | `dsers_my_products` | 查看已推到店铺的商品,带供应商链接方便重新导入 |
|
|
487
|
+
| 12 | `dsers_find_product` | 在 DSers 商品池搜索,支持关键词和以图搜图,结果可直接导入 |
|
|
446
488
|
|
|
447
|
-
报错时会返回清晰的消息,AI
|
|
489
|
+
报错时会返回清晰的消息,AI 助手能看懂出了什么问题、该怎么办。
|
|
448
490
|
|
|
449
491
|
### 推送前安全校验
|
|
450
492
|
|
|
@@ -475,12 +517,36 @@ MCP 客户端可直接展示给用户的工作流模板:
|
|
|
475
517
|
|
|
476
518
|
### 后续计划
|
|
477
519
|
|
|
520
|
+
- 商品池搜索增强(分类过滤、URL 搜索、商品详情查看)
|
|
478
521
|
- 支持更多 DSers 已接入的店铺平台(eBay、Wish 等)
|
|
479
522
|
- 更智能的定价规则模板
|
|
480
523
|
- 更精细的库存同步选项
|
|
481
524
|
|
|
482
525
|
有想法或需求?欢迎 [提 issue](https://github.com/lofder/dsers-mcp-product/issues) —— 非常欢迎其他开发者的建议和贡献。
|
|
483
526
|
|
|
527
|
+
### 常见问题
|
|
528
|
+
|
|
529
|
+
**DSers MCP Product 是什么?**
|
|
530
|
+
一个免费开源的 MCP 服务器,让 AI Agent(如 Claude、Cursor)自动完成从速卖通/Alibaba/1688 到 Shopify/Wix 店铺的商品导入全流程。不用手动复制商品数据,用自然语言对话就能操作。
|
|
531
|
+
|
|
532
|
+
**怎么用?**
|
|
533
|
+
在 Cursor 或 Claude Desktop 里输入"导入这个商品,加价 2.5 倍,推送到我的店铺",MCP 服务器自动完成抓取商品数据、应用定价规则、编辑变体、推送到店铺的全部流程。
|
|
534
|
+
|
|
535
|
+
**收费吗?**
|
|
536
|
+
完全免费。项目开源(MIT 协议),你只需要一个免费的 DSers 账号和已连接的 Shopify 或 Wix 店铺。
|
|
537
|
+
|
|
538
|
+
**安全吗?需要提供密码吗?**
|
|
539
|
+
不需要。登录采用零密码浏览器认证——你在 DSers 官网登录,工具只接收会话令牌,密码永远不会经过 MCP 服务器。项目在 [SafeSkill](https://safeskill.dev/scan/@lofder/dsers-mcp-product) 安全扫描中获得 92/100 分。
|
|
540
|
+
|
|
541
|
+
**支持哪些 AI 客户端?**
|
|
542
|
+
Cursor、Claude Desktop、Claude Code、Windsurf,以及任何支持 stdio 传输的 MCP 兼容客户端。
|
|
543
|
+
|
|
544
|
+
**和 AliDropify、AutoDS 等 Dropshipping 工具有什么区别?**
|
|
545
|
+
大多数 Dropshipping 工具有自己的界面,需要点来点去。DSers MCP Product 完全不同——它直接连接你的 AI Agent,用对话代替点击来自动化工作流。而且完全开源免费,没有订阅分级。
|
|
546
|
+
|
|
547
|
+
**能同时推送到多个店铺吗?**
|
|
548
|
+
可以。`dsers_store_push` 支持一条命令将商品推送到所有已连接的 Shopify 和 Wix 店铺,支持按店铺设置不同的定价和可见性。
|
|
549
|
+
|
|
484
550
|
### 其他版本
|
|
485
551
|
|
|
486
552
|
也提供 [Python 版本](https://github.com/lofder/dsers-mcp-product-py),适用于本地 stdio 部署。
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser-finder.d.ts","sourceRoot":"","sources":["../../src/auth/browser-finder.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;
|
|
1
|
+
{"version":3,"file":"browser-finder.d.ts","sourceRoot":"","sources":["../../src/auth/browser-finder.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AA8FD,wBAAgB,mBAAmB,IAAI,YAAY,GAAG,IAAI,CAMzD"}
|
|
@@ -48,7 +48,10 @@ function findOnMacOS() {
|
|
|
48
48
|
}
|
|
49
49
|
// mdfind fallback for non-standard install locations
|
|
50
50
|
try {
|
|
51
|
-
const result = execSync('mdfind "kMDItemCFBundleIdentifier == com.google.Chrome" 2>/dev/null', {
|
|
51
|
+
const result = execSync('mdfind "kMDItemCFBundleIdentifier == com.google.Chrome" 2>/dev/null', {
|
|
52
|
+
encoding: "utf-8",
|
|
53
|
+
timeout: 3000,
|
|
54
|
+
}).trim();
|
|
52
55
|
if (result) {
|
|
53
56
|
const appPath = result.split("\n")[0];
|
|
54
57
|
const execPath = join(appPath, "Contents/MacOS/Google Chrome");
|
|
@@ -56,7 +59,9 @@ function findOnMacOS() {
|
|
|
56
59
|
return { name: "Google Chrome", path: execPath };
|
|
57
60
|
}
|
|
58
61
|
}
|
|
59
|
-
catch {
|
|
62
|
+
catch {
|
|
63
|
+
/* mdfind unavailable or timed out */
|
|
64
|
+
}
|
|
60
65
|
return null;
|
|
61
66
|
}
|
|
62
67
|
function findOnLinux() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser-finder.js","sourceRoot":"","sources":["../../src/auth/browser-finder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAOjC,MAAM,cAAc,GAAuB;IACzC,CAAC,eAAe,EAAE,8DAA8D,CAAC;IACjF,CAAC,gBAAgB,EAAE,gEAAgE,CAAC;IACpF,CAAC,eAAe,EAAE,8DAA8D,CAAC;IACjF,CAAC,KAAK,EAAE,0CAA0C,CAAC;IACnD,CAAC,UAAU,EAAE,oDAAoD,CAAC;IAClE,CAAC,OAAO,EAAE,8CAA8C,CAAC;IACzD,CAAC,SAAS,EAAE,kDAAkD,CAAC;CAChE,CAAC;AAEF,MAAM,cAAc,GAAuB;IACzC,CAAC,eAAe,EAAE,sBAAsB,CAAC;IACzC,CAAC,eAAe,EAAE,eAAe,CAAC;IAClC,CAAC,UAAU,EAAE,kBAAkB,CAAC;IAChC,CAAC,UAAU,EAAE,UAAU,CAAC;IACxB,CAAC,gBAAgB,EAAE,uBAAuB,CAAC;IAC3C,CAAC,gBAAgB,EAAE,gBAAgB,CAAC;IACpC,CAAC,eAAe,EAAE,eAAe,CAAC;IAClC,CAAC,eAAe,EAAE,sBAAsB,CAAC;IACzC,CAAC,OAAO,EAAE,OAAO,CAAC;IAClB,CAAC,SAAS,EAAE,gBAAgB,CAAC;IAC7B,CAAC,SAAS,EAAE,SAAS,CAAC;CACvB,CAAC;AAEF,MAAM,SAAS,GAAuB;IACpC,CAAC,eAAe,EAAE,yCAAyC,CAAC;IAC5D,CAAC,gBAAgB,EAAE,0CAA0C,CAAC;IAC9D,CAAC,eAAe,EAAE,sDAAsD,CAAC;IACzE,CAAC,OAAO,EAAE,kBAAkB,CAAC;IAC7B,CAAC,SAAS,EAAE,mCAAmC,CAAC;IAChD,CAAC,UAAU,EAAE,mCAAmC,CAAC;CAClD,CAAC;AAEF,SAAS,SAAS,CAAC,GAAW;IAC5B,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAC9C,IAAI,CAAC;QACH,OAAO,QAAQ,CAAC,SAAS,GAAG,cAAc,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,IAAI,CAAC;IACnG,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,WAAW;IAClB,KAAK,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,cAAc,EAAE,CAAC;QACvC,IAAI,UAAU,CAAC,CAAC,CAAC;YAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;IAC9C,CAAC;IACD,qDAAqD;IACrD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,QAAQ,
|
|
1
|
+
{"version":3,"file":"browser-finder.js","sourceRoot":"","sources":["../../src/auth/browser-finder.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cdp-session.d.ts","sourceRoot":"","sources":["../../src/auth/cdp-session.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cdp-session.d.ts","sourceRoot":"","sources":["../../src/auth/cdp-session.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;CACf;AAyQD,wBAAsB,WAAW,CAC/B,WAAW,EAAE,MAAM,EACnB,GAAG,GAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAe,GACpC,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAqGhC"}
|
package/dist/auth/cdp-session.js
CHANGED
|
@@ -5,11 +5,7 @@ import { mkdtempSync, rmSync } from "node:fs";
|
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
6
|
import { join } from "node:path";
|
|
7
7
|
const DSERS_LOGIN_URL = "https://accounts.dsers.com/accounts/login";
|
|
8
|
-
const COOKIE_CHECK_URLS = [
|
|
9
|
-
"https://accounts.dsers.com",
|
|
10
|
-
"https://app.dsers.com",
|
|
11
|
-
"https://www.dsers.com",
|
|
12
|
-
];
|
|
8
|
+
const COOKIE_CHECK_URLS = ["https://accounts.dsers.com", "https://app.dsers.com", "https://www.dsers.com"];
|
|
13
9
|
const POLL_MS = 2_000;
|
|
14
10
|
const PORT_TIMEOUT_MS = 30_000;
|
|
15
11
|
const TARGET_RETRY_MS = 500;
|
|
@@ -29,7 +25,9 @@ class CDPClient {
|
|
|
29
25
|
sock.on("close", () => this.shutdown());
|
|
30
26
|
sock.on("error", () => this.shutdown());
|
|
31
27
|
}
|
|
32
|
-
get closed() {
|
|
28
|
+
get closed() {
|
|
29
|
+
return this._closed;
|
|
30
|
+
}
|
|
33
31
|
send(method, params) {
|
|
34
32
|
if (this._closed)
|
|
35
33
|
return Promise.reject(new Error("CDP closed"));
|
|
@@ -50,11 +48,15 @@ class CDPClient {
|
|
|
50
48
|
try {
|
|
51
49
|
this.sock.write(f);
|
|
52
50
|
}
|
|
53
|
-
catch {
|
|
51
|
+
catch {
|
|
52
|
+
/* ignore */
|
|
53
|
+
}
|
|
54
54
|
try {
|
|
55
55
|
this.sock.end();
|
|
56
56
|
}
|
|
57
|
-
catch {
|
|
57
|
+
catch {
|
|
58
|
+
/* ignore */
|
|
59
|
+
}
|
|
58
60
|
this._closed = true;
|
|
59
61
|
}
|
|
60
62
|
/* ---- framing ---- */
|
|
@@ -82,7 +84,9 @@ class CDPClient {
|
|
|
82
84
|
}
|
|
83
85
|
onData(chunk) {
|
|
84
86
|
this.buf = Buffer.concat([this.buf, chunk]);
|
|
85
|
-
while (this.parseFrame()) {
|
|
87
|
+
while (this.parseFrame()) {
|
|
88
|
+
/* keep consuming */
|
|
89
|
+
}
|
|
86
90
|
}
|
|
87
91
|
parseFrame() {
|
|
88
92
|
if (this.buf.length < 2)
|
|
@@ -117,7 +121,9 @@ class CDPClient {
|
|
|
117
121
|
}
|
|
118
122
|
}
|
|
119
123
|
}
|
|
120
|
-
catch {
|
|
124
|
+
catch {
|
|
125
|
+
/* malformed */
|
|
126
|
+
}
|
|
121
127
|
}
|
|
122
128
|
else if (opcode === 0x08) {
|
|
123
129
|
this.shutdown();
|
|
@@ -138,16 +144,23 @@ function httpGetJson(port, path) {
|
|
|
138
144
|
return new Promise((resolve, reject) => {
|
|
139
145
|
const r = httpReq({ hostname: "127.0.0.1", port, path, method: "GET" }, (res) => {
|
|
140
146
|
let d = "";
|
|
141
|
-
res.on("data", (c) => {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
147
|
+
res.on("data", (c) => {
|
|
148
|
+
d += c;
|
|
149
|
+
});
|
|
150
|
+
res.on("end", () => {
|
|
151
|
+
try {
|
|
152
|
+
resolve(JSON.parse(d));
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
reject(new Error("bad json"));
|
|
156
|
+
}
|
|
157
|
+
});
|
|
148
158
|
});
|
|
149
159
|
r.on("error", reject);
|
|
150
|
-
r.setTimeout(5000, () => {
|
|
160
|
+
r.setTimeout(5000, () => {
|
|
161
|
+
r.destroy();
|
|
162
|
+
reject(new Error("http timeout"));
|
|
163
|
+
});
|
|
151
164
|
r.end();
|
|
152
165
|
});
|
|
153
166
|
}
|
|
@@ -156,13 +169,18 @@ function wsConnect(url) {
|
|
|
156
169
|
const u = new URL(url.replace(/^ws:/, "http:"));
|
|
157
170
|
const key = randomBytes(16).toString("base64");
|
|
158
171
|
const r = httpReq({
|
|
159
|
-
hostname: u.hostname,
|
|
160
|
-
|
|
172
|
+
hostname: u.hostname,
|
|
173
|
+
port: u.port,
|
|
174
|
+
path: u.pathname + u.search,
|
|
175
|
+
method: "GET",
|
|
161
176
|
headers: { Upgrade: "websocket", Connection: "Upgrade", "Sec-WebSocket-Key": key, "Sec-WebSocket-Version": "13" },
|
|
162
177
|
});
|
|
163
178
|
r.on("upgrade", (_res, socket) => resolve(new CDPClient(socket)));
|
|
164
179
|
r.on("error", reject);
|
|
165
|
-
r.setTimeout(5000, () => {
|
|
180
|
+
r.setTimeout(5000, () => {
|
|
181
|
+
r.destroy();
|
|
182
|
+
reject(new Error("ws timeout"));
|
|
183
|
+
});
|
|
166
184
|
r.end();
|
|
167
185
|
});
|
|
168
186
|
}
|
|
@@ -170,8 +188,13 @@ function waitForPort(proc) {
|
|
|
170
188
|
return new Promise((resolve, reject) => {
|
|
171
189
|
const timer = setTimeout(() => reject(new Error("Browser did not expose debug port within 30 s")), PORT_TIMEOUT_MS);
|
|
172
190
|
let settled = false;
|
|
173
|
-
const fail = (msg) => {
|
|
174
|
-
|
|
191
|
+
const fail = (msg) => {
|
|
192
|
+
if (settled)
|
|
193
|
+
return;
|
|
194
|
+
settled = true;
|
|
195
|
+
clearTimeout(timer);
|
|
196
|
+
reject(new Error(msg));
|
|
197
|
+
};
|
|
175
198
|
let acc = "";
|
|
176
199
|
proc.stderr?.on("data", (c) => {
|
|
177
200
|
acc += c.toString();
|
|
@@ -194,7 +217,9 @@ async function findPageWs(port) {
|
|
|
194
217
|
if (pg?.webSocketDebuggerUrl)
|
|
195
218
|
return pg.webSocketDebuggerUrl;
|
|
196
219
|
}
|
|
197
|
-
catch {
|
|
220
|
+
catch {
|
|
221
|
+
/* retry */
|
|
222
|
+
}
|
|
198
223
|
await new Promise((r) => setTimeout(r, TARGET_RETRY_MS));
|
|
199
224
|
}
|
|
200
225
|
throw new Error("No page target found in browser");
|
|
@@ -212,7 +237,9 @@ async function extractSession(cdp) {
|
|
|
212
237
|
return { session_id: sid.value, state: st?.value ?? "" };
|
|
213
238
|
}
|
|
214
239
|
}
|
|
215
|
-
catch {
|
|
240
|
+
catch {
|
|
241
|
+
/* domain not enabled or page navigating */
|
|
242
|
+
}
|
|
216
243
|
// 2) JS document.cookie (non-HttpOnly)
|
|
217
244
|
try {
|
|
218
245
|
const { result } = await cdp.send("Runtime.evaluate", { expression: "document.cookie" });
|
|
@@ -223,7 +250,9 @@ async function extractSession(cdp) {
|
|
|
223
250
|
return { session_id: sm[1].trim(), state: tm?.[1]?.trim() ?? "" };
|
|
224
251
|
}
|
|
225
252
|
}
|
|
226
|
-
catch {
|
|
253
|
+
catch {
|
|
254
|
+
/* context destroyed during navigation */
|
|
255
|
+
}
|
|
227
256
|
// 3) localStorage / sessionStorage
|
|
228
257
|
try {
|
|
229
258
|
const expr = 'JSON.stringify({s:localStorage.getItem("session_id")||localStorage.getItem("sessionId")||sessionStorage.getItem("session_id")||"",t:localStorage.getItem("state")||sessionStorage.getItem("state")||""})';
|
|
@@ -232,7 +261,9 @@ async function extractSession(cdp) {
|
|
|
232
261
|
if (d.s)
|
|
233
262
|
return { session_id: d.s, state: d.t ?? "" };
|
|
234
263
|
}
|
|
235
|
-
catch {
|
|
264
|
+
catch {
|
|
265
|
+
/* not available */
|
|
266
|
+
}
|
|
236
267
|
return null;
|
|
237
268
|
}
|
|
238
269
|
/* ------------------------------------------------------------------ */
|
|
@@ -250,19 +281,30 @@ export async function loginViaCDP(browserPath, log = () => { }) {
|
|
|
250
281
|
try {
|
|
251
282
|
cdp?.close();
|
|
252
283
|
}
|
|
253
|
-
catch {
|
|
284
|
+
catch {
|
|
285
|
+
/* ignore */
|
|
286
|
+
}
|
|
254
287
|
if (proc && !proc.killed) {
|
|
255
288
|
try {
|
|
256
289
|
proc.kill();
|
|
257
290
|
}
|
|
258
|
-
catch {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
rmSync(tempDir, { recursive: true, force: true });
|
|
291
|
+
catch {
|
|
292
|
+
/* ignore */
|
|
293
|
+
}
|
|
262
294
|
}
|
|
263
|
-
|
|
295
|
+
setTimeout(() => {
|
|
296
|
+
try {
|
|
297
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
298
|
+
}
|
|
299
|
+
catch {
|
|
300
|
+
/* ignore */
|
|
301
|
+
}
|
|
302
|
+
}, 500);
|
|
303
|
+
};
|
|
304
|
+
const onSig = () => {
|
|
305
|
+
cleanup();
|
|
306
|
+
process.exit(130);
|
|
264
307
|
};
|
|
265
|
-
const onSig = () => { cleanup(); process.exit(130); };
|
|
266
308
|
process.on("SIGINT", onSig);
|
|
267
309
|
process.on("SIGTERM", onSig);
|
|
268
310
|
try {
|
|
@@ -278,18 +320,25 @@ export async function loginViaCDP(browserPath, log = () => { }) {
|
|
|
278
320
|
"--mute-audio",
|
|
279
321
|
DSERS_LOGIN_URL,
|
|
280
322
|
], { stdio: ["ignore", "pipe", "pipe"], detached: false });
|
|
281
|
-
proc.on("error", () => {
|
|
323
|
+
proc.on("error", () => {
|
|
324
|
+
/* handled by waitForPort exit handler */
|
|
325
|
+
});
|
|
282
326
|
const port = await waitForPort(proc);
|
|
283
327
|
const wsUrl = await findPageWs(port);
|
|
284
328
|
cdp = await wsConnect(wsUrl);
|
|
285
329
|
try {
|
|
286
330
|
await cdp.send("Network.enable");
|
|
287
331
|
}
|
|
288
|
-
catch {
|
|
332
|
+
catch {
|
|
333
|
+
/* optional */
|
|
334
|
+
}
|
|
289
335
|
log("Waiting for DSers login... (close browser or Ctrl+C to cancel)");
|
|
290
336
|
const result = await new Promise((resolve) => {
|
|
291
337
|
let exited = false;
|
|
292
|
-
proc.on("exit", () => {
|
|
338
|
+
proc.on("exit", () => {
|
|
339
|
+
exited = true;
|
|
340
|
+
resolve(null);
|
|
341
|
+
});
|
|
293
342
|
const tick = async () => {
|
|
294
343
|
if (exited || cdp.closed)
|
|
295
344
|
return resolve(null);
|
|
@@ -298,7 +347,9 @@ export async function loginViaCDP(browserPath, log = () => { }) {
|
|
|
298
347
|
if (session)
|
|
299
348
|
return resolve(session);
|
|
300
349
|
}
|
|
301
|
-
catch {
|
|
350
|
+
catch {
|
|
351
|
+
/* polling error, retry */
|
|
352
|
+
}
|
|
302
353
|
setTimeout(tick, POLL_MS);
|
|
303
354
|
};
|
|
304
355
|
tick();
|