dingding-local-api-core 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 +17 -0
- package/package.json +18 -0
- package/src/index.cjs +173 -0
package/README.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# dingding-local-api-core
|
|
2
|
+
|
|
3
|
+
Shared Node.js client for DingDing Browser Local API.
|
|
4
|
+
|
|
5
|
+
```js
|
|
6
|
+
const { createClient } = require('dingding-local-api-core')
|
|
7
|
+
|
|
8
|
+
const client = createClient({ port: 19876, apiKey: process.env.DDB_API_KEY })
|
|
9
|
+
const profiles = await client.listProfiles()
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Environment variables:
|
|
13
|
+
|
|
14
|
+
- `DDB_BASE_URL` / `DINGDING_BASE_URL`
|
|
15
|
+
- `DDB_PORT` / `DINGDING_PORT`
|
|
16
|
+
- `DDB_API_KEY` / `DINGDING_API_KEY`
|
|
17
|
+
- `DDB_AUTH_HEADER` / `DINGDING_AUTH_HEADER`
|
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dingding-local-api-core",
|
|
3
|
+
"version": "1.3.8",
|
|
4
|
+
"description": "DingDing Browser Local API client for CLI and MCP integrations.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"main": "src/index.cjs",
|
|
7
|
+
"files": [
|
|
8
|
+
"src",
|
|
9
|
+
"README.md"
|
|
10
|
+
],
|
|
11
|
+
"engines": {
|
|
12
|
+
"node": ">=18"
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"test": "node --test test/*.test.cjs",
|
|
16
|
+
"check": "node --check src/index.cjs"
|
|
17
|
+
}
|
|
18
|
+
}
|
package/src/index.cjs
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const DEFAULT_PORT = 19876
|
|
4
|
+
const DEFAULT_AUTH_HEADER = 'X-Ant-Api-Key'
|
|
5
|
+
|
|
6
|
+
function normalizeBaseUrl(options = {}, env = process.env) {
|
|
7
|
+
const explicit = trim(options.baseUrl || env.DDB_BASE_URL || env.DINGDING_BASE_URL)
|
|
8
|
+
if (explicit) return explicit.replace(/\/+$/, '')
|
|
9
|
+
|
|
10
|
+
const rawPort = trim(options.port || env.DDB_PORT || env.DINGDING_PORT || env.PORT)
|
|
11
|
+
const port = rawPort || String(DEFAULT_PORT)
|
|
12
|
+
return `http://127.0.0.1:${port}`
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function createClient(options = {}) {
|
|
16
|
+
const env = options.env || process.env
|
|
17
|
+
const baseUrl = normalizeBaseUrl(options, env)
|
|
18
|
+
const apiKey = trim(options.apiKey || env.DDB_API_KEY || env.DINGDING_API_KEY || env.API_KEY)
|
|
19
|
+
const authHeader = trim(options.authHeader || env.DDB_AUTH_HEADER || env.DINGDING_AUTH_HEADER || env.AUTH_HEADER) || DEFAULT_AUTH_HEADER
|
|
20
|
+
const fetchImpl = options.fetch || globalThis.fetch
|
|
21
|
+
if (typeof fetchImpl !== 'function') {
|
|
22
|
+
throw new Error('global fetch is unavailable; DingDing CLI requires Node.js 18 or newer')
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function request(method, path, body, query) {
|
|
26
|
+
const url = new URL(path, baseUrl)
|
|
27
|
+
for (const [key, value] of Object.entries(query || {})) {
|
|
28
|
+
if (value === undefined || value === null || value === '') continue
|
|
29
|
+
url.searchParams.set(key, String(value))
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const headers = { Accept: 'application/json' }
|
|
33
|
+
const init = { method, headers }
|
|
34
|
+
if (apiKey) headers[authHeader] = apiKey
|
|
35
|
+
if (body !== undefined) {
|
|
36
|
+
headers['Content-Type'] = 'application/json'
|
|
37
|
+
init.body = JSON.stringify(body)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const response = await fetchImpl(url, init)
|
|
41
|
+
const text = await response.text()
|
|
42
|
+
const payload = parseJSONText(text)
|
|
43
|
+
if (!response.ok) {
|
|
44
|
+
const message = payload && typeof payload === 'object' && payload.error ? payload.error : `HTTP ${response.status}`
|
|
45
|
+
const err = new Error(message)
|
|
46
|
+
err.status = response.status
|
|
47
|
+
err.payload = payload || text
|
|
48
|
+
throw err
|
|
49
|
+
}
|
|
50
|
+
return payload === undefined ? text : payload
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
baseUrl,
|
|
55
|
+
authHeader,
|
|
56
|
+
request,
|
|
57
|
+
health: () => request('GET', '/api/health'),
|
|
58
|
+
listProfiles: () => request('GET', '/api/profiles'),
|
|
59
|
+
getProfile: (profileId) => request('GET', `/api/profiles/${encodeURIComponent(required(profileId, 'profileId'))}`),
|
|
60
|
+
createProfile: (input) => request('POST', '/api/profiles', input),
|
|
61
|
+
updateProfile: (profileId, input) => request('PUT', `/api/profiles/${encodeURIComponent(required(profileId, 'profileId'))}`, input),
|
|
62
|
+
deleteProfile: (profileId) => request('DELETE', `/api/profiles/${encodeURIComponent(required(profileId, 'profileId'))}`),
|
|
63
|
+
launch: (input) => request('POST', '/api/launch', input),
|
|
64
|
+
launchByCode: (code) => request('GET', `/api/launch/${encodeURIComponent(required(code, 'code'))}`),
|
|
65
|
+
launchLogs: (limit) => request('GET', '/api/launch/logs', undefined, { limit }),
|
|
66
|
+
runtimeActive: () => request('GET', '/api/runtime/active'),
|
|
67
|
+
runtimeSession: (input) => request('POST', '/api/runtime/session', input),
|
|
68
|
+
runtimeStatus: (input) => request('POST', '/api/runtime/status', input),
|
|
69
|
+
runtimeStop: (input) => request('POST', '/api/runtime/stop', input),
|
|
70
|
+
listAutomationScripts: () => request('GET', '/api/automation/scripts'),
|
|
71
|
+
getAutomationScript: (scriptId) => request('GET', `/api/automation/scripts/${encodeURIComponent(required(scriptId, 'scriptId'))}`),
|
|
72
|
+
runAutomationScript: (input) => request('POST', '/api/automation/scripts/run', input),
|
|
73
|
+
listAutomationRuns: (limit) => request('GET', '/api/automation/scripts/runs', undefined, { limit }),
|
|
74
|
+
listGroups: () => request('GET', '/api/groups'),
|
|
75
|
+
createGroup: (input) => request('POST', '/api/groups', input),
|
|
76
|
+
updateGroup: (groupId, input) => request('PUT', `/api/groups/${encodeURIComponent(required(groupId, 'groupId'))}`, input),
|
|
77
|
+
deleteGroup: (groupId) => request('DELETE', `/api/groups/${encodeURIComponent(required(groupId, 'groupId'))}`),
|
|
78
|
+
moveProfilesToGroup: (groupId, profileIds) => request('POST', `/api/groups/${encodeURIComponent(required(groupId, 'groupId'))}/profiles`, { profileIds }),
|
|
79
|
+
listProxies: (groupName) => request('GET', '/api/proxies', undefined, { groupName }),
|
|
80
|
+
listProxyGroups: () => request('GET', '/api/proxies/groups'),
|
|
81
|
+
getProxy: (proxyId) => request('GET', `/api/proxies/${encodeURIComponent(required(proxyId, 'proxyId'))}`),
|
|
82
|
+
createProxy: (input) => request('POST', '/api/proxies', input),
|
|
83
|
+
saveProxies: (items) => request('PUT', '/api/proxies', items),
|
|
84
|
+
updateProxy: (proxyId, input) => request('PUT', `/api/proxies/${encodeURIComponent(required(proxyId, 'proxyId'))}`, input),
|
|
85
|
+
deleteProxy: (proxyId) => request('DELETE', `/api/proxies/${encodeURIComponent(required(proxyId, 'proxyId'))}`),
|
|
86
|
+
listTags: () => request('GET', '/api/tags'),
|
|
87
|
+
renameTag: (oldName, newName) => request('POST', '/api/tags/rename', { oldName, newName }),
|
|
88
|
+
setTags: (profileIds, tags, replace = false) => request('POST', '/api/tags/set', { profileIds, tags, replace }),
|
|
89
|
+
removeTags: (profileIds, tags) => request('POST', '/api/tags/remove', { profileIds, tags }),
|
|
90
|
+
listCores: () => request('GET', '/api/cores'),
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function selectorBodyFromValue(value, options = {}) {
|
|
95
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) return value
|
|
96
|
+
const field = selectorFieldName(options.by || options.selector || 'code')
|
|
97
|
+
return { [field]: required(value, field) }
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function selectorFieldName(input) {
|
|
101
|
+
const normalized = trim(input).toLowerCase().replace(/[_\s]+/g, '-')
|
|
102
|
+
switch (normalized) {
|
|
103
|
+
case 'profile-id':
|
|
104
|
+
case 'profileid':
|
|
105
|
+
case 'id':
|
|
106
|
+
return 'profileId'
|
|
107
|
+
case 'profile-name':
|
|
108
|
+
case 'profilename':
|
|
109
|
+
case 'name':
|
|
110
|
+
return 'profileName'
|
|
111
|
+
case 'group-id':
|
|
112
|
+
case 'groupid':
|
|
113
|
+
return 'groupId'
|
|
114
|
+
case 'keyword':
|
|
115
|
+
case 'tag':
|
|
116
|
+
case 'key':
|
|
117
|
+
case 'code':
|
|
118
|
+
return normalized
|
|
119
|
+
default:
|
|
120
|
+
throw new Error(`unsupported selector field: ${input}`)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function parseJSONArg(raw, label = 'argument') {
|
|
125
|
+
const text = trim(raw)
|
|
126
|
+
if (!text) return {}
|
|
127
|
+
try {
|
|
128
|
+
return JSON.parse(text)
|
|
129
|
+
} catch (err) {
|
|
130
|
+
throw new Error(`${label} must be valid JSON: ${err.message}`)
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function parseValueOrJSON(raw) {
|
|
135
|
+
const text = trim(raw)
|
|
136
|
+
if (!text) return {}
|
|
137
|
+
if (text.startsWith('{') || text.startsWith('[')) return parseJSONArg(text)
|
|
138
|
+
return text
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function parseJSONText(text) {
|
|
142
|
+
const body = trim(text)
|
|
143
|
+
if (!body) return undefined
|
|
144
|
+
try {
|
|
145
|
+
return JSON.parse(body)
|
|
146
|
+
} catch {
|
|
147
|
+
return undefined
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function required(value, name) {
|
|
152
|
+
const out = trim(value)
|
|
153
|
+
if (!out) throw new Error(`${name} is required`)
|
|
154
|
+
return out
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function trim(value) {
|
|
158
|
+
if (value === undefined || value === null) return ''
|
|
159
|
+
return String(value).trim()
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
module.exports = {
|
|
163
|
+
DEFAULT_AUTH_HEADER,
|
|
164
|
+
DEFAULT_PORT,
|
|
165
|
+
createClient,
|
|
166
|
+
normalizeBaseUrl,
|
|
167
|
+
parseJSONArg,
|
|
168
|
+
parseValueOrJSON,
|
|
169
|
+
required,
|
|
170
|
+
selectorBodyFromValue,
|
|
171
|
+
selectorFieldName,
|
|
172
|
+
trim,
|
|
173
|
+
}
|