node-nim 10.9.70 → 10.9.71-beta.87

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-nim",
3
- "version": "10.9.70",
3
+ "version": "10.9.71-beta.87",
4
4
  "description": "NetEase IM nodejs wrapper based on NetEase IM C++ SDK",
5
5
  "main": "dist/node-nim.js",
6
6
  "bin": {
@@ -20,8 +20,8 @@
20
20
  "build_ts": "rimraf ./dist && rimraf ./types && tsc",
21
21
  "build_html_doc": "typedoc --plugin typedoc-github-theme",
22
22
  "prepublishOnly": "npm run build_ts",
23
- "install": "npm run download_sdk",
24
- "download_sdk": "node -e \"require('./script/download-sdk.js').downloadSDK()\"",
23
+ "postinstall": "node script/download-sdk.js",
24
+ "download_sdk": "node script/download-sdk.js",
25
25
  "publish_to_netease_npm": "node script/publish-to-netease-npm.js http://npm.netease.im/",
26
26
  "publish_to_npmjs": "node script/publish-to-netease-npm.js https://registry.npmjs.org",
27
27
  "test": "npx cross-env BABEL_ENV=test mocha"
@@ -33,10 +33,10 @@
33
33
  ]
34
34
  },
35
35
  "dependencies": {
36
+ "axios": "^1.6.2",
36
37
  "compare-versions": "^4.1.4",
37
- "download": "^8.0.0",
38
- "eventemitter3": "^4.0.7",
39
- "node-fetch": "^2.6.9"
38
+ "decompress": "^4.2.1",
39
+ "eventemitter3": "^4.0.7"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@babel/preset-env": "^7.24.0",
@@ -1,8 +1,8 @@
1
- const fetch = require('node-fetch')
1
+ const axios = require('axios')
2
2
  const fs = require('fs')
3
3
  const path = require('path')
4
4
  const compareVersions = require('compare-versions')
5
- const download = require('download')
5
+ const decompress = require('decompress')
6
6
 
7
7
  // Global variables
8
8
  const default_arch = 'universal'
@@ -14,56 +14,136 @@ const product = 'nim'
14
14
  const savePath = path.join(__dirname, '..', 'temporary')
15
15
 
16
16
  if (process.env.npm_config_ignoredownloadsdk) {
17
- console.log('ignore download product')
17
+ console.log('[node-nim] Ignore download product')
18
18
  process.exit(0)
19
19
  }
20
20
  let version
21
- let downloadUrl = process.env.npm_config_nimsdkurl
22
21
  if (process.env.npm_package_version) {
23
22
  version = process.env.npm_package_version.split('-')[0]
24
23
  }
25
24
  if (process.env.npm_config_nimsdkversion) {
26
25
  version = process.env.npm_config_nimsdkversion
27
26
  }
28
- async function downloadSDK(custom_sdk_url) {
29
- if (custom_sdk_url) {
30
- downloadUrl = custom_sdk_url
27
+
28
+ // Simple logger that works with npm postinstall
29
+ // Use console.error to ensure output is visible when installed as dependency
30
+ // npm captures stdout but is less aggressive with stderr
31
+ function log(message) {
32
+ console.error(message)
33
+ }
34
+
35
+ // Progress bar utility - real-time progress bar
36
+ function createProgressBar(total) {
37
+ let lastUpdate = 0
38
+ const updateInterval = 100 // Update every 100ms
39
+ return (loaded) => {
40
+ const now = Date.now()
41
+ const percent = Math.floor((loaded * 100) / total)
42
+ // Update based on time interval or when complete
43
+ if (now - lastUpdate < updateInterval && loaded < total) {
44
+ return
45
+ }
46
+ lastUpdate = now
47
+ const size = formatBytes(loaded)
48
+ const totalSize = formatBytes(total)
49
+ // Create progress bar
50
+ const barLength = 30
51
+ const filledLength = Math.floor((barLength * loaded) / total)
52
+ const bar = '█'.repeat(filledLength) + '░'.repeat(barLength - filledLength)
53
+ // Use \r to overwrite the same line
54
+ process.stderr.write(`\r[node-nim] ⬇ ${bar} ${percent}% (${size}/${totalSize})`)
55
+ // Print newline when complete
56
+ if (loaded >= total) {
57
+ process.stderr.write('\n')
58
+ log(` ✅ Download complete`)
59
+ }
31
60
  }
32
- // fetch publish list
33
- const res = await fetch('https://admin.netease.im/public-service/free/publish/list')
34
- const publish_json = await res.json()
35
- // get sdk list
36
- if (!downloadUrl) {
37
- let latestVersion = '0.0.0'
38
- let latestDownloadUrl = ''
39
- Object.keys(publish_json.data[channel]).forEach((temp) => {
40
- if (compareVersions.compare(latestVersion, temp, '<')) {
41
- publish_json.data[channel][temp].forEach((member) => {
42
- if (member.filename.includes(product) && member.filename.includes(platform) && member.filename.includes(arch)) {
43
- latestVersion = temp
44
- latestDownloadUrl = member.cdnlink
45
- }
46
- })
61
+ }
62
+
63
+ // Format bytes to human readable format
64
+ function formatBytes(bytes) {
65
+ if (bytes === 0) return '0 B'
66
+ const k = 1024
67
+ const sizes = ['B', 'KB', 'MB', 'GB']
68
+ const i = Math.floor(Math.log(bytes) / Math.log(k))
69
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]
70
+ }
71
+
72
+ // Download and extract function using axios + decompress
73
+ async function downloadAndExtract(url, destination) {
74
+ if (!fs.existsSync(destination)) {
75
+ fs.mkdirSync(destination, { recursive: true })
76
+ }
77
+ // Determine archive file name from URL
78
+ const urlPath = new URL(url).pathname
79
+ const fileName = path.basename(urlPath) || 'temp-archive'
80
+ const archivePath = path.join(destination, fileName)
81
+ log(` 📥 Starting download...`)
82
+ try {
83
+ // Download with progress tracking
84
+ const response = await axios({
85
+ method: 'GET',
86
+ url: url,
87
+ responseType: 'stream',
88
+ timeout: 300000, // 5 minutes timeout
89
+ onDownloadProgress: (progressEvent) => {
90
+ if (progressEvent.total) {
91
+ const progressBar = createProgressBar(progressEvent.total)
92
+ progressBar(progressEvent.loaded)
93
+ }
47
94
  }
48
- if (version === temp) {
49
- publish_json.data[channel][temp].forEach((member) => {
50
- if (member.filename.includes(product) && member.filename.includes(platform) && member.filename.includes(arch)) {
51
- downloadUrl = member.cdnlink
52
- }
53
- })
95
+ })
96
+ // Save the downloaded file
97
+ const writeStream = fs.createWriteStream(archivePath)
98
+ response.data.pipe(writeStream)
99
+ // Wait for download to complete
100
+ await new Promise((resolve, reject) => {
101
+ writeStream.on('finish', resolve)
102
+ writeStream.on('error', reject)
103
+ response.data.on('error', reject)
104
+ })
105
+ log(` 📦 Extracting archive...`)
106
+ // Extract using decompress (auto-detects format)
107
+ await decompress(archivePath, destination, {
108
+ filter: (file) => {
109
+ // Filter out macOS hidden files (._files) and __MACOSX folders
110
+ const filePath = file.path
111
+ return !filePath.includes('._') &&
112
+ !filePath.includes('__MACOSX') &&
113
+ !filePath.startsWith('._')
114
+ },
115
+ map: (file) => {
116
+ // Remove any leading directory if needed
117
+ file.path = file.path.replace(/^[^\/]+\//, '')
118
+ return file
54
119
  }
55
120
  })
56
- if (!downloadUrl || downloadUrl.length === 0) {
57
- console.log(`[node-nim] Product [${product}] version ${version} not found, use latest version ${latestVersion}`)
58
- downloadUrl = latestDownloadUrl
121
+ log(` Extraction complete`)
122
+ // Clean up the temporary archive file
123
+ fs.unlinkSync(archivePath)
124
+ } catch (error) {
125
+ // Clean up on error
126
+ if (fs.existsSync(archivePath)) {
127
+ fs.unlinkSync(archivePath)
59
128
  }
60
- console.log(`[node-nim] Downloading product: ${product}, platform: ${platform}, arch: ${arch}`)
129
+ throw error
61
130
  }
131
+ }
132
+ async function downloadSDK(customPackageUrl) {
133
+ // Use custom URL if provided, otherwise fetch from official server
134
+ let downloadUrl = customPackageUrl
62
135
  if (!downloadUrl) {
63
- console.error(`[node-nim] Downloading product: ${product}, platform: ${platform}, arch: ${arch} not found`)
64
- return
136
+ // Fetch package list from official server
137
+ const res = await axios.get('https://admin.netease.im/public-service/free/publish/list')
138
+ const publishData = res.data.data[channel]
139
+ // Find package URL for specified version or latest
140
+ downloadUrl = findPackageUrl(publishData, version, platform, arch, product)
141
+ if (!downloadUrl) {
142
+ log(` ❌ ERROR: Package not found for ${platform} (${arch})`)
143
+ return
144
+ }
145
+ log(` 🚀 Preparing to download package for ${platform} (${arch})`)
65
146
  }
66
- console.info(`[node-nim] Downloading prebuilt SDK from ${downloadUrl} to ${savePath}`)
67
147
  // remove temporary download folder and target folder
68
148
  const target = path.join(__dirname, '..', 'build', 'Release')
69
149
  if (fs.existsSync(savePath)) {
@@ -74,12 +154,7 @@ async function downloadSDK(custom_sdk_url) {
74
154
  }
75
155
  // download sdk
76
156
  try {
77
- await download(downloadUrl, savePath, {
78
- extract: true,
79
- filter: (file) => {
80
- return !file.path.includes('._')
81
- }
82
- })
157
+ await downloadAndExtract(downloadUrl, savePath)
83
158
  // create build/Release folder
84
159
  if (!fs.existsSync(target)) {
85
160
  fs.mkdirSync(target, { recursive: true })
@@ -88,20 +163,217 @@ async function downloadSDK(custom_sdk_url) {
88
163
  const from = path.join(savePath, platform === 'win32' ? 'bin' : 'lib')
89
164
  const files = fs.readdirSync(from)
90
165
  files.forEach((file) => {
91
- console.info(`[node-nim] move ${file} to ${target}`)
166
+ log(` 📁 Installing ${file}`)
92
167
  fs.renameSync(path.join(from, file), path.join(target, file))
93
168
  })
94
169
  // remove temporary download folder
95
170
  fs.rmSync(savePath, { recursive: true })
96
- console.info(`[node-nim] Downloading prebuilt SDK complete`)
171
+ log(` Package installation complete!`)
97
172
  } catch (err) {
98
- console.error(`[node-nim] Failed to download, error: ${err}`)
173
+ log(` ERROR: ${err.message}`)
174
+ throw err
175
+ }
176
+ }
177
+ // Helper function to find package download URL from publish data
178
+ function findPackageUrl(publishData, targetVersion, platform, arch, product) {
179
+ let latestVersion = '0.0.0'
180
+ let latestDownloadUrl = ''
181
+ let targetDownloadUrl = ''
182
+ // Check if package matches current platform/arch
183
+ const isMatchingPackage = (member) => {
184
+ return member.filename.includes(product) &&
185
+ member.filename.includes(platform) &&
186
+ member.filename.includes(arch)
187
+ }
188
+ // Iterate through all versions
189
+ Object.keys(publishData).forEach((versionKey) => {
190
+ const versionSDKs = publishData[versionKey]
191
+ // Track latest version
192
+ if (compareVersions.compare(latestVersion, versionKey, '<')) {
193
+ const latestPackage = versionSDKs.find(isMatchingPackage)
194
+ if (latestPackage) {
195
+ latestVersion = versionKey
196
+ latestDownloadUrl = latestPackage.cdnlink
197
+ }
198
+ }
199
+ // Find target version
200
+ if (targetVersion === versionKey) {
201
+ const targetPackage = versionSDKs.find(isMatchingPackage)
202
+ if (targetPackage) {
203
+ targetDownloadUrl = targetPackage.cdnlink
204
+ }
205
+ }
206
+ })
207
+ // Use target version if found, otherwise fallback to latest
208
+ if (targetDownloadUrl) {
209
+ return targetDownloadUrl
210
+ }
211
+ if (latestDownloadUrl) {
212
+ log(` ⚠️ Version ${targetVersion} not found, using latest version ${latestVersion}`)
213
+ return latestDownloadUrl
99
214
  }
215
+ return null
100
216
  }
217
+
218
+ // Parse directory listing from HTTP server
219
+ async function parseDirectoryListing(url) {
220
+ try {
221
+ const response = await axios.get(url)
222
+ const html = response.data
223
+ // Extract directory/file names from href attributes
224
+ // Matches patterns like: href="dirname/" or href="filename.tar.gz"
225
+ const hrefRegex = /href="([^"]+)"/g
226
+ const items = []
227
+ let match
228
+ while ((match = hrefRegex.exec(html)) !== null) {
229
+ const item = match[1]
230
+ // Skip parent directory and absolute URLs
231
+ if (item !== '../' && !item.startsWith('http') && !item.startsWith('/')) {
232
+ items.push(item)
233
+ }
234
+ }
235
+ return items
236
+ } catch (error) {
237
+ throw new Error(`Failed to parse directory listing from ${url}: ${error.message}`)
238
+ }
239
+ }
240
+
241
+ // Find latest build number from directory listing
242
+ async function findLatestBuild(baseUrl, branch) {
243
+ const branchUrl = `${baseUrl}/${branch}/`
244
+ log(` 🔍 Searching for latest build in ${branchUrl}`)
245
+ const items = await parseDirectoryListing(branchUrl)
246
+ // Filter directories (end with /) and extract build numbers
247
+ const buildNumbers = items
248
+ .filter(item => item.endsWith('/'))
249
+ .map(item => parseInt(item.replace('/', '')))
250
+ .filter(num => !isNaN(num))
251
+ .sort((a, b) => b - a) // Sort in descending order
252
+
253
+ if (buildNumbers.length === 0) {
254
+ throw new Error(`No build directories found in ${branchUrl}`)
255
+ }
256
+ // Return all build numbers sorted by latest first
257
+ return buildNumbers
258
+ }
259
+
260
+ // Find latest build that contains the requested platform package
261
+ async function findLatestBuildWithPackage(baseUrl, branch, nodePlatform, nodeArch) {
262
+ const buildNumbers = await findLatestBuild(baseUrl, branch)
263
+
264
+ // Try builds from latest to oldest
265
+ for (const buildNumber of buildNumbers) {
266
+ try {
267
+ const buildUrl = `${baseUrl}/${branch}/${buildNumber}/`
268
+ log(` 🔍 Checking build ${buildNumber} for ${nodePlatform}-${nodeArch} package...`)
269
+
270
+ // Try to find package in this build
271
+ const packageUrl = await findPackage(buildUrl, nodePlatform, nodeArch)
272
+ log(` ✅ Found package in build ${buildNumber}`)
273
+ return { buildNumber, packageUrl }
274
+ } catch (error) {
275
+ log(` ⚠️ Build ${buildNumber} does not contain ${nodePlatform}-${nodeArch} package, trying previous build...`)
276
+ // Continue to next build
277
+ }
278
+ }
279
+
280
+ throw new Error(`No build found with ${nodePlatform}-${nodeArch} package for branch ${branch}`)
281
+ }
282
+
283
+ // Map Node.js platform/arch to SDK directory format
284
+ function getPlatformArchDir(nodePlatform, nodeArch) {
285
+ // darwin-arm64, darwin-x64, win32-ia32, linux-arm64, linux-x64
286
+ let platform = nodePlatform
287
+ let arch = nodeArch
288
+ // Map Node.js arch to package arch format
289
+ if (nodePlatform === 'darwin') {
290
+ arch = nodeArch === 'arm64' ? 'arm64' : 'x64'
291
+ } else if (nodePlatform === 'win32') {
292
+ platform = 'win32'
293
+ arch = nodeArch === 'ia32' ? 'ia32' : nodeArch === 'x64' ? 'x64' : nodeArch
294
+ } else if (nodePlatform === 'linux') {
295
+ arch = nodeArch === 'arm64' ? 'arm64' : 'x64'
296
+ }
297
+ return `${platform}-${arch}/`
298
+ }
299
+
300
+ // Find package from directory listing
301
+ async function findPackage(buildUrl, nodePlatform, nodeArch) {
302
+ const platformArchDir = getPlatformArchDir(nodePlatform, nodeArch)
303
+ const fullUrl = `${buildUrl}${platformArchDir}`
304
+ log(` 🔍 Searching for package in ${fullUrl}`)
305
+ const items = await parseDirectoryListing(fullUrl)
306
+ // Filter tar.gz files and exclude symbol files
307
+ const packageFiles = items.filter(item => {
308
+ if (!item.endsWith('.tar.gz') && !item.endsWith('.zip')) {
309
+ return false
310
+ }
311
+ // Exclude symbol files based on platform
312
+ if (nodePlatform === 'darwin' && item.includes('-dSYM')) {
313
+ return false
314
+ }
315
+ if (nodePlatform === 'win32' && item.includes('-PDB')) {
316
+ return false
317
+ }
318
+ if (nodePlatform === 'linux' && item.includes('-with-symbol')) {
319
+ return false
320
+ }
321
+ // Must start with nim- prefix
322
+ return item.startsWith('nim-')
323
+ })
324
+ if (packageFiles.length === 0) {
325
+ throw new Error(`No package found in ${fullUrl}`)
326
+ }
327
+ // If multiple files, prefer the first one (they should be the same package, just different compression)
328
+ const packageFile = packageFiles[0]
329
+ log(` ✅ Found package: ${packageFile}`)
330
+ return `${fullUrl}${packageFile}`
331
+ }
332
+
333
+ // Build package URL from branch name
334
+ async function buildPackageUrlFromBranch(branch, nodePlatform, nodeArch) {
335
+ // Base64 encoded internal server URL (decode when needed)
336
+ const encodedBaseUrl = 'aHR0cDovLzEwLjIxOS4yNS4xMjc6ODgvSU0tTmF0aXZlL0Rlc2t0b3A='
337
+ const baseUrl = Buffer.from(encodedBaseUrl, 'base64').toString('utf-8')
338
+ log(` 🌿 Resolving package URL for branch: ${branch}`)
339
+
340
+ // Find latest build that contains the requested platform package
341
+ // This will automatically fallback to previous builds if the latest doesn't have the package
342
+ const { buildNumber, packageUrl } = await findLatestBuildWithPackage(baseUrl, branch, nodePlatform, nodeArch)
343
+
344
+ return packageUrl
345
+ }
346
+
101
347
  if (require.main === module) {
102
348
  const args = process.argv
103
- const downloadUrlIndex = args.indexOf('--nimSdkUrl');
104
- const url = downloadUrlIndex !== -1 ? args[downloadUrlIndex + 1] : '';
105
- downloadSDK(url)
349
+ const urlIndex = args.indexOf('--nimSdkUrl')
350
+ const branchIndex = args.indexOf('--branch')
351
+ ;(async () => {
352
+ try {
353
+ let url
354
+ let downloadUrl = process.env.npm_config_nimsdkurl
355
+ let branch = process.env.npm_config_branch
356
+
357
+ // 优先使用环境变量,然后才是命令行参数
358
+ if (branch || (branchIndex !== -1 && args[branchIndex + 1])) {
359
+ // Build URL from branch name
360
+ branch = branch || args[branchIndex + 1]
361
+ url = await buildPackageUrlFromBranch(branch, platform, process.arch)
362
+ } else if (downloadUrl || (urlIndex !== -1 && args[urlIndex + 1])) {
363
+ // Use provided URL directly
364
+ url = downloadUrl || args[urlIndex + 1]
365
+ // If URL is a directory path (ends with /), find the package
366
+ if (url.endsWith('/')) {
367
+ url = await findPackage(url, platform, arch)
368
+ }
369
+ }
370
+ // Pass undefined to downloadSDK when no custom URL is provided
371
+ // This allows it to fetch the latest package from admin.netease.im
372
+ await downloadSDK(url)
373
+ } catch (error) {
374
+ log(` ❌ ERROR: ${error.message}`)
375
+ process.exit(1)
376
+ }
377
+ })()
106
378
  }
107
379
  exports.downloadSDK = downloadSDK