腾讯云智能媒资托管(Smart Media Hosting,SMH)浏览器端 JavaScript/TypeScript SDK,为开发者提供便捷的浏览器端接口来访问和管理腾讯云 SMH 服务,支持文件上传(含秒传、分片、断点续传)、下载、目录管理等完整功能。
🤖 使用 AI / Agent 接入本 SDK? 请阅读 sdk-docs/AGENTS.md,这是为 Coding Agent(Codex / Claude / CodeBuddy 等)定制的精简手册,读完即可正确生成代码。
使用 npm:
npm install smh-js-sdk
使用 yarn:
yarn add smh-js-sdk
使用 pnpm:
pnpm add smh-js-sdk
// ES Module(推荐)
import { SMHClient } from 'smh-js-sdk'
// CommonJS
const { SMHClient } = require('smh-js-sdk')
const client = new SMHClient({
basePath: 'https://smhxxx.api.tencentsmh.cn', // 专属域名(推荐)
libraryId: 'your-library-id',
spaceId: 'your-space-id',
accessToken: 'your-access-token', // 由后端服务创建
maxRetries: 3, // 可选,请求失败重试次数,默认 3
timeout: 30000, // 可选,请求超时时间(毫秒),默认 30000
})
basePath 专属域名获取方式:在 腾讯云智能媒资托管控制台 创建媒体库后,控制台会展示为您生成的专属域名(如
smhxxx.api.tencentsmh.cn)。强烈建议将basePath设置为您的专属域名,以获得更好的访问性能和稳定性。
安全提示:
librarySecret属于敏感凭据,切勿在浏览器端暴露。创建访问令牌(createToken)应在后端服务中完成,前端仅使用后端签发的accessToken。
初始化后,后续调用 API 时无需每次都传入 libraryId、spaceId、accessToken,SDK 会自动注入。
accessToken 有有效期限制(默认 24 小时),过期后需要续期。
通过 onTokenRefresh 回调实现自动续期,token 过期时 SDK 自动调用回调获取新 token 并重试请求,业务侧无需关心续期时机:
const client = new SMHClient({
basePath: 'https://smhxxx.api.tencentsmh.cn',
libraryId: 'your-library-id',
spaceId: 'your-space-id',
accessToken: 'your-access-token',
onTokenRefresh: async () => {
// 调用后端服务获取新 token
const res = await fetch('/api/refresh-smh-token')
const { accessToken } = await res.json()
return accessToken
}
})
也可以运行时动态设置:
client.setOnTokenRefresh(async () => {
const { accessToken } = await myBackend.refreshToken()
return accessToken
})
多个并发请求同时遇到 token 过期时,只会触发一次续期回调,其余请求等待同一结果。
const renewResponse = await client.token.renewToken({
libraryId: 'your-library-id',
accessToken: accessToken,
})
const newAccessToken = renewResponse.data.accessToken
// 更新默认 accessToken
client.setDefaultAccessToken(newAccessToken)
注意:手动续期方式建议在业务逻辑中提前进行续期以避免请求失败。
// 列出目录内容
const contents = await client.directory.listDirectory({
filePath: '/',
limit: 100,
})
console.log('目录内容:', contents.data)
// 创建目录
await client.directory.createDirectory({
filePath: '/new-folder',
})
// 删除文件
await client.file.deleteFile({
filePath: '/path/to/file.txt',
})
// 搜索文件
const searchResult = await client.search.searchFs({
searchFsRequest: { keywords: ['test'] },
})
import { SMHError, ErrorCode, ServerErrorCode } from 'smh-js-sdk'
try {
await client.directory.listDirectory({ filePath: '/' })
} catch (error) {
if (error instanceof SMHError) {
// error.message 已是友好文案,可直接展示给用户
alert(error.message)
// 按 SDK 错误码做分支
switch (error.code) {
case ErrorCode.NETWORK_ERROR:
console.warn('网络异常,请检查连接')
break
case ErrorCode.SERVER_ERROR:
console.warn('服务器异常,请稍后重试')
break
case ErrorCode.OPERATION_FAILED:
// 可进一步按服务端错误码细分
const serverCode = error.response?.serverCode
if (serverCode === ServerErrorCode.NoPermission) {
console.warn('没有权限')
}
break
}
// 排障信息
console.log('status:', error.status, 'reqId:', error.reqId)
}
}
const uploader = client.createUploadTask({
filePath: '/remote/path/file.txt',
file: fileInput.files[0], // 浏览器 File 对象
enableInstantUpload: true, // 启用秒传
chunkSize: 5, // 分块大小 5MB
parallel: 2, // 2 并发
onStateChange: (checkpoint, state, error) => {
console.log('状态:', state) // start → computing_hash → created → running → success
},
onProgress: (info) => {
console.log(`[${info.state}] 进度: ${info.progress}%, 速度: ${info.speed} B/s`)
},
onPartComplete: (checkpoint, partInfo) => {
console.log(`分片 ${partInfo.part_number} 完成`)
},
})
await uploader.start()
// 暂停 / 恢复 / 取消
await uploader.pause()
await uploader.start() // 恢复(自动断点续传)
await uploader.cancel()
const uploader = client.createUploadTask({
filePath: '/remote/path/file.txt',
file: fileInput.files[0],
onStateChange: (checkpoint, state, error) => {
if (state === 'error' && error) {
// error 即 SMHError,message 已是友好文案
console.error('上传失败:', error.message)
console.log('错误码:', error.code) // 如 'UploadFailed'
console.log('HTTP 状态:', error.status) // 如 400
console.log('服务端错误码:', error.response?.serverCode) // 如 'QuotaLimitReached'
console.log('请求 ID:', error.reqId) // 用于排障
}
},
})
await uploader.start()
通过 <a> 标签触发浏览器原生下载,不占用内存,适合任意大小的文件:
await client.downloadByUrl({
filePath: '/remote/path/file.pdf',
fileName: '自定义文件名.pdf', // 可选,不传则使用远端文件名
})
文件内容下载到内存中(Blob),适合需要二次处理的场景:
const downloader = client.createDownloadTask({
filePath: '/remote/path/file.txt',
chunkSize: 5,
parallel: 2,
onStateChange: (checkpoint, state, error) => {
console.log('状态:', state)
},
onProgress: (info) => {
console.log(`[${info.state}] 进度: ${info.progress}%`)
},
})
const blob = await downloader.startAndGetBlob() // 返回 Blob 对象
// 暂停 / 恢复 / 取消
await downloader.pause()
await downloader.start() // 恢复(自动断点续传)
await downloader.cancel()
const downloader = client.createDownloadTask({
filePath: '/remote/path/file.txt',
onStateChange: (checkpoint, state, error) => {
if (state === 'error' && error) {
console.error('下载失败:', error.message)
console.log('错误码:', error.code) // 如 'DownloadFailed'
console.log('HTTP 状态:', error.status)
console.log('服务端错误码:', error.response?.serverCode)
console.log('请求 ID:', error.reqId)
}
},
})
// 方式一:start() 不抛异常,错误走 onStateChange
await downloader.start()
// 方式二:startAndGetBlob() 会抛异常,需要 catch
try {
const blob = await downloader.startAndGetBlob()
} catch (error) {
if (error instanceof SMHError) {
alert(error.message)
}
}
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
basePath | string | - | API 基础路径,推荐使用专属域名 |
libraryId | string | - | 媒体库 ID |
spaceId | string | - | 空间 ID |
accessToken | string | - | 访问令牌 |
maxRetries | number | 3 | 请求失败重试次数(仅对网络错误和 5xx 错误重试) |
retryDelay | number | 1000 | 重试基础延迟(毫秒),使用指数退避策略 |
timeout | number | 30000 | 请求超时时间(毫秒) |
baseOptions | object | - | 传递给 axios 的额外配置 |
onTokenRefresh | () => Promise<string> | - | Token 续期回调,设置后 token 过期时自动调用并重试请求 |
SDK 会将请求异常统一包装为 SMHError,并默认优先使用服务端错误码映射后的友好文案作为 error.message。通常业务侧无需再手动做一次错误码转文案;只有在需要多语言、自定义文案或更细粒度分流时,才建议自行处理。
import {
SMHClient,
SMHError,
ErrorCode,
ServerErrorCode,
getServerErrorMessage,
setServerErrorMessages,
resetServerErrorMessages,
wrapErrorToSMHError,
} from 'smh-js-sdk'
const client = new SMHClient({
basePath: 'https://smhxxx.api.tencentsmh.cn',
libraryId: 'your-library-id',
spaceId: 'your-space-id',
accessToken: 'your-access-token',
})
try {
await client.file.infoFile({ filePath: '/not-exist.txt', info: 1 })
} catch (error) {
if (error instanceof SMHError) {
// SDK 级错误码(稳定,可用于分支判断)
console.log('code:', error.code)
// HTTP 状态码(推荐优先读 status)
console.log('status:', error.status)
// 向后兼容:仍可通过 response.status 读取
console.log('compat response.status:', error.response?.status)
// 服务端请求 ID(用于排障)
console.log('reqId:', error.reqId || error.response?.requestId)
// 服务端错误码(如 LibraryNotFound / NoPermission)
const serverCode = error.response?.serverCode as string | undefined
console.log('serverCode:', serverCode)
// 通常直接用 error.message 即可(SDK 已完成默认映射)
console.log('friendlyMessage:', error.message)
// 仅在你需要自定义兜底策略时再手动调用
console.log('friendlyMessage(custom-fallback):', getServerErrorMessage(serverCode, '操作失败,请稍后重试'))
if (error.code === ErrorCode.NETWORK_ERROR) {
// 无响应的网络错误(超时/断网/DNS/连接失败等)
console.warn('网络异常,请检查网络连接后重试')
}
if (error.code === ErrorCode.SERVER_ERROR) {
// 服务端返回 5xx 错误
console.warn('服务器异常,请稍后重试')
}
if (serverCode === ServerErrorCode.QuotaLimitReached) {
console.warn('空间不足,请清理文件或扩容')
}
}
}
// 可按业务自定义(覆盖)服务端错误码文案
setServerErrorMessages({
[ServerErrorCode.QuotaLimitReached]: '您的网盘空间已满,请升级套餐',
[ServerErrorCode.NoPermission]: '无权限执行该操作,请联系管理员',
})
// 恢复为 SDK 默认文案
resetServerErrorMessages()
| 字段 | 类型 | 说明 |
|---|---|---|
code | ErrorCode | SDK 统一错误码 |
message | string | 错误消息(优先使用服务端友好文案) |
status | number | undefined | HTTP 状态码 |
reqId | string | undefined | 服务端请求 ID |
response | object | 额外上下文(api、serverCode、serverMessage、responseData 等) |
response.status | number | undefined | 兼容旧版本读取方式 |
response.requestId | string | undefined | 兼容旧版本读取方式 |
| 错误码 | 含义 |
|---|---|
FileNotFound | 文件不存在 |
FileModified | 文件已被修改 |
FileSizeMismatch | 文件大小不匹配 |
FileCrc64Mismatch | CRC64 校验不一致 |
FileTooLarge | 文件过大 |
InvalidFile | 非法文件对象 |
UploadFailed | 上传失败 |
UploadCanceled | 上传已取消 |
UploadPaused | 上传已暂停 |
PartUploadFailed | 分片上传失败 |
RenewUploadFailed | 上传续期失败 |
DownloadFailed | 下载失败 |
DownloadCanceled | 下载已取消 |
DownloadPaused | 下载已暂停 |
InvalidParameter | 参数非法 |
NetworkError | 网络错误(无响应的网络异常:超时、断网、DNS 等) |
ServerError | 服务端错误(HTTP 5xx) |
RequestTimeout | 请求超时 |
OperationFailed | 通用操作失败 |
SDK 内置了完整的服务端错误码枚举和中文映射,详见 服务端错误码文档。
error.code 做业务分支,保证逻辑稳定。NetworkError 表示客户端网络不可达(断网/超时/DNS 等),ServerError 表示服务端返回 5xx,两者可分别制定重试策略。error.message(默认已映射);仅在需要覆盖文案时再调用 getServerErrorMessage(serverCode)。status、reqId、response.serverCode、response.responseData。error.response?.status。| 方法 | 说明 |
|---|---|
getServerErrorMessage(serverCode, fallback?) | 根据服务端错误码获取友好提示 |
setServerErrorMessages(messages) | 批量自定义/覆盖服务端错误码文案 |
resetServerErrorMessages() | 恢复为 SDK 默认的错误码文案映射 |
wrapErrorToSMHError(error, defaultCode, fallbackMsg, extra?) | 将任意错误统一包装为 SMHError(自动提取 AxiosError 详情) |
newError(code, message, cause?, response?, options?) | 手动创建 SMHError 实例 |
client.setOnTokenRefresh(callback) | 运行时动态设置/更新 Token 续期回调 |
ask / rename / overwrite<a> 标签触发浏览器原生下载,不占用内存,适合任意大小文件| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
filePath | string | (必填) | 远端目标路径 |
file | File | (必填) | 浏览器 File 对象 |
chunkSize | number | 5 | 分块大小 (MB) |
parallel | number | 2 | 并发数 |
partFileSize | number | 32 | 分片上传阈值 (MB),范围 1~5120 |
enableInstantUpload | boolean | true | 是否启用秒传 |
trafficLimit | number | - | 单链接限速 (100KB/s ~ 100MB/s) |
internalDomain | 0 | 1 | - | 是否使用内网域名生成上传链接,1 为使用,适用于同地域内网访问场景 |
conflictResolutionStrategy | string | - | 冲突策略:ask / rename / overwrite |
checkpoint | UploadCheckpoint | - | 断点续传 checkpoint |
verbose | boolean | false | 详细日志 |
onStateChange | function | - | 状态变更回调 |
onProgress | function | - | 进度回调 |
onPartComplete | function | - | 分片完成回调 |
通过
client.createUploadTask()创建时,libraryId、spaceId、accessToken会自动从 client 注入,无需手动传入。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
filePath | string | (必填) | 远端文件路径 |
fileName | string | - | 自定义下载文件名,不传则使用远端文件名 |
historyId | string | - | 历史版本 ID,用于下载指定历史版本的文件,不传则下载最新版 |
internalDomain | 0 | 1 | - | 是否使用内网域名生成下载链接,1 为使用,适用于同地域内网访问场景 |
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
filePath | string | (必填) | 远端文件路径 |
chunkSize | number | 5 | 分块大小 (MB) |
parallel | number | 2 | 并发数 |
partFileSize | number | 32 | 分块下载阈值 (MB) |
trafficLimit | number | - | 单链接限速 |
historyId | string | - | 历史版本 ID,用于下载指定历史版本的文件,不传则下载最新版 |
internalDomain | 0 | 1 | - | 是否使用内网域名生成下载链接,1 为使用,适用于同地域内网访问场景 |
checkpoint | DownloadCheckpoint | - | 断点续传 checkpoint |
verbose | boolean | false | 详细日志 |
onStateChange | function | - | 状态变更回调 |
onProgress | function | - | 进度回调 |
onPartComplete | function | - | 分片完成回调 |
通过
client.createDownloadTask()或client.downloadByUrl()创建时,libraryId、spaceId、accessToken会自动从 client 注入,无需手动传入。
本仓库 demo/ 目录下提供了可直接运行的浏览器端示例:
基于 Vite + React + smh-js-sdk 的极简上传队列管理示例,演示大文件分片上传、秒传、断点续传、并发调度、进度与状态管理等能力。
cd demo/queue
npm install
npm run dev
启动后浏览器会自动打开 http://localhost:5191。凭证(basePath / libraryId / spaceId / accessToken)可在 src/App.jsx 顶部 DEFAULT_CRED 中填写。
更多说明与架构解析见 demo/queue/README.md 与 demo/queue/ARCHITECTURE.md。
本项目采用 ISC 许可证。
如有问题或建议,欢迎: