node-nim 10.9.71-beta.69 → 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.
Files changed (2) hide show
  1. package/package.json +6 -7
  2. package/script/download-sdk.js +311 -150
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-nim",
3
- "version": "10.9.71-beta.69",
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,11 +33,10 @@
33
33
  ]
34
34
  },
35
35
  "dependencies": {
36
- "eventemitter3": "^4.0.7",
36
+ "axios": "^1.6.2",
37
37
  "compare-versions": "^4.1.4",
38
- "node-fetch": "^2.6.9",
39
- "yauzl": "^2.10.0",
40
- "tar": "^6.2.0"
38
+ "decompress": "^4.2.1",
39
+ "eventemitter3": "^4.0.7"
41
40
  },
42
41
  "devDependencies": {
43
42
  "@babel/preset-env": "^7.24.0",
@@ -1,12 +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 yauzl = require('yauzl')
6
- const tar = require('tar')
7
- const { pipeline } = require('stream')
8
- const { promisify } = require('util')
9
- const pipelineAsync = promisify(pipeline)
5
+ const decompress = require('decompress')
10
6
 
11
7
  // Global variables
12
8
  const default_arch = 'universal'
@@ -18,11 +14,10 @@ const product = 'nim'
18
14
  const savePath = path.join(__dirname, '..', 'temporary')
19
15
 
20
16
  if (process.env.npm_config_ignoredownloadsdk) {
21
- console.log('ignore download product')
17
+ console.log('[node-nim] Ignore download product')
22
18
  process.exit(0)
23
19
  }
24
20
  let version
25
- let downloadUrl = process.env.npm_config_nimsdkurl
26
21
  if (process.env.npm_package_version) {
27
22
  version = process.env.npm_package_version.split('-')[0]
28
23
  }
@@ -30,155 +25,125 @@ if (process.env.npm_config_nimsdkversion) {
30
25
  version = process.env.npm_config_nimsdkversion
31
26
  }
32
27
 
33
- // Download and extract function to replace the 'download' package
34
- async function downloadAndExtract(url, destination) {
35
- if (!fs.existsSync(destination)) {
36
- fs.mkdirSync(destination, { recursive: true })
37
- }
38
-
39
- // Download the file
40
- const response = await fetch(url)
41
- if (!response.ok) {
42
- throw new Error(`Failed to download: ${response.statusText}`)
43
- }
44
-
45
- // Determine file type from URL
46
- const isZip = url.toLowerCase().includes('.zip')
47
- const isTarGz = url.toLowerCase().includes('.tar.gz') || url.toLowerCase().includes('.tgz')
48
-
49
- let archivePath
50
- let fileExtension
51
-
52
- if (isZip) {
53
- fileExtension = '.zip'
54
- archivePath = path.join(destination, 'temp.zip')
55
- } else if (isTarGz) {
56
- fileExtension = '.tar.gz'
57
- archivePath = path.join(destination, 'temp.tar.gz')
58
- } else {
59
- // Default to zip if we can't determine the type
60
- fileExtension = '.zip'
61
- archivePath = path.join(destination, 'temp.zip')
62
- console.warn('[node-nim] Could not determine archive type from URL, assuming ZIP format')
63
- }
64
-
65
- // Save the downloaded file
66
- const writeStream = fs.createWriteStream(archivePath)
67
- await pipelineAsync(response.body, writeStream)
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
+ }
68
34
 
69
- // Extract based on file type
70
- if (isTarGz) {
71
- await extractTarGz(archivePath, destination)
72
- } else {
73
- await extractZip(archivePath, destination)
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
+ }
74
60
  }
75
-
76
- // Clean up the temporary archive file
77
- fs.unlinkSync(archivePath)
78
61
  }
79
62
 
80
- // Extract ZIP files
81
- async function extractZip(zipPath, destination) {
82
- return new Promise((resolve, reject) => {
83
- yauzl.open(zipPath, { lazyEntries: true }, (err, zipfile) => {
84
- if (err) return reject(err)
85
-
86
- zipfile.readEntry()
87
- zipfile.on('entry', (entry) => {
88
- // Filter out macOS hidden files (._files)
89
- if (entry.fileName.includes('._')) {
90
- zipfile.readEntry()
91
- return
92
- }
93
-
94
- if (/\/$/.test(entry.fileName)) {
95
- // Directory entry
96
- const dirPath = path.join(destination, entry.fileName)
97
- fs.mkdirSync(dirPath, { recursive: true })
98
- zipfile.readEntry()
99
- } else {
100
- // File entry
101
- zipfile.openReadStream(entry, (err, readStream) => {
102
- if (err) return reject(err)
103
-
104
- const filePath = path.join(destination, entry.fileName)
105
- const fileDir = path.dirname(filePath)
106
-
107
- if (!fs.existsSync(fileDir)) {
108
- fs.mkdirSync(fileDir, { recursive: true })
109
- }
110
-
111
- const writeStream = fs.createWriteStream(filePath)
112
- readStream.pipe(writeStream)
113
-
114
- writeStream.on('close', () => {
115
- zipfile.readEntry()
116
- })
117
-
118
- writeStream.on('error', reject)
119
- })
120
- }
121
- })
122
-
123
- zipfile.on('end', () => {
124
- resolve()
125
- })
126
-
127
- zipfile.on('error', reject)
128
- })
129
- })
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]
130
70
  }
131
71
 
132
- // Extract TAR.GZ files
133
- async function extractTarGz(tarPath, destination) {
134
- return tar.extract({
135
- file: tarPath,
136
- cwd: destination,
137
- filter: (path, entry) => {
138
- // Filter out macOS hidden files (._files)
139
- return !path.includes('/._') && !path.startsWith('._')
140
- }
141
- })
142
- }
143
- async function downloadSDK(custom_sdk_url) {
144
- if (custom_sdk_url) {
145
- downloadUrl = custom_sdk_url
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 })
146
76
  }
147
- // fetch publish list
148
- const res = await fetch('https://admin.netease.im/public-service/free/publish/list')
149
- const publish_json = await res.json()
150
- // get sdk list
151
- if (!downloadUrl) {
152
- let latestVersion = '0.0.0'
153
- let latestDownloadUrl = ''
154
- Object.keys(publish_json.data[channel]).forEach((temp) => {
155
- if (compareVersions.compare(latestVersion, temp, '<')) {
156
- publish_json.data[channel][temp].forEach((member) => {
157
- if (member.filename.includes(product) && member.filename.includes(platform) && member.filename.includes(arch)) {
158
- latestVersion = temp
159
- latestDownloadUrl = member.cdnlink
160
- }
161
- })
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
+ }
162
94
  }
163
- if (version === temp) {
164
- publish_json.data[channel][temp].forEach((member) => {
165
- if (member.filename.includes(product) && member.filename.includes(platform) && member.filename.includes(arch)) {
166
- downloadUrl = member.cdnlink
167
- }
168
- })
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
169
119
  }
170
120
  })
171
- if (!downloadUrl || downloadUrl.length === 0) {
172
- console.log(`[node-nim] Product [${product}] version ${version} not found, use latest version ${latestVersion}`)
173
- 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)
174
128
  }
175
- console.log(`[node-nim] Downloading product: ${product}, platform: ${platform}, arch: ${arch}`)
129
+ throw error
176
130
  }
131
+ }
132
+ async function downloadSDK(customPackageUrl) {
133
+ // Use custom URL if provided, otherwise fetch from official server
134
+ let downloadUrl = customPackageUrl
177
135
  if (!downloadUrl) {
178
- console.error(`[node-nim] Downloading product: ${product}, platform: ${platform}, arch: ${arch} not found`)
179
- 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})`)
180
146
  }
181
- console.info(`[node-nim] Downloading prebuilt SDK from ${downloadUrl} to ${savePath}`)
182
147
  // remove temporary download folder and target folder
183
148
  const target = path.join(__dirname, '..', 'build', 'Release')
184
149
  if (fs.existsSync(savePath)) {
@@ -190,7 +155,6 @@ async function downloadSDK(custom_sdk_url) {
190
155
  // download sdk
191
156
  try {
192
157
  await downloadAndExtract(downloadUrl, savePath)
193
-
194
158
  // create build/Release folder
195
159
  if (!fs.existsSync(target)) {
196
160
  fs.mkdirSync(target, { recursive: true })
@@ -199,20 +163,217 @@ async function downloadSDK(custom_sdk_url) {
199
163
  const from = path.join(savePath, platform === 'win32' ? 'bin' : 'lib')
200
164
  const files = fs.readdirSync(from)
201
165
  files.forEach((file) => {
202
- console.info(`[node-nim] move ${file} to ${target}`)
166
+ log(` 📁 Installing ${file}`)
203
167
  fs.renameSync(path.join(from, file), path.join(target, file))
204
168
  })
205
169
  // remove temporary download folder
206
170
  fs.rmSync(savePath, { recursive: true })
207
- console.info(`[node-nim] Downloading prebuilt SDK complete`)
171
+ log(` Package installation complete!`)
208
172
  } catch (err) {
209
- 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
214
+ }
215
+ return null
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}`)
210
255
  }
256
+ // Return all build numbers sorted by latest first
257
+ return buildNumbers
211
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
+
212
347
  if (require.main === module) {
213
348
  const args = process.argv
214
- const downloadUrlIndex = args.indexOf('--nimSdkUrl');
215
- const url = downloadUrlIndex !== -1 ? args[downloadUrlIndex + 1] : '';
216
- 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
+ })()
217
378
  }
218
379
  exports.downloadSDK = downloadSDK