├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .travis.yml ├── Makefile ├── README.md ├── build ├── build.js └── release.sh ├── dist ├── upyun.common.js ├── upyun.esm.js ├── upyun.js └── upyun.min.js ├── examples ├── README.md ├── ai.js └── process.js ├── index.js ├── package.json ├── sample ├── index.html └── server.js ├── tests ├── client │ └── upyun.js ├── fixtures │ ├── cat.jpg │ └── example.amr ├── karma.conf.js └── server │ ├── sign.js │ └── upyun.js └── upyun ├── browser-form-upload.js ├── browser-utils.js ├── constants.js ├── create-req.js ├── form-upload.js ├── service.js ├── sign.js ├── upyun.js └── utils.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env"], 3 | "plugins": ["transform-runtime"] 4 | } 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | indent_size = 2 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [Makefile] 15 | indent_style = tab 16 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist/* 2 | 3 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "commonjs": true, 5 | "es6": true, 6 | "node": true, 7 | "mocha": true 8 | }, 9 | "extends": "eslint:recommended", 10 | "parserOptions": { 11 | "sourceType": "module", 12 | "ecmaVersion": 8 13 | }, 14 | "rules": { 15 | "indent": [ 16 | "error", 17 | 2 18 | ], 19 | "linebreak-style": [ 20 | "error", 21 | "unix" 22 | ], 23 | "quotes": [ 24 | "error", 25 | "single" 26 | ], 27 | "semi": [ 28 | "error", 29 | "never" 30 | ] 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | tests/fixtures/getFile.txt 2 | tests/fixtures/中文带 空格.txt 3 | dev-test/* 4 | # Logs 5 | logs 6 | *.log 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | 13 | # Directory for instrumented libs generated by jscoverage/JSCover 14 | lib-cov 15 | 16 | # Coverage directory used by tools like istanbul 17 | coverage 18 | 19 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 20 | .grunt 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # Commenting this out is preferred by some people, see 27 | # https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git 28 | node_modules 29 | 30 | # Users Environment Variables 31 | .lock-wscript 32 | .DS_Store 33 | [._]*.s[a-w][a-z] 34 | [._]s[a-w][a-z] 35 | *.un~ 36 | Session.vim 37 | .netrwhist 38 | *~ 39 | 40 | package-lock.json 41 | yarn.lock 42 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - 8 5 | 6 | script: 7 | - "npm run test:server" 8 | - "npm run lint" 9 | after_script: "npm install coveralls@2.10.0 && cat ./coverage/lcov.info | coveralls" 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TESTS = test/*.js 2 | 3 | test: 4 | @NODE_ENV=test ./node_modules/.bin/mocha \ 5 | --require should \ 6 | --reporter spec \ 7 | --timeout 50000 \ 8 | $(TESTS) 9 | 10 | test-travis: 11 | @NODE_ENV=test node \ 12 | node_modules/.bin/istanbul cover \ 13 | ./node_modules/.bin/_mocha \ 14 | --report lcovonly \ 15 | -- -u exports \ 16 | --require should \ 17 | --slow 5s \ 18 | --timeout 50000 \ 19 | $(TESTS) \ 20 | --bail 21 | 22 | .PHONY: test 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UPYUN JS SDK 2 | [![NPM version](https://img.shields.io/npm/v/upyun.svg?style=flat)](https://www.npmjs.org/package/upyun) 3 | [![Build Status](https://travis-ci.org/upyun/node-sdk.svg?branch=master)](https://travis-ci.org/upyun/node-sdk) 4 | [![dependencies Status](https://david-dm.org/upyun/node-sdk/status.svg)](https://david-dm.org/upyun/node-sdk) 5 | 6 | upyun js sdk, 支持服务端和客户端使用,集成: 7 | 8 | * [UPYUN HTTP REST 接口](http://docs.upyun.com/api/rest_api/) 9 | * [UPYUN HTTP FORM 接口](http://docs.upyun.com/api/form_api/) 10 | 11 | - 安全起见,浏览器端不能设置操作员账号名和密码 12 | - 服务端需要设置操作员账号名和密码 13 | - 浏览器端使用时,部分参数设置或方法调用会导致跨域失败问题 14 | - `listDir` 设置 `limit | order | iter` 15 | - `putFile` 设置 `Content-Type` 以外的其他选项 16 | - `makeDir | updateMetadata | blockUpload | copy` 无法在浏览器端使用 17 | - `deleteFile` 无法再浏览器端使用异步删除 18 | 19 | # 安装 20 | 21 | ## npm 22 | ``` 23 | $ npm install upyun --production --save 24 | ``` 25 | 26 | ## cdn 27 | 28 | 浏览器端手动安装时,需要手动引入 sdk 的依赖 `axios` (考虑到方便 axios 被复用,浏览器版本构建时,没有引入此依赖) 29 | 30 | ``` 31 | 32 | ``` 33 | 34 | 再引入编译后的 sdk 文件 35 | ``` 36 | 37 | ``` 38 | 39 | # 测试 40 | ``` 41 | $ npm run test 42 | ``` 43 | 44 | # 接口列表 45 | 46 | 所有的接口返回均是 Promise 47 | 48 | ## Client 49 | 50 | ### 初始化 51 | ```js 52 | const client = new upyun.Client(service[, options][, getHeaderSignCallback]) 53 | ``` 54 | 55 | **参数** 56 | 57 | - `service`: 又拍云服务,Service 实例: `new upyun.Service('your service name', 'your operator name', 'your operator password')` 58 | - `options`: 配置项,可以配置以下参数 59 | - `domain`: 又拍云 rest api 地址,默认 `v0.api.upyun.com` 其他可配置域名见又拍云[文档](http://docs.upyun.com/api/rest_api/) 60 | - `protocol`: 使用 `http|https` 协议,默认 `https` 协议 61 | - `proxy`: 代理配置见[使用说明](https://github.com/upyun/node-sdk/pull/63). 默认为 `undefined`. 62 | - `getHeaderSignCallback`: 获取又拍云 HTTP Header 签名回调函数,服务端使用时不需要设置该参数。客户端使用必须设置该回调函数,它接受四个参数:`service, method, path, contentMD5`, 用于计算当前请求签名,其中 `contentMD5` 可选填。如果回调函数是异步,则必须返回一个 `Promise` 63 | 64 | **示例** 65 | 66 | - 服务端使用,一般只需要配置完整又拍云服务信息(服务名、操作员名、操作员密码)即可: 67 | 68 | ```js 69 | const service = new upyun.Service('your service name', 'your operator name', 'your operator password') 70 | const client = new upyun.Client(service); 71 | ``` 72 | 73 | - 客户端使用,必须设置签名回调函数,又拍云服务信息只需服务名即可(注意:**如果回调函数是异步,则必须返回一个 Promise**) 74 | 75 | ```js 76 | /** 77 | * @param {!Service} service: Service 实例 78 | * @param {!string} method: 当前请求的 API 使用的方法 79 | * @param {!string} path: 当前请求的资源路径 80 | * @param {?string} contentMD5 内容的 md5 值 81 | */ 82 | function getSignHeader(service, method, path, contentMD5 = null) { 83 | // 请求自己的服务器,计算当前 api 请求签名信息 84 | // 可以参考该项目 sample 目录中的示例代码 85 | ... 86 | } 87 | const service = new upyun.Service('your service name') 88 | const client = new upyun.Client(service, getSignHeader); 89 | ``` 90 | 91 | ### usage(path = '/') 92 | 93 | 查看目录大小(单位: byte) 94 | 95 | **参数** 96 | 97 | - `path`: 目录路径 98 | 99 | **示例** 100 | 101 | ``` 102 | client.usage('/sub/dir').then(function(size) { 103 | console.log('/sub/dir total used size: ' + size) 104 | }) 105 | ``` 106 | 107 | ### listDir(remotePath, options) 108 | 109 | 获取目录下文件列表 110 | 111 | **参数** 112 | 113 | - `remotePath`: 需要查看的目录路径 114 | - `options`: 115 | - `limit`: 每次请求获取的目录最大列表,最大值 10000,默认 100 116 | - `order`: 列表以文件最后修改时间排序,可选值 `asc|desc`,默认 `asc` 117 | - `iter`: 遍历起点,每次响应参数中,将会包含遍历下一页需要的 `iter` 值 118 | 119 | **响应** 120 | 121 | 目录不存在,返回 `false`,否则返回一个对象,结构如下: 122 | 123 | ```js 124 | { 125 | files: [ 126 | { 127 | name: 'example.txt', // file or dir name 128 | type: 'N', // file type, N: file; F: dir 129 | size: 28392812, // file size 130 | time: 1486053098 // last modify time 131 | } 132 | ], 133 | next: 'dlam9pd2Vmd2Z3Zg==' // next page iter 134 | } 135 | ``` 136 | 137 | ### putFile(remotePath, localFile, options = {}) 138 | 139 | 通过 rest api 上传文件 140 | 141 | **参数** 142 | 143 | - `remotePath`: 文件保存路径 *需包含文件名*(**路径不需要 encodeURI,sdk 会统一处理**) 144 | - `localFile`: 需要上传的文件。服务端支持 `String | Stream | Buffer`, 浏览器端支持 `File | String` **注意 `String` 表示文件内容,不是本地文件路径** 145 | - `options`: 其他可选参数 `Content-MD5 | Content-Length | Content-Type | Content-Secret | x-gmkerl-thumb | x-upyun-meta-x | x-upyun-meta-ttl`(大小写无关,详见[上传参数](http://docs.upyun.com/api/rest_api/#_2)),其中 `Content-Type` 未设置时,将会根据文件路径设置默认值 146 | 147 | **响应** 148 | 149 | 如果是非图片类文件,上传成功返回 `true`, 否则返回一个对象,包含图片的基本信息: 150 | 151 | ```js 152 | { 153 | width: 80, 154 | height: 80, 155 | 'file-type': 'image/jpg', 156 | frames: 1 157 | } 158 | ``` 159 | 160 | 如果上传失败,返回 `false` 161 | 162 | 163 | ### initMultipartUpload(remotePath, fileOrPath, options = {}) 164 | 165 | 初始化一个并行式断点续传任务 166 | 167 | **参数** 168 | 169 | - `remotePath`: 文件保存路径 *需包含文件名*(**路径不需要 encodeURI,sdk 会统一处理**) 170 | - `fileOrPath`: 需要上传的文件。服务端支持 `Buffer` 和文件路径 `String`, 浏览器端支持 `File` 171 | - `options`: 其他可选参数 `Content-MD5 | X-Upyun-Multi-Type | X-Upyun-Meta-X | X-Upyun-Meta-Ttl`(大小写无关,详见[上传参数](http://docs.upyun.com/api/rest_api/#_2)),其中 `Content-Type` 未设置时,将会根据文件路径设置默认值。 172 | 173 | **响应** 174 | 175 | 初始化成功,返回一个对象: 176 | 177 | ```js 178 | { 179 | fileSize: 55997, 180 | partCount: 1, 181 | uuid: 'b2326f91-c5c4-4c6e-bce9-a41dae78ca2a' 182 | } 183 | ``` 184 | 185 | 如果初始化失败,返回 `false` 186 | 187 | ### multipartUpload (remotePath, fileOrPath, multiUuid, partId) 188 | 189 | **参数** 190 | 191 | - `remotePath`: 文件保存路径 *需包含文件名*(**路径不需要 encodeURI,sdk 会统一处理**) 192 | - `fileOrPath`: 需要上传的文件。服务端支持 `Buffer` 和文件路径 `String`, 浏览器端支持 `File` 193 | - `multiUuid`: 任务标识,初始化时生成。即 `X-Upyun-Multi-Uuid` 194 | - `partId` 分块序号,序号从 0 开始. 即 `X-Upyun-Part-Id` 195 | 196 | **响应** 197 | 198 | 数据传输成功返回 `true`, 反之返回 `false` 199 | 200 | ### completeMultipartUpload (remotePath, multiUuid) 201 | 202 | **参数** 203 | 204 | - `remotePath`: 文件保存路径 *需包含文件名*(**路径不需要 encodeURI,sdk 会统一处理**) 205 | - `multiUuid`: 任务标识,初始化时生成。即 `X-Upyun-Multi-Uuid` 206 | 207 | **响应** 208 | 209 | 数据传输成功返回 `true`, 反之返回 `false` 210 | 211 | ### makeDir(remotePath) 212 | 213 | 创建目录 214 | 215 | **参数** 216 | - `remotePath`: 新建目录的路径 217 | 218 | **响应** 219 | 220 | 创建成功返回 `true`,否则 `false` 221 | 222 | ### headFile(remotePath) 223 | 224 | `HEAD` 请求,获取文件基本信息 225 | 226 | **参数** 227 | - `remotePath`: 文件在又拍云云存储服务的路径 228 | 229 | **响应** 230 | 231 | 文件不存在返回 `false`。否则返回一个对象,结构如下,详见[又拍云 HEAD](http://docs.upyun.com/api/rest_api/#_12) 232 | 233 | ```js 234 | { 235 | 'type': 'file', // 文件类型 236 | 'size': 289239, // 文件大小 237 | 'date': 1486053098, // 文件创建时间 238 | 'Content-Md5': '9a56c9d185758d2eda3751e03b891fce' // 文件 md5 值 239 | } 240 | ``` 241 | 242 | ### deleteFile(remotePath) 243 | 244 | 删除文件或目录 245 | 246 | **参数** 247 | - `remotePath`: 文件或目录在又拍云服务的路径 248 | 249 | **响应** 250 | 251 | 删除成功返回 `true`, 否则返回 `false` 252 | 253 | ### getFile(remotePath, saveStream = null) 254 | 255 | 下载保存在又拍云服务的文件 256 | 257 | **参数** 258 | - `remotePath`: 需要下载的文件路径 259 | - `saveStream`: 可选值,一个可以写入的流。若传递该参数,下载的文件将直接写入该流。该参数不支持浏览器端使用 260 | 261 | **响应** 262 | 263 | 如果文件不存在,将会返回 `false`。文件存在时,若没有设置 `saveStream`,该方法 264 | 将直接返回文件内容。若设置了 `saveStream`,文件将直接写入该流,并返回流信息 265 | 266 | **示例** 267 | 268 | 获取文件内容 269 | ```js 270 | client.getFile('/sample.txt').then(function (content) { 271 | console.log(content) // will out put file content directly 272 | }) 273 | ``` 274 | 275 | 写入其他流 276 | ```js 277 | const saveTo = fs.createWriteStream('./localSample.txt') 278 | client.getFile('/sample.txt', saveTo).then(function (stream) { 279 | // file has been saved to localSample.txt 280 | // you can pipe the stream to anywhere you want 281 | }) 282 | ``` 283 | 284 | ### formPutFile(remotePath, localFile, params = {}, opts = {}) 285 | 286 | 使用又拍云[表单 api](http://docs.upyun.com/api/form_api/) 上传文件。客户端使用该方法时, 287 | 必须先设置获取又拍云 [HTTP Body 签名](http://docs.upyun.com/api/authorization/#http-body)的回调函数 288 | 289 | **参数** 290 | 291 | - `remotePath`: 保存路径 292 | - `localFile`: 需要上传的文件,和 `putFile` 相同(**如果在浏览器端使用,只支持 String/Blob/File **) 293 | - `params`: 又拍云表单 api 支持的可选参数(`service(同 bucket)`, `save-key` 两个必选参数不需要手动在这里设置) 294 | - `opts` 295 | - `filename` 可选参数. 用于指定上传字符串/ Blob 的文件名. 支持路径取 basename. 文件名取值优先级 `filename` > `localFile` 是文件 > 默认值 `"file"` 296 | 297 | 298 | **响应** 299 | 300 | 成功返回一个对象,详细说明见[异步通知规则](http://docs.upyun.com/api/form_api/#notify_return)参数说明部分,失败返回 `false` 301 | 302 | ### copy(targetPath, sourcePath, options = {}) 303 | 304 | 将文件 `sourcePath` 拷贝至 `targetPath`,不适用于文件夹。 305 | 306 | **参数** 307 | 308 | - `sourcePath`: 原文件地址 309 | - `targetPath`: 目标文件地址 310 | - `options`: 其他可选参数 `x-upyun-metadata-directive | content-md5 | content-length`(无视大小写) 311 | 312 | **响应** 313 | 314 | 操作成功返回 `true`, 反之返回 `false` 315 | 316 | 317 | ### move(targetPath, sourcePath, options = {}) 318 | 319 | 将文件 `sourcePath` 移动并重命名至 `targetPath`,不适用于文件夹。 320 | 321 | **参数** 322 | 323 | - `sourcePath`: 原文件地址 324 | - `targetPath`: 目标文件地址 325 | - `options`: 其他可选参数 `x-upyun-metadata-directive | content-md5 | content-length`(无视大小写) 326 | 327 | **响应** 328 | 329 | 操作成功返回 `true`, 反之返回 `false` 330 | 331 | 332 | ## Servic**e** 333 | 334 | 又拍云服务,包含以下属性 335 | 336 | - `serviceName` 服务名 337 | - `operatorName` 操作员名 338 | - `password` 操作员密码,读取该属性时,获取的值是 md5 加密后的结果 339 | 340 | ## sign 341 | 342 | 签名模块 343 | 344 | # 备注 345 | 346 | [upyun npm package](https://www.npmjs.org/package/upyun) 曾为 [James Chen](http://ashchan.com) 所有。 347 | 348 | 经过与其的交流协商,James 已经将此 npm 包转由 UPYUN 开发团队管理和维护。 349 | 350 | 后续的代码和版本更新,将于原有的项目无任何直接关系。 351 | 352 | 在 npm 上, `"upyun": "<=0.0.3"` 是由 [James Chen](http://ashchan.com) 曾开发的 [node-upyun](https://github.com/ashchan/node-upyun) 项目. 353 | 354 | __非常感谢 [James Chen](http://ashchan.com) 对 upyun 的支持和贡献__ 355 | -------------------------------------------------------------------------------- /build/build.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * inspired by vue-router build script 5 | */ 6 | const fs = require('fs') 7 | const path = require('path') 8 | const zlib = require('zlib') 9 | const uglify = require('uglify-js') 10 | const rollup = require('rollup') 11 | const babel = require('rollup-plugin-babel') 12 | const cjs = require('rollup-plugin-commonjs') 13 | const json = require('rollup-plugin-json') 14 | const node = require('rollup-plugin-node-resolve') 15 | const builtins = require('rollup-plugin-node-builtins') 16 | const builtinModules = require('builtin-modules') 17 | const pkg = require('../package.json') 18 | const resolve = _path => path.resolve(__dirname, _path) 19 | const version = process.env.VERSION || pkg.version 20 | 21 | const banner = 22 | `/** 23 | * UPYUN js-sdk ${version} 24 | * (c) ${new Date().getFullYear()} 25 | * @license MIT 26 | */` 27 | 28 | if (!fs.existsSync('dist')) { 29 | fs.mkdirSync('dist') 30 | } 31 | 32 | build([ 33 | { 34 | dest: resolve('../dist/upyun.js'), 35 | format: 'umd', 36 | isBrowser: true, 37 | external: ['axios'], 38 | env: 'development' 39 | }, 40 | { 41 | dest: resolve('../dist/upyun.min.js'), 42 | format: 'umd', 43 | isBrowser: true, 44 | external: ['axios'], 45 | env: 'production' 46 | }, 47 | { 48 | dest: resolve('../dist/upyun.esm.js'), 49 | format: 'es', 50 | isBrowser: true, 51 | external: ['axios'] 52 | }, 53 | { 54 | dest: resolve('../dist/upyun.common.js'), 55 | external: [].concat(builtinModules, Object.keys(pkg.dependencies)), 56 | isBrowser: false, 57 | format: 'cjs' 58 | } 59 | ].map(genConfig)) 60 | 61 | function genConfig (opts) { 62 | const config = { 63 | entry: resolve('../index.js'), 64 | dest: opts.dest, 65 | banner, 66 | format: opts.format, 67 | moduleName: 'upyun', 68 | external: opts.external, 69 | paths: opts.paths, 70 | plugins: [ 71 | node({ 72 | browser: opts.isBrowser, 73 | preferBuiltins: !opts.isBrowser 74 | }), 75 | builtins(), 76 | cjs(), 77 | json(), 78 | babel({ 79 | babelrc: false, 80 | plugins: ['external-helpers'], 81 | exclude: 'node_modules/**', 82 | presets: [['env', {modules: false}]] 83 | }) 84 | ] 85 | } 86 | 87 | return config 88 | } 89 | 90 | function build (builds) { 91 | let built = 0 92 | const total = builds.length 93 | const next = () => { 94 | buildEntry(builds[built]).then(() => { 95 | built++ 96 | if (built < total) { 97 | next() 98 | } 99 | }).catch(logError) 100 | } 101 | 102 | next() 103 | } 104 | 105 | function buildEntry (config) { 106 | const isProd = /min\.js$/.test(config.dest) 107 | return rollup.rollup(config).then(bundle => { 108 | const code = bundle.generate(config).code 109 | if (isProd) { 110 | var minified = (config.banner ? config.banner + '\n' : '') + uglify.minify(code, { 111 | output: { 112 | ascii_only: true 113 | } 114 | }).code 115 | return write(config.dest, minified, true) 116 | } else { 117 | return write(config.dest, code) 118 | } 119 | }) 120 | } 121 | 122 | function write (dest, code, zip) { 123 | return new Promise((resolve, reject) => { 124 | function report (extra) { 125 | // eslint-disable-next-line no-console 126 | console.log(blue(path.relative(process.cwd(), dest)) + ' ' + getSize(code) + (extra || '')) 127 | resolve() 128 | } 129 | 130 | fs.writeFile(dest, code, err => { 131 | if (err) return reject(err) 132 | if (zip) { 133 | zlib.gzip(code, (err, zipped) => { 134 | if (err) return reject(err) 135 | report(' (gzipped: ' + getSize(zipped) + ')') 136 | }) 137 | } else { 138 | report() 139 | } 140 | }) 141 | }) 142 | } 143 | 144 | function getSize (code) { 145 | return (code.length / 1024).toFixed(2) + 'kb' 146 | } 147 | 148 | function logError (e) { 149 | // eslint-disable-next-line no-console 150 | console.log(e) 151 | } 152 | 153 | function blue (str) { 154 | return '\x1b[1m\x1b[34m' + str + '\x1b[39m\x1b[22m' 155 | } 156 | -------------------------------------------------------------------------------- /build/release.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | echo "Enter release version: " 3 | read VERSION 4 | 5 | read -p "Releasing $VERSION - are you sure? (y/n)" -n 1 -r 6 | echo # (optional) move to a new line 7 | if [[ $REPLY =~ ^[Yy]$ ]] 8 | then 9 | echo "Releasing $VERSION ..." 10 | npm test 11 | VERSION=$VERSION npm run build 12 | 13 | # commit 14 | git add -A 15 | git commit -m "[build] $VERSION" 16 | npm version $VERSION --message "[release] $VERSION" 17 | 18 | # publish 19 | git push origin refs/tags/v$VERSION 20 | git push 21 | npm publish 22 | fi 23 | -------------------------------------------------------------------------------- /dist/upyun.common.js: -------------------------------------------------------------------------------- 1 | /** 2 | * UPYUN js-sdk 3.4.6 3 | * (c) 2022 4 | * @license MIT 5 | */ 6 | 'use strict'; 7 | 8 | function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } 9 | 10 | var axios = _interopDefault(require('axios')); 11 | var isPromise = _interopDefault(require('is-promise')); 12 | var url = _interopDefault(require('url')); 13 | var fs = _interopDefault(require('fs')); 14 | var mime = _interopDefault(require('mime-types')); 15 | var FormData = _interopDefault(require('form-data')); 16 | var path = _interopDefault(require('path')); 17 | var hmacsha1 = _interopDefault(require('hmacsha1')); 18 | var base64 = _interopDefault(require('base-64')); 19 | var md5 = _interopDefault(require('md5')); 20 | 21 | // NOTE: choose node.js first 22 | // process is defined in client test 23 | 24 | var isBrowser = typeof window !== 'undefined' && (typeof process === 'undefined' || process.title === 'browser'); 25 | 26 | var PARTSIZE = 1024 * 1024; 27 | 28 | var adapter = axios.defaults.adapter; 29 | 30 | axios.defaults.adapter = function () { 31 | // NOTE: in electron environment, support http and xhr both, use http adapter first 32 | if (isBrowser) { 33 | return adapter; 34 | } 35 | 36 | var http = require('axios/lib/adapters/http'); 37 | return http; 38 | }(); 39 | 40 | var createReq = function (endpoint, service, getHeaderSign) { 41 | var _ref = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}, 42 | proxy = _ref.proxy; 43 | 44 | var req = axios.create({ 45 | baseURL: endpoint + '/' + service.serviceName, 46 | maxRedirects: 0, 47 | proxy: proxy 48 | }); 49 | 50 | req.interceptors.request.use(function (config) { 51 | var method = config.method.toUpperCase(); 52 | var path$$1 = url.resolve('/', encodeURI(config.url || '')); 53 | 54 | if (path$$1.indexOf(config.baseURL) === 0) { 55 | path$$1 = path$$1.substring(config.baseURL.length); 56 | } 57 | config.url = path$$1; 58 | var headerSign = getHeaderSign(service, method, path$$1, config.headers['Content-MD5']); 59 | headerSign = isPromise(headerSign) ? headerSign : Promise.resolve(headerSign); 60 | 61 | return headerSign.then(function (headers) { 62 | config.headers.common = headers; 63 | return Promise.resolve(config); 64 | }); 65 | }, function (error) { 66 | throw new Error('upyun - request failed: ' + error.message); 67 | }); 68 | 69 | req.interceptors.response.use(function (response) { 70 | return response; 71 | }, function (error) { 72 | var response = error.response; 73 | 74 | if (typeof response === 'undefined') { 75 | throw error; 76 | } 77 | 78 | if (response.status !== 404) { 79 | var err = new Error('upyun - response error: ' + error.message); 80 | if (error.response.data && error.response.data.code) { 81 | err.code = error.response.data.code; 82 | } 83 | throw err; 84 | } else { 85 | return response; 86 | } 87 | }); 88 | return req; 89 | }; 90 | 91 | function readBlockAsync(filePath, start, end) { 92 | var size = end - start; 93 | var b = makeBuffer(size); 94 | return new Promise(function (resolve, reject) { 95 | fs.open(filePath, 'r', function (err, fd) { 96 | if (err) { 97 | return reject(err); 98 | } 99 | 100 | fs.read(fd, b, 0, size, start, function (err, bytesRead, buffer) { 101 | if (err) { 102 | return reject(err); 103 | } 104 | 105 | return resolve(buffer); 106 | }); 107 | }); 108 | }); 109 | } 110 | 111 | function makeBuffer(size) { 112 | if (Buffer.alloc) { 113 | return Buffer.alloc(size); 114 | } else { 115 | var b = new Buffer(size); 116 | b.fill(0); 117 | return b; 118 | } 119 | } 120 | 121 | function getFileSizeAsync(filePath) { 122 | return new Promise(function (resolve, reject) { 123 | fs.stat(filePath, function (err, stat) { 124 | if (err) return reject(err); 125 | 126 | return resolve(stat.size); 127 | }); 128 | }); 129 | } 130 | 131 | function getContentType(filePath) { 132 | return mime.lookup(filePath); 133 | } 134 | 135 | var utils = { 136 | readBlockAsync: readBlockAsync, 137 | getFileSizeAsync: getFileSizeAsync, 138 | getContentType: getContentType 139 | }; 140 | 141 | function formUpload(remoteUrl, localFile, _ref) { 142 | var authorization = _ref.authorization, 143 | policy = _ref.policy; 144 | 145 | var _ref2 = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}, 146 | filename = _ref2.filename; 147 | 148 | return new Promise(function (resolve, reject) { 149 | var data = new FormData(); 150 | data.append('authorization', authorization); 151 | data.append('policy', policy); 152 | // NOTE when type of localFile is buffer/string, 153 | // force set filename=file, FormData will treat it as a file 154 | // real filename will be set by save-key in policy 155 | filename = filename || localFile.name || localFile.path ? path.basename(filename || localFile.name || localFile.path) : 'file'; 156 | 157 | data.append('file', localFile, { 158 | filename: filename 159 | }); 160 | data.submit(remoteUrl, function (err, res) { 161 | if (err) { 162 | return reject(err); 163 | } 164 | 165 | if (res.statusCode !== 200) { 166 | return resolve(false); 167 | } 168 | 169 | var body = []; 170 | res.on('data', function (chunk) { 171 | body.push(chunk); 172 | }); 173 | res.on('end', function () { 174 | body = Buffer.concat(body).toString('utf8'); 175 | try { 176 | var _data = JSON.parse(body); 177 | return resolve(_data); 178 | } catch (err) { 179 | return reject(err); 180 | } 181 | }); 182 | 183 | res.on('error', function (err) { 184 | reject(err); 185 | }); 186 | }); 187 | }); 188 | } 189 | 190 | var name = "upyun"; 191 | var version = "3.4.6"; 192 | var description = "UPYUN js sdk"; 193 | var main = "dist/upyun.common.js"; 194 | var module$1 = "dist/upyun.esm.js"; 195 | var scripts = { "build": "node build/build.js", "lint": "eslint .", "test": "npm run test:server && npm run test:client", "test:client": "karma start tests/karma.conf.js", "test:server": "mocha --compilers js:babel-register tests/server/*", "preversion": "npm run lint && npm run test", "version": "npm run build && git add -A dist", "postversion": "git push && git push --tags" }; 196 | var repository = { "type": "git", "url": "git@github.com:upyun/node-sdk.git" }; 197 | var engines = { "node": ">=8.0.0" }; 198 | var keywords = ["upyun", "js", "nodejs", "sdk", "cdn", "cloud", "storage"]; 199 | var author = "Leigh"; 200 | var license = "MIT"; 201 | var bugs = { "url": "https://github.com/upyun/node-sdk/issues" }; 202 | var homepage = "https://github.com/upyun/node-sdk"; 203 | var contributors = [{ "name": "yejingx", "email": "yejingx@gmail.com" }, { "name": "Leigh", "email": "i@zhuli.me" }, { "name": "kaidiren", "email": "kaidiren@gmail.com" }, { "name": "Gaara", "email": "sabakugaara@users.noreply.github.com" }]; 204 | var devDependencies = { "babel-cli": "^6.24.1", "babel-loader": "^7.0.0", "babel-plugin-external-helpers": "^6.22.0", "babel-plugin-transform-runtime": "^6.23.0", "babel-preset-env": "^1.4.0", "babel-register": "^6.24.1", "builtin-modules": "^3.1.0", "chai": "^3.5.0", "delay": "^4.2.0", "eslint": "^5.16.0", "istanbul": "^0.4.3", "karma": "^1.7.0", "karma-chrome-launcher": "^2.1.1", "karma-mocha": "^1.3.0", "karma-sourcemap-loader": "^0.3.7", "karma-webpack": "^2.0.3", "mocha": "^3.4.1", "rollup": "^0.41.6", "rollup-plugin-alias": "^1.3.1", "rollup-plugin-babel": "^2.7.1", "rollup-plugin-commonjs": "^8.0.2", "rollup-plugin-json": "^2.1.1", "rollup-plugin-node-builtins": "^2.1.2", "rollup-plugin-node-resolve": "^3.0.0", "should": "^9.0.2", "uglify-js": "^3.0.11", "webpack": "^2.5.1" }; 205 | var dependencies = { "axios": "^0.26.1", "base-64": "^1.0.0", "form-data": "^4.0.0", "hmacsha1": "^1.0.0", "is-promise": "^4.0.0", "md5": "^2.3.0", "mime-types": "^2.1.15" }; 206 | var browser = { "./upyun/utils.js": "./upyun/browser-utils.js", "./upyun/form-upload.js": "./upyun/browser-form-upload.js" }; 207 | var pkg = { 208 | name: name, 209 | version: version, 210 | description: description, 211 | main: main, 212 | module: module$1, 213 | scripts: scripts, 214 | repository: repository, 215 | engines: engines, 216 | keywords: keywords, 217 | author: author, 218 | license: license, 219 | bugs: bugs, 220 | homepage: homepage, 221 | contributors: contributors, 222 | devDependencies: devDependencies, 223 | dependencies: dependencies, 224 | browser: browser 225 | }; 226 | 227 | /** 228 | * generate head sign for rest api 229 | * {@link http://docs.upyun.com/api/authorization/#_2} 230 | * @param {object} service 231 | * @param {string} path - storage path on upyun server, e.g: /your/dir/example.txt 232 | * @param {string} contentMd5 - md5 of the file that will be uploaded 233 | */ 234 | function getHeaderSign(service, method, path$$1) { 235 | var contentMd5 = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null; 236 | 237 | var date = new Date().toGMTString(); 238 | path$$1 = '/' + service.serviceName + path$$1; 239 | var sign = genSign(service, { 240 | method: method, 241 | path: path$$1, 242 | date: date, 243 | contentMd5: contentMd5 244 | }); 245 | return { 246 | 'Authorization': sign, 247 | 'X-Date': date 248 | }; 249 | } 250 | 251 | /** 252 | * generate signature string which can be used in head sign or body sign 253 | * {@link http://docs.upyun.com/api/authorization/#_2} 254 | * @param {object} service 255 | * @param {object} options - must include key is method, path 256 | */ 257 | function genSign(service, options) { 258 | var method = options.method, 259 | path$$1 = options.path; 260 | 261 | 262 | var data = [method, path$$1]; 263 | 264 | // optional params 265 | ['date', 'policy', 'contentMd5'].forEach(function (item) { 266 | if (options[item]) { 267 | data.push(options[item]); 268 | } 269 | }); 270 | 271 | // hmacsha1 return base64 encoded string 272 | var sign = hmacsha1(service.password, data.join('&')); 273 | return 'UPYUN ' + service.operatorName + ':' + sign; 274 | } 275 | 276 | /** 277 | * get policy and authorization for form api 278 | * @param {object} service 279 | * @param {object} - other optional params @see http://docs.upyun.com/api/form_api/#_2 280 | */ 281 | function getPolicyAndAuthorization(service, params) { 282 | params['service'] = service.serviceName; 283 | if (typeof params['save-key'] === 'undefined') { 284 | throw new Error('upyun - calclate body sign need save-key'); 285 | } 286 | 287 | if (typeof params['expiration'] === 'undefined') { 288 | // default 30 minutes 289 | params['expiration'] = parseInt(new Date() / 1000 + 30 * 60, 10); 290 | } 291 | 292 | var policy = base64.encode(JSON.stringify(params)); 293 | var authorization = genSign(service, { 294 | method: 'POST', 295 | path: '/' + service.serviceName, 296 | policy: policy, 297 | contentMd5: params['content-md5'] 298 | }); 299 | return { 300 | policy: policy, 301 | authorization: authorization 302 | }; 303 | } 304 | 305 | /** 306 | * get Authorization and Date for purge api 307 | * {@link http://docs.upyun.com/api/purge/#_1} 308 | * 309 | * @param {!object} service 310 | * @param {!string[]} urls 311 | * 312 | */ 313 | function getPurgeHeaderSign(service, urls) { 314 | var date = new Date().toGMTString(); 315 | var str = urls.join('\n'); 316 | var sign = md5(str + '&' + service.serviceName + '&' + date + '&' + service.password); 317 | 318 | return { 319 | 'Authorization': 'UpYun ' + service.serviceName + ':' + service.operatorName + ':' + sign, 320 | 'Date': date, 321 | 'User-Agent': 'Js-Sdk/' + pkg.version 322 | }; 323 | } 324 | 325 | var sign = { 326 | genSign: genSign, 327 | getHeaderSign: getHeaderSign, 328 | getPolicyAndAuthorization: getPolicyAndAuthorization, 329 | getPurgeHeaderSign: getPurgeHeaderSign 330 | }; 331 | 332 | var classCallCheck = function (instance, Constructor) { 333 | if (!(instance instanceof Constructor)) { 334 | throw new TypeError("Cannot call a class as a function"); 335 | } 336 | }; 337 | 338 | var createClass = function () { 339 | function defineProperties(target, props) { 340 | for (var i = 0; i < props.length; i++) { 341 | var descriptor = props[i]; 342 | descriptor.enumerable = descriptor.enumerable || false; 343 | descriptor.configurable = true; 344 | if ("value" in descriptor) descriptor.writable = true; 345 | Object.defineProperty(target, descriptor.key, descriptor); 346 | } 347 | } 348 | 349 | return function (Constructor, protoProps, staticProps) { 350 | if (protoProps) defineProperties(Constructor.prototype, protoProps); 351 | if (staticProps) defineProperties(Constructor, staticProps); 352 | return Constructor; 353 | }; 354 | }(); 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | var slicedToArray = function () { 383 | function sliceIterator(arr, i) { 384 | var _arr = []; 385 | var _n = true; 386 | var _d = false; 387 | var _e = undefined; 388 | 389 | try { 390 | for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { 391 | _arr.push(_s.value); 392 | 393 | if (i && _arr.length === i) break; 394 | } 395 | } catch (err) { 396 | _d = true; 397 | _e = err; 398 | } finally { 399 | try { 400 | if (!_n && _i["return"]) _i["return"](); 401 | } finally { 402 | if (_d) throw _e; 403 | } 404 | } 405 | 406 | return _arr; 407 | } 408 | 409 | return function (arr, i) { 410 | if (Array.isArray(arr)) { 411 | return arr; 412 | } else if (Symbol.iterator in Object(arr)) { 413 | return sliceIterator(arr, i); 414 | } else { 415 | throw new TypeError("Invalid attempt to destructure non-iterable instance"); 416 | } 417 | }; 418 | }(); 419 | 420 | /** 421 | * @class 422 | */ 423 | 424 | var Upyun = function () { 425 | /** 426 | * @param {object} service - a instance of Service class 427 | * @param {object} params - optional params 428 | * @param {callback} getHeaderSign - callback function to get header sign 429 | */ 430 | function Upyun(service) { 431 | var params = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 432 | var getHeaderSign$$1 = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null; 433 | classCallCheck(this, Upyun); 434 | 435 | if (typeof service.serviceName === 'undefined') { 436 | throw new Error('upyun - must config serviceName'); 437 | } 438 | 439 | if (typeof params === 'function') { 440 | getHeaderSign$$1 = params; 441 | params = {}; 442 | } 443 | 444 | if (typeof getHeaderSign$$1 !== 'function' && isBrowser) { 445 | throw new Error('upyun - must config a callback function getHeaderSign in client side'); 446 | } 447 | 448 | if (!isBrowser && (typeof service.operatorName === 'undefined' || typeof service.password === 'undefined')) { 449 | throw new Error('upyun - must config operateName and password in server side'); 450 | } 451 | 452 | var config = Object.assign({ 453 | domain: 'v0.api.upyun.com', 454 | protocol: 'https' 455 | // proxy: false // 禁用代理 // 参考 axios 配置. 如: {host: '127.0.0.1', post: 1081} 456 | }, params); 457 | 458 | this.endpoint = config.protocol + '://' + config.domain; 459 | var proxy = config.proxy; 460 | 461 | this.proxy = proxy; 462 | this.req = createReq(this.endpoint, service, getHeaderSign$$1 || defaultGetHeaderSign, { proxy: proxy }); 463 | // NOTE this will be removed 464 | this.bucket = service; 465 | this.service = service; 466 | if (!isBrowser) { 467 | this.setBodySignCallback(sign.getPolicyAndAuthorization); 468 | } 469 | } 470 | 471 | createClass(Upyun, [{ 472 | key: 'setService', 473 | value: function setService(service) { 474 | this.service = service; 475 | this.req.defaults.baseURL = this.endpoint + '/' + service.serviceName; 476 | } 477 | 478 | // NOTE this will be removed 479 | 480 | }, { 481 | key: 'setBucket', 482 | value: function setBucket(bucket) { 483 | return this.setService(bucket); 484 | } 485 | }, { 486 | key: 'setBodySignCallback', 487 | value: function setBodySignCallback(getBodySign) { 488 | if (typeof getBodySign !== 'function') { 489 | throw new Error('upyun - getBodySign should be a function'); 490 | } 491 | this.bodySignCallback = getBodySign; 492 | } 493 | }, { 494 | key: 'usage', 495 | value: function usage() { 496 | var dir = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '/'; 497 | 498 | return this.req.get(dir + '?usage').then(function (_ref) { 499 | var data = _ref.data; 500 | 501 | return Promise.resolve(data); 502 | }); 503 | } 504 | }, { 505 | key: 'listDir', 506 | value: function listDir() { 507 | var dir = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '/'; 508 | 509 | var _ref2 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, 510 | _ref2$limit = _ref2.limit, 511 | limit = _ref2$limit === undefined ? 100 : _ref2$limit, 512 | _ref2$order = _ref2.order, 513 | order = _ref2$order === undefined ? 'asc' : _ref2$order, 514 | _ref2$iter = _ref2.iter, 515 | iter = _ref2$iter === undefined ? '' : _ref2$iter; 516 | 517 | var requestHeaders = {}; 518 | 519 | // NOTE: 默认值可以省去请求头设置,避免跨域影响 520 | if (limit !== 100) { 521 | requestHeaders['x-list-limit'] = limit; 522 | } 523 | 524 | if (order !== 'asc') { 525 | requestHeaders['x-list-order'] = order; 526 | } 527 | 528 | if (iter) { 529 | requestHeaders['x-list-iter'] = iter; 530 | } 531 | 532 | return this.req.get(dir, { 533 | headers: requestHeaders 534 | }).then(function (_ref3) { 535 | var data = _ref3.data, 536 | headers = _ref3.headers, 537 | status = _ref3.status; 538 | 539 | if (status === 404) { 540 | return false; 541 | } 542 | 543 | var next = headers['x-upyun-list-iter']; 544 | if (!data) { 545 | return Promise.resolve({ 546 | files: [], 547 | next: next 548 | }); 549 | } 550 | 551 | var items = data.split('\n'); 552 | var files = items.map(function (item) { 553 | var _item$split = item.split('\t'), 554 | _item$split2 = slicedToArray(_item$split, 4), 555 | name = _item$split2[0], 556 | type = _item$split2[1], 557 | size = _item$split2[2], 558 | time = _item$split2[3]; 559 | 560 | return { 561 | name: name, 562 | type: type, 563 | size: parseInt(size), 564 | time: parseInt(time) 565 | }; 566 | }); 567 | 568 | return Promise.resolve({ 569 | files: files, 570 | next: next 571 | }); 572 | }); 573 | } 574 | 575 | /** 576 | * @param localFile: file content, available type is Stream | String | Buffer for server; File | String for client 577 | * @see https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/send 578 | * @see https://github.com/mzabriskie/axios/blob/master/lib/adapters/http.js#L32 579 | */ 580 | 581 | }, { 582 | key: 'putFile', 583 | value: function putFile(remotePath, localFile) { 584 | var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; 585 | 586 | // optional params 587 | var keys = ['Content-MD5', 'Content-Length', 'Content-Type', 'Content-Secret', 'x-gmkerl-thumb']; 588 | var headers = {}; 589 | var optionsLower = {}; 590 | var _iteratorNormalCompletion = true; 591 | var _didIteratorError = false; 592 | var _iteratorError = undefined; 593 | 594 | try { 595 | for (var _iterator = Object.keys(options)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { 596 | var key = _step.value; 597 | 598 | optionsLower[key.toLowerCase()] = options[key]; 599 | } 600 | } catch (err) { 601 | _didIteratorError = true; 602 | _iteratorError = err; 603 | } finally { 604 | try { 605 | if (!_iteratorNormalCompletion && _iterator.return) { 606 | _iterator.return(); 607 | } 608 | } finally { 609 | if (_didIteratorError) { 610 | throw _iteratorError; 611 | } 612 | } 613 | } 614 | 615 | var _iteratorNormalCompletion2 = true; 616 | var _didIteratorError2 = false; 617 | var _iteratorError2 = undefined; 618 | 619 | try { 620 | for (var _iterator2 = Object.keys(optionsLower)[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { 621 | var _key = _step2.value; 622 | 623 | if (isMeta(_key) && optionsLower[_key]) { 624 | headers[_key] = optionsLower[_key]; 625 | } else { 626 | keys.forEach(function (key) { 627 | var lower = key.toLowerCase(); 628 | var finded = optionsLower[lower]; 629 | if (finded) { 630 | headers[key] = finded; 631 | } 632 | }); 633 | } 634 | } 635 | } catch (err) { 636 | _didIteratorError2 = true; 637 | _iteratorError2 = err; 638 | } finally { 639 | try { 640 | if (!_iteratorNormalCompletion2 && _iterator2.return) { 641 | _iterator2.return(); 642 | } 643 | } finally { 644 | if (_didIteratorError2) { 645 | throw _iteratorError2; 646 | } 647 | } 648 | } 649 | 650 | return this.req.put(remotePath, localFile, { 651 | headers: headers 652 | }).then(function (_ref4) { 653 | var responseHeaders = _ref4.headers, 654 | status = _ref4.status; 655 | 656 | if (status !== 200) { 657 | return Promise.resolve(false); 658 | } 659 | 660 | var params = ['x-upyun-width', 'x-upyun-height', 'x-upyun-file-type', 'x-upyun-frames']; 661 | var result = {}; 662 | params.forEach(function (item) { 663 | var key = item.split('x-upyun-')[1]; 664 | if (responseHeaders[item]) { 665 | result[key] = responseHeaders[item]; 666 | if (key !== 'file-type') { 667 | result[key] = parseInt(result[key], 10); 668 | } 669 | } 670 | }); 671 | return Promise.resolve(Object.keys(result).length > 0 ? result : true); 672 | }); 673 | } 674 | }, { 675 | key: 'initMultipartUpload', 676 | value: function initMultipartUpload(remotePath, fileOrPath) { 677 | var _this = this; 678 | 679 | var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; 680 | 681 | var fileSizePromise = void 0; 682 | var lowerOptions = key2LowerCase(options); 683 | var contentType = lowerOptions['x-upyun-multi-type']; 684 | 685 | if (isBrowser) { 686 | fileSizePromise = Promise.resolve(fileOrPath.size); 687 | contentType = contentType || fileOrPath.type; 688 | } else { 689 | if (Buffer.isBuffer(fileOrPath)) { 690 | fileSizePromise = Promise.resolve(fileOrPath.length); 691 | contentType = contentType || 'application/octet-stream'; 692 | } else { 693 | fileSizePromise = utils.getFileSizeAsync(fileOrPath); 694 | contentType = contentType || utils.getContentType(fileOrPath); 695 | } 696 | } 697 | 698 | return fileSizePromise.then(function (fileSize) { 699 | Object.assign(lowerOptions, { 700 | 'x-upyun-multi-disorder': true, 701 | 'x-upyun-multi-stage': 'initiate', 702 | 'x-upyun-multi-length': fileSize, 703 | 'x-upyun-multi-type': contentType 704 | }); 705 | 706 | return _this.req.put(remotePath, null, { 707 | headers: lowerOptions 708 | }).then(function (_ref5) { 709 | var headers = _ref5.headers, 710 | status = _ref5.status; 711 | 712 | if (status !== 204) { 713 | return Promise.resolve(false); 714 | } 715 | 716 | var uuid = headers['x-upyun-multi-uuid']; 717 | 718 | return Promise.resolve({ 719 | fileSize: fileSize, 720 | partCount: Math.ceil(fileSize / PARTSIZE), 721 | uuid: uuid 722 | }); 723 | }); 724 | }); 725 | } 726 | }, { 727 | key: 'multipartUpload', 728 | value: function multipartUpload(remotePath, fileOrPath, multiUuid, partId) { 729 | var _this2 = this; 730 | 731 | var start = partId * PARTSIZE; 732 | var fileSizePromise = void 0; 733 | // let contentType 734 | 735 | if (isBrowser) { 736 | fileSizePromise = Promise.resolve(fileOrPath.size); 737 | // contentType = fileOrPath.type 738 | } else { 739 | if (Buffer.isBuffer(fileOrPath)) { 740 | fileSizePromise = Promise.resolve(fileOrPath.length); 741 | } else { 742 | fileSizePromise = utils.getFileSizeAsync(fileOrPath); 743 | // contentType = utils.getContentType(fileOrPath) 744 | } 745 | } 746 | 747 | var blockPromise = fileSizePromise.then(function (fileSize) { 748 | var end = Math.min(start + PARTSIZE, fileSize); 749 | 750 | return Buffer.isBuffer(fileOrPath) ? fileOrPath.slice(start, end) : utils.readBlockAsync(fileOrPath, start, end); 751 | }); 752 | 753 | return blockPromise.then(function (block) { 754 | return _this2.req.put(remotePath, block, { 755 | headers: { 756 | 'x-upyun-multi-stage': 'upload', 757 | 'x-upyun-multi-uuid': multiUuid, 758 | 'x-upyun-part-id': partId 759 | } 760 | }).then(function (_ref6) { 761 | var status = _ref6.status; 762 | 763 | return Promise.resolve(status === 204); 764 | }); 765 | }); 766 | } 767 | }, { 768 | key: 'completeMultipartUpload', 769 | value: function completeMultipartUpload(remotePath, multiUuid) { 770 | return this.req.put(remotePath, null, { 771 | headers: { 772 | 'x-upyun-multi-stage': 'complete', 773 | 'x-upyun-multi-uuid': multiUuid 774 | } 775 | }).then(function (_ref7) { 776 | var status = _ref7.status; 777 | 778 | return Promise.resolve(status === 204 || status === 201); 779 | }); 780 | } 781 | }, { 782 | key: 'makeDir', 783 | value: function makeDir(remotePath) { 784 | return this.req.post(remotePath, null, { 785 | headers: { folder: 'true' } 786 | }).then(function (_ref8) { 787 | var status = _ref8.status; 788 | 789 | return Promise.resolve(status === 200); 790 | }); 791 | } 792 | 793 | /** 794 | * copy file 795 | * 796 | * {@link https://help.upyun.com/knowledge-base/rest_api/#e5a48de588b6e69687e4bbb6 } 797 | * 798 | * @param {!string} targetPath 799 | * @param {!string} sourcePath 800 | * @param {?object} options={} - 可传入参数 `x-upyun-metadata-directive`, `content-md5`, `content-length` 801 | */ 802 | 803 | }, { 804 | key: 'copy', 805 | value: function copy(targetPath, sourcePath) { 806 | var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; 807 | 808 | var lowerOptions = key2LowerCase(options); 809 | 810 | var headers = Object.assign(lowerOptions, { 811 | 'x-upyun-copy-source': path.join('/', this.service.serviceName, sourcePath) 812 | }); 813 | 814 | return this.req.put(targetPath, null, { 815 | headers: headers 816 | }).then(function (_ref9) { 817 | var status = _ref9.status; 818 | 819 | return Promise.resolve(status >= 200 && status < 300); 820 | }); 821 | } 822 | 823 | /** 824 | * move file 825 | * 826 | * {@link https://help.upyun.com/knowledge-base/rest_api/#e7a7bbe58aa8e69687e4bbb6 } 827 | * 828 | * @param {!string} targetPath 829 | * @param {!string} sourcePath 830 | * @param {?object} options={} - 可传入参数 `x-upyun-metadata-directive`, `content-md5`, `content-length` 831 | */ 832 | 833 | }, { 834 | key: 'move', 835 | value: function move(targetPath, sourcePath) { 836 | var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; 837 | 838 | var lowerOptions = key2LowerCase(options); 839 | 840 | var headers = Object.assign(lowerOptions, { 841 | 'x-upyun-move-source': path.join('/', this.service.serviceName, sourcePath) 842 | }); 843 | 844 | return this.req.put(targetPath, null, { 845 | headers: headers 846 | }).then(function (_ref10) { 847 | var status = _ref10.status; 848 | 849 | return Promise.resolve(status >= 200 && status < 300); 850 | }); 851 | } 852 | }, { 853 | key: 'headFile', 854 | value: function headFile(remotePath) { 855 | return this.req.head(remotePath).then(function (_ref11) { 856 | var headers = _ref11.headers, 857 | status = _ref11.status; 858 | 859 | if (status === 404) { 860 | return Promise.resolve(false); 861 | } 862 | 863 | var params = ['x-upyun-file-type', 'x-upyun-file-size', 'x-upyun-file-date']; 864 | var result = { 865 | 'Content-Md5': headers['content-md5'] || '' 866 | }; 867 | 868 | params.forEach(function (item) { 869 | var key = item.split('x-upyun-file-')[1]; 870 | if (headers[item]) { 871 | result[key] = headers[item]; 872 | if (key === 'size' || key === 'date') { 873 | result[key] = parseInt(result[key], 10); 874 | } 875 | } 876 | }); 877 | return Promise.resolve(result); 878 | }); 879 | } 880 | }, { 881 | key: 'deleteFile', 882 | value: function deleteFile(remotePath) { 883 | var isAsync = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; 884 | 885 | var headers = {}; 886 | if (isAsync) { 887 | headers['x-upyun-async'] = true; 888 | } 889 | return this.req.delete(remotePath, { 890 | headers: headers 891 | }).then(function (_ref12) { 892 | var status = _ref12.status; 893 | 894 | return Promise.resolve(status === 200); 895 | }); 896 | } 897 | }, { 898 | key: 'deleteDir', 899 | value: function deleteDir() { 900 | for (var _len = arguments.length, args = Array(_len), _key2 = 0; _key2 < _len; _key2++) { 901 | args[_key2] = arguments[_key2]; 902 | } 903 | 904 | return this.deleteFile.apply(this, args); 905 | } 906 | }, { 907 | key: 'getFile', 908 | value: function getFile(remotePath) { 909 | var saveStream = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; 910 | 911 | if (saveStream && isBrowser) { 912 | throw new Error('upyun - save as stream are only available on the server side.'); 913 | } 914 | 915 | return this.req({ 916 | method: 'GET', 917 | url: remotePath, 918 | responseType: saveStream ? 'stream' : null 919 | }).then(function (response) { 920 | if (response.status === 404) { 921 | return Promise.resolve(false); 922 | } 923 | 924 | if (!saveStream) { 925 | return Promise.resolve(response.data); 926 | } 927 | 928 | var stream = response.data.pipe(saveStream); 929 | 930 | return new Promise(function (resolve, reject) { 931 | stream.on('finish', function () { 932 | return resolve(stream); 933 | }); 934 | 935 | stream.on('error', reject); 936 | }); 937 | }); 938 | } 939 | }, { 940 | key: 'updateMetadata', 941 | value: function updateMetadata(remotePath, metas) { 942 | var operate = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'merge'; 943 | 944 | var metaHeaders = {}; 945 | for (var key in metas) { 946 | if (!isMeta(key)) { 947 | metaHeaders['x-upyun-meta-' + key] = metas[key]; 948 | } else { 949 | metaHeaders[key] = metas; 950 | } 951 | } 952 | 953 | return this.req.patch(remotePath + '?metadata=' + operate, null, { headers: metaHeaders }).then(function (_ref13) { 954 | var status = _ref13.status; 955 | 956 | return Promise.resolve(status === 200); 957 | }); 958 | } 959 | 960 | // be careful: this will download the entire file 961 | 962 | }, { 963 | key: 'getMetadata', 964 | value: function getMetadata(remotePath) { 965 | return this.req.get(remotePath).then(function (_ref14) { 966 | var headers = _ref14.headers, 967 | status = _ref14.status; 968 | 969 | if (status !== 200) { 970 | return Promise.resolve(false); 971 | } 972 | 973 | var result = {}; 974 | for (var key in headers) { 975 | if (isMeta(key)) { 976 | result[key] = headers[key]; 977 | } 978 | } 979 | 980 | return Promise.resolve(result); 981 | }); 982 | } 983 | 984 | /** 985 | * in browser: type of fileOrPath is File 986 | * in server: type of fileOrPath is string: local file path 987 | */ 988 | 989 | }, { 990 | key: 'blockUpload', 991 | value: function blockUpload(remotePath, fileOrPath) { 992 | var _this3 = this; 993 | 994 | var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; 995 | 996 | var fileSizePromise = void 0; 997 | var contentType = void 0; 998 | if (isBrowser) { 999 | fileSizePromise = Promise.resolve(fileOrPath.size); 1000 | contentType = fileOrPath.type; 1001 | } else { 1002 | fileSizePromise = utils.getFileSizeAsync(fileOrPath); 1003 | contentType = utils.getContentType(fileOrPath); 1004 | } 1005 | 1006 | return fileSizePromise.then(function (fileSize) { 1007 | Object.assign(options, { 1008 | 'x-upyun-multi-stage': 'initiate', 1009 | 'x-upyun-multi-length': fileSize, 1010 | 'x-upyun-multi-type': contentType 1011 | }); 1012 | 1013 | var blockSize = 1024 * 1024; 1014 | var blocks = Math.ceil(fileSize / blockSize); 1015 | 1016 | return _this3.req.put(remotePath, null, { 1017 | headers: options 1018 | }).then(function (_ref15) { 1019 | var headers = _ref15.headers; 1020 | 1021 | var uuid = headers['x-upyun-multi-uuid']; 1022 | var nextId = headers['x-upyun-next-part-id']; 1023 | 1024 | var p = Promise.resolve(nextId); 1025 | for (var index = 0; index < blocks; index++) { 1026 | p = p.then(function (nextId) { 1027 | var start = nextId * blockSize; 1028 | var end = Math.min(start + blockSize, fileSize); 1029 | var blockPromise = utils.readBlockAsync(fileOrPath, start, end); 1030 | return blockPromise.then(function (block) { 1031 | return _this3.req.put(remotePath, block, { 1032 | headers: { 1033 | 'x-upyun-multi-stage': 'upload', 1034 | 'x-upyun-multi-uuid': uuid, 1035 | 'x-upyun-part-id': nextId 1036 | } 1037 | }).then(function (_ref16) { 1038 | var headers = _ref16.headers; 1039 | 1040 | nextId = headers['x-upyun-next-part-id']; 1041 | return Promise.resolve(nextId); 1042 | }); 1043 | }); 1044 | }); 1045 | } 1046 | 1047 | return p.then(function () { 1048 | return _this3.req.put(remotePath, null, { 1049 | headers: { 1050 | 'x-upyun-multi-stage': 'complete', 1051 | 'x-upyun-multi-uuid': uuid 1052 | } 1053 | }).then(function (_ref17) { 1054 | var status = _ref17.status; 1055 | 1056 | return Promise.resolve(status === 204 || status === 201); 1057 | }); 1058 | }); 1059 | }); 1060 | }); 1061 | } 1062 | }, { 1063 | key: 'formPutFile', 1064 | value: function formPutFile(remotePath, localFile) { 1065 | var _this4 = this; 1066 | 1067 | var orignParams = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; 1068 | var opts = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; 1069 | 1070 | var params = {}; 1071 | var _iteratorNormalCompletion3 = true; 1072 | var _didIteratorError3 = false; 1073 | var _iteratorError3 = undefined; 1074 | 1075 | try { 1076 | for (var _iterator3 = Object.keys(orignParams)[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { 1077 | var key = _step3.value; 1078 | 1079 | params[key.toLowerCase()] = orignParams[key]; 1080 | } 1081 | } catch (err) { 1082 | _didIteratorError3 = true; 1083 | _iteratorError3 = err; 1084 | } finally { 1085 | try { 1086 | if (!_iteratorNormalCompletion3 && _iterator3.return) { 1087 | _iterator3.return(); 1088 | } 1089 | } finally { 1090 | if (_didIteratorError3) { 1091 | throw _iteratorError3; 1092 | } 1093 | } 1094 | } 1095 | 1096 | if (typeof this.bodySignCallback !== 'function') { 1097 | throw new Error('upyun - must setBodySignCallback first!'); 1098 | } 1099 | 1100 | params['service'] = this.service.serviceName; 1101 | params['save-key'] = remotePath; 1102 | var result = this.bodySignCallback(this.service, params); 1103 | result = isPromise(result) ? result : Promise.resolve(result); 1104 | 1105 | return result.then(function (bodySign) { 1106 | return formUpload(_this4.endpoint + '/' + params['service'], localFile, bodySign, opts); 1107 | }); 1108 | } 1109 | }, { 1110 | key: 'purge', 1111 | value: function purge(urls) { 1112 | if (typeof urls === 'string') { 1113 | urls = [urls]; 1114 | } 1115 | var headers = sign.getPurgeHeaderSign(this.service, urls); 1116 | return axios.post('http://purge.upyun.com/purge/', 'purge=' + urls.join('\n'), { 1117 | headers: headers, 1118 | proxy: this.proxy 1119 | }).then(function (_ref18) { 1120 | var data = _ref18.data; 1121 | 1122 | if (Object.keys(data.invalid_domain_of_url).length === 0) { 1123 | return true; 1124 | } else { 1125 | throw new Error('some url purge failed ' + data.invalid_domain_of_url.join(' ')); 1126 | } 1127 | }, function (err) { 1128 | throw new Error('upyun - request failed: ' + err.message); 1129 | }); 1130 | } 1131 | }]); 1132 | return Upyun; 1133 | }(); 1134 | 1135 | function isMeta(key) { 1136 | return key.indexOf('x-upyun-meta-') === 0; 1137 | } 1138 | 1139 | function defaultGetHeaderSign() { 1140 | return sign.getHeaderSign.apply(sign, arguments); 1141 | } 1142 | 1143 | function key2LowerCase(obj) { 1144 | var objLower = {}; 1145 | var _iteratorNormalCompletion4 = true; 1146 | var _didIteratorError4 = false; 1147 | var _iteratorError4 = undefined; 1148 | 1149 | try { 1150 | for (var _iterator4 = Object.keys(obj)[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { 1151 | var key = _step4.value; 1152 | 1153 | objLower[key.toLowerCase()] = obj[key]; 1154 | } 1155 | } catch (err) { 1156 | _didIteratorError4 = true; 1157 | _iteratorError4 = err; 1158 | } finally { 1159 | try { 1160 | if (!_iteratorNormalCompletion4 && _iterator4.return) { 1161 | _iterator4.return(); 1162 | } 1163 | } finally { 1164 | if (_didIteratorError4) { 1165 | throw _iteratorError4; 1166 | } 1167 | } 1168 | } 1169 | 1170 | return objLower; 1171 | } 1172 | 1173 | /** 1174 | * @class 1175 | */ 1176 | 1177 | var Service = function Service(serviceName) { 1178 | var operatorName = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ''; 1179 | var password = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : ''; 1180 | classCallCheck(this, Service); 1181 | 1182 | // NOTE bucketName will be removed 1183 | this.bucketName = serviceName; 1184 | this.serviceName = this.bucketName; 1185 | this.operatorName = operatorName; 1186 | this.password = md5(password); 1187 | }; 1188 | 1189 | var index = { 1190 | Client: Upyun, 1191 | sign: sign, 1192 | Bucket: Service, 1193 | Service: Service 1194 | }; 1195 | 1196 | module.exports = index; 1197 | -------------------------------------------------------------------------------- /dist/upyun.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * UPYUN js-sdk 3.4.6 3 | * (c) 2022 4 | * @license MIT 5 | */ 6 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("axios")):"function"==typeof define&&define.amd?define(["axios"],t):e.upyun=t(e.axios)}(this,function(p){"use strict";p="default"in p?p.default:p;var a="undefined"!=typeof window&&("undefined"==typeof process||"browser"===process.title),u=1048576,v=function(e){return!!e&&("object"==typeof e||"function"==typeof e)&&"function"==typeof e.then};var U="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{};function e(e,t){return e(t={exports:{}},t.exports),t.exports}var E=e(function(I,B){!function(e){var t=B&&!B.nodeType&&B,r=I&&!I.nodeType&&I,n="object"==typeof U&&U;n.global!==n&&n.window!==n&&n.self!==n||(e=n);var o,i,m=2147483647,g=36,b=1,x=26,s=38,a=700,w=72,A=128,j="-",u=/^xn--/,h=/[^\x20-\x7E]/,l=/[\x2E\u3002\uFF0E\uFF61]/g,c={overflow:"Overflow: input needs wider integers to process","not-basic":"Illegal input >= 0x80 (not a basic code point)","invalid-input":"Invalid input"},f=g-b,C=Math.floor,k=String.fromCharCode;function S(e){throw RangeError(c[e])}function p(e,t){for(var r=e.length,n=[];r--;)n[r]=t(e[r]);return n}function v(e,t){var r=e.split("@"),n="";return 1>>10&1023|55296),e=56320|1023&e),t+=k(e)}).join("")}function q(e,t){return e+22+75*(e<26)-((0!=t)<<5)}function T(e,t,r){var n=0;for(e=r?C(e/a):e>>1,e+=C(e/t);f*x>>1C((m-v)/s))&&S("overflow"),v+=u*s,!(u<(h=a<=y?b:y+x<=a?x:a-y));a+=g)s>C(m/(l=g-h))&&S("overflow"),s*=l;y=T(v-i,t=f.length+1,0==i),C(v/t)>m-d&&S("overflow"),d+=C(v/t),v%=t,f.splice(v++,0,d)}return P(f)}function y(e){var t,r,n,o,i,s,a,u,h,l,c,f,p,v,d,y=[];for(f=(e=O(e)).length,t=A,i=w,s=r=0;sC((m-r)/(p=n+1))&&S("overflow"),r+=(a-t)*p,t=a,s=0;sm&&S("overflow"),c==t){for(u=r,h=g;!(u<(l=h<=i?b:i+x<=h?x:h-i));h+=g)d=u-l,v=g-l,y.push(k(q(l+d%v,0))),u=C(d/v);y.push(k(q(u,0))),i=T(r,p,n==o),r=0,++n}++r,++t}return y.join("")}if(o={version:"1.3.2",ucs2:{decode:O,encode:P},decode:d,encode:y,toASCII:function(e){return v(e,function(e){return h.test(e)?"xn--"+y(e):e})},toUnicode:function(e){return v(e,function(e){return u.test(e)?d(e.slice(4).toLowerCase()):e})}},t&&r)if(I.exports==t)r.exports=o;else for(i in o)o.hasOwnProperty(i)&&(t[i]=o[i]);else e.punycode=o}(U)}),N={isString:function(e){return"string"==typeof e},isObject:function(e){return"object"==typeof e&&null!==e},isNull:function(e){return null===e},isNullOrUndefined:function(e){return null==e}};function r(e,t,r,n){t=t||"&",r=r||"=";var o={};if("string"!=typeof e||0===e.length)return o;var i=/\+/g;e=e.split(t);var s=1e3;n&&"number"==typeof n.maxKeys&&(s=n.maxKeys);var a,u,h=e.length;0",'"',"`"," ","\r","\n","\t"]),F=["'"].concat(f),_=["%","/","?",";","#"].concat(F),L=["/","?","#"],D=/^[+a-z0-9A-Z_-]{0,63}$/,H=/^([+a-z0-9A-Z_-]{0,63})(.*)$/,Z={javascript:!0,"javascript:":!0},G={javascript:!0,"javascript:":!0},$={http:!0,https:!0,ftp:!0,gopher:!0,file:!0,"http:":!0,"https:":!0,"ftp:":!0,"gopher:":!0,"file:":!0};function d(e,t,r){if(e&&N.isObject(e)&&e instanceof O)return e;var n=new O;return n.parse(e,t,r),n}O.prototype.parse=function(e,t,r){if(!N.isString(e))throw new TypeError("Parameter 'url' must be a string, not "+typeof e);var n=e.indexOf("?"),o=-1!==n&&n>16)+(t>>16)+(r>>16)<<16|65535&r}function A(e,t){return e<>>32-t}function u(e,t){e[t>>5]|=128<<24-t%32,e[15+(t+64>>9<<4)]=t;for(var r,n,o,i,s,a=[80],u=1732584193,h=-271733879,l=-1732584194,c=271733878,f=-1009589776,p=0;p>5]|=(e.charCodeAt(n/8)&r)<<32-a-n%32;return t}return i||(i="="),a||(a=8),function(e){for(var t="",r=0;r<4*e.length;r+=3)for(var n=(e[r>>2]>>8*(3-r%4)&255)<<16|(e[r+1>>2]>>8*(3-(r+1)%4)&255)<<8|e[r+2>>2]>>8*(3-(r+2)%4)&255,o=0;o<4;o++)8*r+6*o>32*e.length?t+=i:t+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(n>>6*(3-o)&63);return t}(function(e,t){var r=h(e);16>18&63)+l.charAt(o>>12&63)+l.charAt(o>>6&63)+l.charAt(63&o);return 2==i?(t=e.charCodeAt(a)<<8,r=e.charCodeAt(++a),s+=l.charAt((o=t+r)>>10)+l.charAt(o>>4&63)+l.charAt(o<<2&63)+"="):1==i&&(o=e.charCodeAt(a),s+=l.charAt(o>>2)+l.charAt(o<<4&63)+"=="),s},decode:function(e){var t=(e=String(e).replace(a,"")).length;t%4==0&&(t=(e=e.replace(/==?$/,"")).length),t%4!=1&&!/[^+a-zA-Z0-9/]/.test(e)||h("Invalid character: the string to be decoded is not correctly encoded.");for(var r,n,o=0,i="",s=-1;++s>(-2*o&6)));return i},version:"0.1.0"};if(t&&!t.nodeType)if(r)r.exports=i;else for(var s in i)i.hasOwnProperty(s)&&(t[s]=i[s]);else e.base64=i}(U)}),B="3.4.6",J=e(function(e){var i,r;i="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",r={rotl:function(e,t){return e<>>32-t},rotr:function(e,t){return e<<32-t|e>>>t},endian:function(e){if(e.constructor==Number)return 16711935&r.rotl(e,8)|4278255360&r.rotl(e,24);for(var t=0;t>>5]|=e[r]<<24-n%32;return t},wordsToBytes:function(e){for(var t=[],r=0;r<32*e.length;r+=8)t.push(e[r>>>5]>>>24-r%32&255);return t},bytesToHex:function(e){for(var t=[],r=0;r>>4).toString(16)),t.push((15&e[r]).toString(16));return t.join("")},hexToBytes:function(e){for(var t=[],r=0;r>>6*(3-o)&63)):t.push("=");return t.join("")},base64ToBytes:function(e){e=e.replace(/[^A-Z0-9+\/]/gi,"");for(var t=[],r=0,n=0;r>>6-2*n);return t}},e.exports=r}),K={utf8:{stringToBytes:function(e){return K.bin.stringToBytes(unescape(encodeURIComponent(e)))},bytesToString:function(e){return decodeURIComponent(escape(K.bin.bytesToString(e)))}},bin:{stringToBytes:function(e){for(var t=[],r=0;r>>24)|4278255360&(r[u]<<24|r[u]>>>8);r[n>>>5]|=128<>>9<<4)]=n;var h=w._ff,l=w._gg,c=w._hh,f=w._ii;for(u=0;u>>0,i=i+v>>>0,s=s+d>>>0,a=a+y>>>0}return m.endian([o,i,s,a])})._ff=function(e,t,r,n,o,i,s){var a=e+(t&r|~t&n)+(o>>>0)+s;return(a<>>32-i)+t},w._gg=function(e,t,r,n,o,i,s){var a=e+(t&n|r&~n)+(o>>>0)+s;return(a<>>32-i)+t},w._hh=function(e,t,r,n,o,i,s){var a=e+(t^r^n)+(o>>>0)+s;return(a<>>32-i)+t},w._ii=function(e,t,r,n,o,i,s){var a=e+(r^(t|~n))+(o>>>0)+s;return(a<>>32-i)+t},w._blocksize=16,w._digestsize=16,e.exports=function(e,t){if(null==e)throw new Error("Illegal argument "+e);var r=m.wordsToBytes(w(e,t));return t&&t.asBytes?r:t&&t.asString?x.bytesToString(r):m.bytesToHex(r)}});function Q(e,t){var r=[t.method,t.path];["date","policy","contentMd5"].forEach(function(e){t[e]&&r.push(t[e])});var n=T(e.password,r.join("&"));return"UPYUN "+e.operatorName+":"+n}function V(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var ee={genSign:Q,getHeaderSign:function(e,t,r){var n=3=8.0.0" 23 | }, 24 | "keywords": [ 25 | "upyun", 26 | "js", 27 | "nodejs", 28 | "sdk", 29 | "cdn", 30 | "cloud", 31 | "storage" 32 | ], 33 | "author": "Leigh", 34 | "license": "MIT", 35 | "bugs": { 36 | "url": "https://github.com/upyun/node-sdk/issues" 37 | }, 38 | "homepage": "https://github.com/upyun/node-sdk", 39 | "contributors": [ 40 | { 41 | "name": "yejingx", 42 | "email": "yejingx@gmail.com" 43 | }, 44 | { 45 | "name": "Leigh", 46 | "email": "i@zhuli.me" 47 | }, 48 | { 49 | "name": "kaidiren", 50 | "email": "kaidiren@gmail.com" 51 | }, 52 | { 53 | "name": "Gaara", 54 | "email": "sabakugaara@users.noreply.github.com" 55 | } 56 | ], 57 | "devDependencies": { 58 | "babel-cli": "^6.24.1", 59 | "babel-loader": "^7.0.0", 60 | "babel-plugin-external-helpers": "^6.22.0", 61 | "babel-plugin-transform-runtime": "^6.23.0", 62 | "babel-preset-env": "^1.4.0", 63 | "babel-register": "^6.24.1", 64 | "builtin-modules": "^3.1.0", 65 | "chai": "^3.5.0", 66 | "delay": "^4.2.0", 67 | "eslint": "^5.16.0", 68 | "istanbul": "^0.4.3", 69 | "karma": "^1.7.0", 70 | "karma-chrome-launcher": "^2.1.1", 71 | "karma-mocha": "^1.3.0", 72 | "karma-sourcemap-loader": "^0.3.7", 73 | "karma-webpack": "^2.0.3", 74 | "mocha": "^3.4.1", 75 | "rollup": "^0.41.6", 76 | "rollup-plugin-alias": "^1.3.1", 77 | "rollup-plugin-babel": "^2.7.1", 78 | "rollup-plugin-commonjs": "^8.0.2", 79 | "rollup-plugin-json": "^2.1.1", 80 | "rollup-plugin-node-builtins": "^2.1.2", 81 | "rollup-plugin-node-resolve": "^3.0.0", 82 | "should": "^9.0.2", 83 | "uglify-js": "^3.0.11", 84 | "webpack": "^2.5.1" 85 | }, 86 | "dependencies": { 87 | "axios": "^0.26.1", 88 | "base-64": "^1.0.0", 89 | "form-data": "^4.0.0", 90 | "hmacsha1": "^1.0.0", 91 | "is-promise": "^4.0.0", 92 | "md5": "^2.3.0", 93 | "mime-types": "^2.1.15" 94 | }, 95 | "browser": { 96 | "./upyun/utils.js": "./upyun/browser-utils.js", 97 | "./upyun/form-upload.js": "./upyun/browser-form-upload.js" 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /sample/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 又拍云 - js sdk 使用示例 6 | 7 | 8 | 9 | 10 | 11 | 12 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /sample/server.js: -------------------------------------------------------------------------------- 1 | const http = require('http') 2 | const url = require('url') 3 | const upyun = require('../') 4 | const fs = require('fs') 5 | const path = require('path') 6 | 7 | const bucket = new upyun.Bucket('sdkimg', 'tester', 'grjxv2mxELR3') 8 | 9 | http.createServer(function(req, res) { 10 | if (req.url.indexOf('index.html') !== -1 || req.url === '/') { 11 | res.end(fs.readFileSync(path.join(__dirname, './index.html'), 'utf-8')) 12 | } else if (req.url.indexOf('upyun.js') !== -1) { 13 | res.end(fs.readFileSync(path.join(__dirname, '../dist/upyun.js'), 'utf-8')) 14 | } else if (req.url.indexOf('/sign/head') !== -1) { 15 | const query = url.parse(req.url, true).query 16 | const headSign = upyun.sign.getHeaderSign(bucket, query.method, query.path, query.contentMD5) 17 | res.end(JSON.stringify(headSign)) 18 | } else { 19 | res.writeHead(404) 20 | res.end() 21 | } 22 | }).listen(3000) 23 | 24 | -------------------------------------------------------------------------------- /tests/client/upyun.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import { expect } from 'chai' 4 | import Upyun from '../../upyun/upyun' 5 | import Service from '../../upyun/service' 6 | import sign from '../../upyun/sign' 7 | 8 | const service = new Service('sdkimg', 'tester', 'grjxv2mxELR3') 9 | function getHeaderSign (ignore, method, path) { 10 | const headers = sign.getHeaderSign(service, method, path) 11 | return Promise.resolve(headers) 12 | } 13 | 14 | const client = new Upyun({ 15 | serviceName: 'sdkimg' 16 | }, getHeaderSign) 17 | 18 | client.setBodySignCallback((ignore, params) => { 19 | return Promise.resolve(sign.getPolicyAndAuthorization(service, params)) 20 | }) 21 | 22 | describe('index', function () { 23 | this.timeout(10000) 24 | describe('#blockUpload', () => { 25 | it('should upload file success', async () => { 26 | const f = new Blob(['text'], {type: 'text/plain'}) 27 | f.name = 'testBlockUpload.txt' 28 | 29 | const result = await client.blockUpload('/testBlockUpload.txt', f, { 30 | 'Content-Length': 4, 31 | 'Content-Type': 'text/plain' 32 | }) 33 | expect(result).to.equal(true) 34 | }) 35 | }) 36 | 37 | describe('#multipartUpload', () => { 38 | it('should upload file success', async () => { 39 | const remotePath = '/testMultipartUpload.txt' 40 | 41 | const f = new Blob(['text'], {type: 'text/plain'}) 42 | f.name = 'testBlockUpload.txt' 43 | 44 | const {/*fileSize, */partCount, uuid} = await client.initMultipartUpload(remotePath, f) 45 | 46 | await Promise.all(Array.apply(null, {length: partCount}).map(Function.call, index => { 47 | const partId = index 48 | return client.multipartUpload(remotePath, f, uuid, partId) 49 | })) 50 | 51 | const result = await client.completeMultipartUpload(remotePath, uuid) 52 | 53 | expect(result).to.equal(true) 54 | }) 55 | }) 56 | 57 | describe('#formUpload', () => { 58 | it('should upload file success', async () => { 59 | const f = new Blob(['text'], {type: 'text/plain'}) 60 | f.name = 'testFormUpload.txt' 61 | const result = await client.formPutFile('/testFormUpload.txt', f) 62 | expect(result.code).to.equal(200) 63 | }) 64 | 65 | it('should upload base64 encode file success', async () => { 66 | const options = { 67 | 'content-type': 'text/plain', 68 | 'b64encoded': 'on', 69 | } 70 | const result = await client.formPutFile( 71 | '/test-client-base64.txt', 72 | 'dGVzdCBiYXNlNjQgdXBsb2Fk', 73 | options 74 | ) 75 | expect(result.code).to.equal(200) 76 | }) 77 | }) 78 | }) 79 | -------------------------------------------------------------------------------- /tests/fixtures/cat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/upyun/node-sdk/06345246e9c21d08f4e0836dc73ba7903db63032/tests/fixtures/cat.jpg -------------------------------------------------------------------------------- /tests/fixtures/example.amr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/upyun/node-sdk/06345246e9c21d08f4e0836dc73ba7903db63032/tests/fixtures/example.amr -------------------------------------------------------------------------------- /tests/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | 3 | module.exports = function (config) { 4 | config.set({ 5 | 6 | // base path that will be used to resolve all patterns (eg. files, exclude) 7 | basePath: '..', 8 | 9 | // frameworks to use 10 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 11 | frameworks: ['mocha'], 12 | 13 | // list of files / patterns to load in the browser 14 | files: [ 15 | 'tests/client/*.js' 16 | ], 17 | 18 | // list of files to exclude 19 | exclude: [ 20 | '**/*.swp' 21 | ], 22 | 23 | // preprocess matching files before serving them to the browser 24 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 25 | preprocessors: { 26 | 'tests/client/*.js': ['webpack', 'sourcemap'], 27 | 'upyun/*.js': ['webpack', 'sourcemap'] 28 | }, 29 | webpack: { 30 | resolve: { 31 | extensions: ['.js'], 32 | modules: [ 33 | '.', 34 | 'node_modules' 35 | ] 36 | }, 37 | module: { 38 | rules: [ 39 | {test: /\.js$/, loader: 'babel-loader', exclude: /node_modules/} 40 | ] 41 | }, 42 | devtool: 'inline-source-map' 43 | }, 44 | 45 | // test results reporter to use 46 | // possible values: 'dots', 'progress' // available reporters: https://npmjs.org/browse/keyword/karma-reporter 47 | reporters: ['progress'], 48 | 49 | // web server port 50 | port: 9876, 51 | 52 | // enable / disable colors in the output (reporters and logs) 53 | colors: true, 54 | 55 | // level of logging 56 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 57 | logLevel: config.LOG_INFO, 58 | 59 | // enable / disable watching file and executing tests whenever any file changes 60 | autoWatch: true, 61 | 62 | // start these browsers 63 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 64 | browsers: ['Chrome_without_security'], 65 | // you can define custom flags 66 | customLaunchers: { 67 | Chrome_without_security: { 68 | base: 'Chrome', 69 | flags: ['--disable-web-security'] 70 | } 71 | }, 72 | 73 | // Continuous Integration mode 74 | // if true, Karma captures browsers, runs the tests and exits 75 | singleRun: true, 76 | 77 | // Concurrency level 78 | // how many browser should be started simultaneous 79 | concurrency: 1, 80 | webpackMiddleware: { 81 | stats: 'errors-only' 82 | } 83 | }) 84 | } 85 | -------------------------------------------------------------------------------- /tests/server/sign.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import { expect } from 'chai' 4 | import sign from '../../upyun/sign' 5 | import Service from '../../upyun/service' 6 | 7 | describe('sign', () => { 8 | const service = new Service('sdkimg', 'operator', 'password') 9 | describe('#genSign', () => { 10 | it('should gen sign success', () => { 11 | const str = sign.genSign(service, { 12 | method: 'POST', 13 | path: '/bucket' 14 | }) 15 | 16 | expect(str).to.equal('UPYUN operator:Xx3G6+DAvUyCL2Y2npSW/giTFI8=') 17 | }) 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /tests/server/upyun.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import { expect } from 'chai' 4 | import Upyun from '../../upyun/upyun' 5 | import Service from '../../upyun/service' 6 | import fs from 'fs' 7 | import path from 'path' 8 | import {promisify} from 'util' 9 | import md5 from 'md5' 10 | import delay from 'delay' 11 | 12 | const readFileAsync = promisify(fs.readFile) 13 | 14 | const fixtures = path.join(__dirname, '../fixtures') 15 | 16 | const client = new Upyun(new Service('sdkimg', 'tester', 'grjxv2mxELR3')) 17 | 18 | describe('index', function () { 19 | this.timeout(10000) 20 | describe('#usage', () => { 21 | it('should get usage success', async () => { 22 | let data = await client.usage() 23 | expect(data >= 0).to.equal(true) 24 | }) 25 | }) 26 | 27 | describe('#putFile', () => { 28 | it('should upload string success', async () => { 29 | let data = await client.putFile('/textForPutfile.txt', 'Augue et arcu blandit tincidunt. Pellentesque.') 30 | 31 | expect(data).to.equal(true) 32 | }) 33 | 34 | it('should upload with chinese path success', async () => { 35 | let data = await client.putFile('/中文.txt', '中文测试') 36 | 37 | expect(data).to.equal(true) 38 | }) 39 | 40 | it('should upload picture success', async () => { 41 | // only use stream on server side 42 | let jpg = fixtures + '/cat.jpg' 43 | // TODO better for length 44 | const content = await readFileAsync(jpg) 45 | let options = { 46 | 'Content-Length': fs.statSync(jpg).size, 47 | 'content-MD5': md5(content) 48 | } 49 | 50 | let data = await client.putFile('/cat.jpg', content, options) 51 | expect(data).to.deep.equal({ 52 | width: 500, 53 | frames: 1, 54 | height: 333, 55 | 'file-type': 'JPEG' 56 | }) 57 | }) 58 | }) 59 | 60 | describe('#listDir', () => { 61 | const txtFileName = 'textForListdir.txt' 62 | before(async () => { 63 | await client.putFile(`/${txtFileName}`, 'Augue et arcu blandit tincidunt. Pellentesque.') 64 | }) 65 | 66 | it('should get dir list success', async () => { 67 | let data = await client.listDir() 68 | 69 | expect(data.files.length > 0).to.equal(true) 70 | 71 | let file = data.files.find(ele => { 72 | return ele.name === txtFileName 73 | }) 74 | expect(file.name).to.equal(txtFileName) 75 | expect(file.size).to.equal(46) 76 | expect(file.time > 0).to.equal(true) 77 | expect(file.type).to.equal('N') 78 | }) 79 | 80 | it('should list not exist dir path success', async () => { 81 | let data = await client.listDir('/not-exist-dir') 82 | expect(data).to.equal(false) 83 | }) 84 | }) 85 | 86 | 87 | describe('#makeDir', () => { 88 | it('should create dir success', async () => { 89 | let data = await client.makeDir('/testdir2') 90 | 91 | expect(data).to.equal(true) 92 | }) 93 | }) 94 | 95 | describe('#headFile', () => { 96 | let filePath = '/headFile.txt' 97 | let dirPath = '/headDir' 98 | let content = 'Dictum accumsan, convallis accumsan, cursus sit amet, ipsum. In pharetra sagittis.' 99 | before(async () => { 100 | await client.putFile(filePath, content) 101 | await client.makeDir(dirPath) 102 | }) 103 | 104 | it('should get file info success', async () => { 105 | let result = await client.headFile(filePath) 106 | expect(result['type']).to.equal('file') 107 | expect(result['size']).to.equal(82) 108 | expect(result['Content-Md5']).to.equal(md5(content)) 109 | expect(result).to.have.property('date') 110 | }) 111 | 112 | it('should get dir info success', async () => { 113 | let result = await client.headFile(dirPath) 114 | expect(result['type']).to.equal('folder') 115 | expect(result).to.have.property('date') 116 | }) 117 | 118 | it('should get false when file not exist', async () => { 119 | let result = await client.headFile('/not-exist-path2333') 120 | expect(result).to.equal(false) 121 | }) 122 | }) 123 | 124 | describe('#deleteFile', () => { 125 | let syncPath = '/fileForSyncDeleteFile.txt' 126 | let asyncPath = '/fileForAsyncDeleteFile.txt' 127 | 128 | context('when file is exist', () => { 129 | before(async () => { 130 | await client.putFile(syncPath, 'Dictum accumsan, convallis accumsan.') 131 | await client.putFile(asyncPath, 'Dictum accumsan, convallis accumsan.') 132 | }) 133 | 134 | it('should delete success', async () => { 135 | await delay(1000) // 降低响应码 429 Too Many Requests 发生几率 136 | let result = await client.deleteFile(syncPath) 137 | expect(result).to.equal(true) 138 | }) 139 | 140 | it('should async delete success', async () => { 141 | await delay(1000) // 降低响应码 429 Too Many Requests 发生几率 142 | let result = await client.deleteFile(asyncPath, true) 143 | expect(result).to.equal(true) 144 | }) 145 | }) 146 | 147 | it('should get false when file not exist', async () => { 148 | let result = await client.deleteFile('/not-exist-path2333') 149 | expect(result).to.equal(false) 150 | }) 151 | }) 152 | 153 | describe('#deleteDir', () => { 154 | let dirPath = '/headDir' 155 | 156 | it('should delete success', async () => { 157 | let result = await client.deleteDir(dirPath) 158 | expect(result).to.equal(true) 159 | }) 160 | }) 161 | 162 | describe('#getFile', () => { 163 | let filePath = '/中文带 空格.txt' 164 | before(async () => { 165 | await client.putFile(filePath, 'Dictum accumsan, convallis accumsan.') 166 | }) 167 | 168 | it('should get file content success', async () => { 169 | let result = await client.getFile(filePath) 170 | expect(result).to.equal('Dictum accumsan, convallis accumsan.') 171 | }) 172 | 173 | it('should pipe file content to stream success', async () => { 174 | await client.getFile(filePath, fs.createWriteStream(fixtures + filePath)) 175 | let result = fs.readFileSync(fixtures + filePath, 'utf-8') 176 | expect(result).to.equal('Dictum accumsan, convallis accumsan.') 177 | }) 178 | 179 | it('should get false when remote file not exist', async () => { 180 | let result = await client.getFile('/not-exists-path') 181 | expect(result).to.equal(false) 182 | }) 183 | }) 184 | 185 | describe('#updateMetadata', () => { 186 | let filePath = '/meta.txt' 187 | before(async () => { 188 | await client.putFile(filePath, 'Dictum accumsan, convallis accumsan.') 189 | }) 190 | 191 | it('should update metadata success', async () => { 192 | let result = await client.updateMetadata(filePath, { 193 | 'foo': 'bar' 194 | }) 195 | 196 | expect(result).to.equal(true) 197 | 198 | let metas = await client.getMetadata(filePath) 199 | expect(metas['x-upyun-meta-foo']).to.equal('bar') 200 | }) 201 | }) 202 | 203 | describe('#blockUpload', () => { 204 | it('should upload file success', async () => { 205 | const result = await client.blockUpload('/testBlockUpload.jpg', fixtures + '/cat.jpg') 206 | expect(result).to.equal(true) 207 | }) 208 | }) 209 | 210 | describe('#multipartUpload', () => { 211 | it('should upload file success', async () => { 212 | const remotePath = 'testMultipartUpload.jpg' 213 | const localPath = fixtures + '/cat.jpg' 214 | const {/**fileSize, */partCount, uuid} = await client.initMultipartUpload(remotePath, localPath) 215 | 216 | await Promise.all(Array.apply(null, {length: partCount}).map(Function.call, index => { 217 | const partId = index 218 | return client.multipartUpload(remotePath, localPath, uuid, partId) 219 | })) 220 | 221 | const result = await client.completeMultipartUpload(remotePath, uuid) 222 | 223 | expect(result).to.equal(true) 224 | }) 225 | 226 | it('should upload buffer success', async () => { 227 | const remotePath = 'testMultipartUploadForBuffer.jpg' 228 | const buf = await fs.promises.readFile(fixtures + '/cat.jpg') 229 | const {/**fileSize, */partCount, uuid} = await client.initMultipartUpload(remotePath, buf, { 230 | 'x-upyun-multi-type': 'image/jpeg', 231 | }) 232 | 233 | await Promise.all(Array.apply(null, {length: partCount}).map(Function.call, index => { 234 | const partId = index 235 | return client.multipartUpload(remotePath, buf, uuid, partId) 236 | })) 237 | 238 | const result = await client.completeMultipartUpload(remotePath, uuid) 239 | 240 | expect(result).to.equal(true) 241 | }) 242 | }) 243 | 244 | describe('#formUpload', () => { 245 | it('should upload file success', async () => { 246 | const result = await client.formPutFile('/testFormUpload.jpg', fs.createReadStream(fixtures + '/cat.jpg')) 247 | expect(result.code).to.equal(200) 248 | }) 249 | 250 | it('should convert amr to mp3 success when upload amr file', async () => { 251 | const options = { 252 | 'content-type': 'audio/amr', 253 | apps: [{ 254 | name: 'naga', 255 | type: 'video', 256 | avopts: '/f/mp3', 257 | return_info: true, 258 | save_as: '/amr-mp3-test.mp3' 259 | }] 260 | } 261 | const result = await client.formPutFile( 262 | '/test.amr', 263 | fs.createReadStream(fixtures + '/example.amr'), 264 | options 265 | ) 266 | expect(result.code).to.equal(200) 267 | }) 268 | 269 | it('should upload base64 encode file success', async () => { 270 | const content = 'dGVzdCBiYXNlNjQgdXBsb2Fk' 271 | const options = { 272 | 'content-type': 'text/plain', 273 | 'b64encoded': 'on', 274 | 'content-md5': md5(content) 275 | } 276 | const result = await client.formPutFile( 277 | '/test-base64.txt', 278 | content, 279 | options 280 | ) 281 | expect(result.code).to.equal(200) 282 | }) 283 | }) 284 | 285 | xdescribe('#purge', function () { 286 | this.timeout(5000) 287 | it('should purge urls sucess', async () => { 288 | const urls = 'http://sdkimg.b0.upaiyun.com/a.txt' 289 | const result = await client.purge(urls) 290 | expect(result).to.equal(true) 291 | }) 292 | 293 | it('should get some url purge failed', async () => { 294 | const urls = ['http://zzzzzz.b0.upaiyun.com/a.txt', 'http://xxxx.b0.upaiyun.com/a.txt'] 295 | try { 296 | await client.purge(urls) 297 | } catch (err) { 298 | expect(err.message).to.include('some url purge failed') 299 | return 300 | } 301 | throw new Error('should get error') 302 | }) 303 | }) 304 | 305 | describe('#copy', function () { 306 | context('when source file is exist', () => { 307 | const sourcePath = '/copysource.txt' 308 | const targetPath = '/copytarget.txt' 309 | 310 | before(async () => { 311 | await delay(2000) // 降低响应码 429 Too Many Requests 发生几率 312 | await client.putFile(sourcePath, 'copy file') 313 | }) 314 | 315 | it('should copy file success', async () => { 316 | await delay(2000) // 降低响应码 429 Too Many Requests 发生几率 317 | const result = await client.copy(targetPath, sourcePath) 318 | expect(result).to.equal(true) 319 | }) 320 | }) 321 | 322 | context('when source file is not exist', () => { 323 | const sourcePath = '/test/notExistCopySource.txt' 324 | const targetPath = '/test/notExistCopyTarget.txt' 325 | it('should copy file that does not exist', async () => { 326 | await delay(2000) // 降低响应码 429 Too Many Requests 发生几率 327 | const result = await client.copy(targetPath, sourcePath) 328 | expect(result).to.equal(false) 329 | }) 330 | }) 331 | }) 332 | 333 | describe('#move', function () { 334 | context('when source file is exist', () => { 335 | const sourcePath = '/test/movesource.txt' 336 | const targetPath = '/test/movetarget.txt' 337 | before(async () => { 338 | await delay(2000) // 降低响应码 429 Too Many Requests 发生几率 339 | await client.putFile(sourcePath, 'move file') 340 | }) 341 | 342 | it('should move file success', async () => { 343 | await delay(2000) // 降低响应码 429 Too Many Requests 发生几率 344 | const result = await client.move(targetPath, sourcePath) 345 | expect(result).to.equal(true) 346 | }) 347 | }) 348 | 349 | context('when source file is not exist', () => { 350 | const sourcePath = '/test/notExistMoveSource.txt' 351 | const targetPath = '/test/notExistMoveTarget.txt' 352 | it('should move file that does not exist', async () => { 353 | await delay(2000) // 降低响应码 429 Too Many Requests 发生几率 354 | const result = await client.move(targetPath, sourcePath) 355 | expect(result).to.equal(false) 356 | }) 357 | }) 358 | }) 359 | }) 360 | -------------------------------------------------------------------------------- /upyun/browser-form-upload.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import path from 'path' 3 | 4 | export default function formUpload (remoteUrl, localFile, {authorization, policy}, {filename} = {}) { 5 | const data = new FormData() 6 | data.append('authorization', authorization) 7 | data.append('policy', policy) 8 | if (typeof localFile === 'string') { 9 | localFile = new Blob([localFile], {type: 'text/plain'}) 10 | } 11 | 12 | filename = filename ? path.basename(filename) : filename 13 | data.append('file', localFile, filename) 14 | return axios.post(remoteUrl, data).then(({status, data}) => { 15 | if (status === 200) { 16 | return Promise.resolve(data) 17 | } 18 | 19 | return false 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /upyun/browser-utils.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | export function readBlockAsync (localFile, start, end) { 4 | return new Promise((resolve, reject) => { 5 | const slice = localFile.slice || localFile.mozSlice || localFile.webkitSlice 6 | if (slice) { 7 | return resolve(slice.call(localFile, start, end)) 8 | } else { 9 | return reject(new Error('not support File type!')) 10 | } 11 | }) 12 | } 13 | 14 | export default { 15 | readBlockAsync 16 | } 17 | -------------------------------------------------------------------------------- /upyun/constants.js: -------------------------------------------------------------------------------- 1 | // NOTE: choose node.js first 2 | // process is defined in client test 3 | 4 | export const isBrowser = typeof window !== 'undefined' && 5 | (typeof process === 'undefined' || process.title === 'browser') 6 | 7 | export const PARTSIZE = 1024 * 1024 8 | -------------------------------------------------------------------------------- /upyun/create-req.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import { isBrowser } from './constants' 3 | import isPromise from 'is-promise' 4 | import url from 'url' 5 | 6 | const adapter = axios.defaults.adapter 7 | 8 | axios.defaults.adapter = (function () { 9 | // NOTE: in electron environment, support http and xhr both, use http adapter first 10 | if (isBrowser) { 11 | return adapter 12 | } 13 | 14 | const http = require('axios/lib/adapters/http') 15 | return http 16 | })() 17 | 18 | export default function (endpoint, service, getHeaderSign, {proxy} = {}) { 19 | const req = axios.create({ 20 | baseURL: endpoint + '/' + service.serviceName, 21 | maxRedirects: 0, 22 | proxy 23 | }) 24 | 25 | req.interceptors.request.use((config) => { 26 | let method = config.method.toUpperCase() 27 | let path = url.resolve('/', encodeURI(config.url || '')) 28 | 29 | if (path.indexOf(config.baseURL) === 0) { 30 | path = path.substring(config.baseURL.length) 31 | } 32 | config.url = path 33 | let headerSign = getHeaderSign(service, method, path, config.headers['Content-MD5']) 34 | headerSign = isPromise(headerSign) ? headerSign : Promise.resolve(headerSign) 35 | 36 | return headerSign.then((headers) => { 37 | config.headers.common = headers 38 | return Promise.resolve(config) 39 | }) 40 | }, error => { 41 | throw new Error('upyun - request failed: ' + error.message) 42 | }) 43 | 44 | req.interceptors.response.use( 45 | response => response, 46 | error => { 47 | const {response} = error 48 | if (typeof response === 'undefined') { 49 | throw error 50 | } 51 | 52 | if (response.status !== 404) { 53 | let err = new Error('upyun - response error: ' + error.message) 54 | if (error.response.data && error.response.data.code) { 55 | err.code = error.response.data.code 56 | } 57 | throw err 58 | } else { 59 | return response 60 | } 61 | } 62 | ) 63 | return req 64 | } 65 | -------------------------------------------------------------------------------- /upyun/form-upload.js: -------------------------------------------------------------------------------- 1 | import FormData from 'form-data' 2 | import path from 'path' 3 | 4 | export default function formUpload (remoteUrl, localFile, {authorization, policy}, {filename} = {}) { 5 | return new Promise((resolve, reject) => { 6 | const data = new FormData() 7 | data.append('authorization', authorization) 8 | data.append('policy', policy) 9 | // NOTE when type of localFile is buffer/string, 10 | // force set filename=file, FormData will treat it as a file 11 | // real filename will be set by save-key in policy 12 | filename = (filename || localFile.name || localFile.path) ? 13 | path.basename(filename || localFile.name || localFile.path) : 14 | 'file' 15 | 16 | data.append('file', localFile, { 17 | filename: filename 18 | }) 19 | data.submit(remoteUrl, (err, res) => { 20 | if (err) { 21 | return reject(err) 22 | } 23 | 24 | if (res.statusCode !== 200) { 25 | return resolve(false) 26 | } 27 | 28 | let body = [] 29 | res.on('data', (chunk) => { 30 | body.push(chunk) 31 | }) 32 | res.on('end', () => { 33 | body = Buffer.concat(body).toString('utf8') 34 | try { 35 | const data = JSON.parse(body) 36 | return resolve(data) 37 | } catch (err) { 38 | return reject(err) 39 | } 40 | }) 41 | 42 | res.on('error', (err) => { 43 | reject(err) 44 | }) 45 | }) 46 | }) 47 | } 48 | -------------------------------------------------------------------------------- /upyun/service.js: -------------------------------------------------------------------------------- 1 | import md5 from 'md5' 2 | 3 | /** 4 | * @class 5 | */ 6 | export default class Service { 7 | constructor (serviceName, operatorName = '', password = '') { 8 | // NOTE bucketName will be removed 9 | this.bucketName = serviceName 10 | this.serviceName = this.bucketName 11 | this.operatorName = operatorName 12 | this.password = md5(password) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /upyun/sign.js: -------------------------------------------------------------------------------- 1 | import hmacsha1 from 'hmacsha1' 2 | import base64 from 'base-64' 3 | import pkg from '../package.json' 4 | import md5 from 'md5' 5 | 6 | /** 7 | * generate head sign for rest api 8 | * {@link http://docs.upyun.com/api/authorization/#_2} 9 | * @param {object} service 10 | * @param {string} path - storage path on upyun server, e.g: /your/dir/example.txt 11 | * @param {string} contentMd5 - md5 of the file that will be uploaded 12 | */ 13 | export function getHeaderSign (service, method, path, contentMd5 = null) { 14 | const date = new Date().toGMTString() 15 | path = '/' + service.serviceName + path 16 | const sign = genSign(service, { 17 | method, 18 | path, 19 | date, 20 | contentMd5 21 | }) 22 | return { 23 | 'Authorization': sign, 24 | 'X-Date': date, 25 | } 26 | } 27 | 28 | /** 29 | * generate signature string which can be used in head sign or body sign 30 | * {@link http://docs.upyun.com/api/authorization/#_2} 31 | * @param {object} service 32 | * @param {object} options - must include key is method, path 33 | */ 34 | export function genSign (service, options) { 35 | const {method, path} = options 36 | 37 | const data = [ 38 | method, 39 | path 40 | ]; 41 | 42 | // optional params 43 | ['date', 'policy', 'contentMd5'].forEach(item => { 44 | if (options[item]) { 45 | data.push(options[item]) 46 | } 47 | }) 48 | 49 | // hmacsha1 return base64 encoded string 50 | const sign = hmacsha1(service.password, data.join('&')) 51 | return `UPYUN ${service.operatorName}:${sign}` 52 | } 53 | 54 | /** 55 | * get policy and authorization for form api 56 | * @param {object} service 57 | * @param {object} - other optional params @see http://docs.upyun.com/api/form_api/#_2 58 | */ 59 | export function getPolicyAndAuthorization (service, params) { 60 | params['service'] = service.serviceName 61 | if (typeof params['save-key'] === 'undefined') { 62 | throw new Error('upyun - calclate body sign need save-key') 63 | } 64 | 65 | if (typeof params['expiration'] === 'undefined') { 66 | // default 30 minutes 67 | params['expiration'] = parseInt(new Date() / 1000 + 30 * 60, 10) 68 | } 69 | 70 | const policy = base64.encode(JSON.stringify(params)) 71 | const authorization = genSign(service, { 72 | method: 'POST', 73 | path: '/' + service.serviceName, 74 | policy, 75 | contentMd5: params['content-md5'] 76 | }) 77 | return { 78 | policy, 79 | authorization 80 | } 81 | } 82 | 83 | /** 84 | * get Authorization and Date for purge api 85 | * {@link http://docs.upyun.com/api/purge/#_1} 86 | * 87 | * @param {!object} service 88 | * @param {!string[]} urls 89 | * 90 | */ 91 | export function getPurgeHeaderSign (service, urls) { 92 | const date = new Date().toGMTString() 93 | const str = urls.join('\n') 94 | const sign = md5(`${str}&${service.serviceName}&${date}&${service.password}`) 95 | 96 | return { 97 | 'Authorization': `UpYun ${service.serviceName}:${service.operatorName}:${sign}`, 98 | 'Date': date, 99 | 'User-Agent': 'Js-Sdk/' + pkg.version 100 | } 101 | } 102 | 103 | export default { 104 | genSign, 105 | getHeaderSign, 106 | getPolicyAndAuthorization, 107 | getPurgeHeaderSign 108 | } 109 | -------------------------------------------------------------------------------- /upyun/upyun.js: -------------------------------------------------------------------------------- 1 | import createReq from './create-req' 2 | import utils from './utils' 3 | import formUpload from './form-upload' 4 | import axios from 'axios' 5 | import sign from './sign' 6 | import { isBrowser, PARTSIZE } from './constants' 7 | import isPromise from 'is-promise' 8 | import path from 'path' 9 | 10 | /** 11 | * @class 12 | */ 13 | export default class Upyun { 14 | /** 15 | * @param {object} service - a instance of Service class 16 | * @param {object} params - optional params 17 | * @param {callback} getHeaderSign - callback function to get header sign 18 | */ 19 | constructor (service, params = {}, getHeaderSign = null) { 20 | if (typeof service.serviceName === 'undefined') { 21 | throw new Error('upyun - must config serviceName') 22 | } 23 | 24 | if (typeof params === 'function') { 25 | getHeaderSign = params 26 | params = {} 27 | } 28 | 29 | if (typeof getHeaderSign !== 'function' && isBrowser) { 30 | throw new Error('upyun - must config a callback function getHeaderSign in client side') 31 | } 32 | 33 | if (!isBrowser && ( 34 | typeof service.operatorName === 'undefined' || 35 | typeof service.password === 'undefined' 36 | )) { 37 | throw new Error('upyun - must config operateName and password in server side') 38 | } 39 | 40 | const config = Object.assign({ 41 | domain: 'v0.api.upyun.com', 42 | protocol: 'https', 43 | // proxy: false // 禁用代理 // 参考 axios 配置. 如: {host: '127.0.0.1', post: 1081} 44 | }, params) 45 | 46 | this.endpoint = config.protocol + '://' + config.domain 47 | const {proxy} = config 48 | this.proxy = proxy 49 | this.req = createReq(this.endpoint, service, getHeaderSign || defaultGetHeaderSign, {proxy}) 50 | // NOTE this will be removed 51 | this.bucket = service 52 | this.service = service 53 | if (!isBrowser) { 54 | this.setBodySignCallback(sign.getPolicyAndAuthorization) 55 | } 56 | } 57 | 58 | setService (service) { 59 | this.service = service 60 | this.req.defaults.baseURL = this.endpoint + '/' + service.serviceName 61 | } 62 | 63 | // NOTE this will be removed 64 | setBucket (bucket) { 65 | return this.setService(bucket) 66 | } 67 | 68 | setBodySignCallback (getBodySign) { 69 | if (typeof getBodySign !== 'function') { 70 | throw new Error('upyun - getBodySign should be a function') 71 | } 72 | this.bodySignCallback = getBodySign 73 | } 74 | 75 | usage (dir = '/') { 76 | return this.req.get(dir + '?usage').then(({data}) => { 77 | return Promise.resolve(data) 78 | }) 79 | } 80 | 81 | listDir (dir = '/', {limit = 100, order = 'asc', iter = ''} = {}) { 82 | const requestHeaders = {} 83 | 84 | // NOTE: 默认值可以省去请求头设置,避免跨域影响 85 | if (limit !== 100) { 86 | requestHeaders['x-list-limit'] = limit 87 | } 88 | 89 | if (order !== 'asc') { 90 | requestHeaders['x-list-order'] = order 91 | } 92 | 93 | if (iter) { 94 | requestHeaders['x-list-iter'] = iter 95 | } 96 | 97 | return this.req.get(dir, { 98 | headers: requestHeaders 99 | }).then(({data, headers, status}) => { 100 | if (status === 404) { 101 | return false 102 | } 103 | 104 | const next = headers['x-upyun-list-iter'] 105 | if (!data) { 106 | return Promise.resolve({ 107 | files: [], 108 | next 109 | }) 110 | } 111 | 112 | const items = data.split('\n') 113 | const files = items.map(item => { 114 | const [name, type, size, time] = item.split('\t') 115 | return { 116 | name, 117 | type, 118 | size: parseInt(size), 119 | time: parseInt(time) 120 | } 121 | }) 122 | 123 | return Promise.resolve({ 124 | files, 125 | next 126 | }) 127 | }) 128 | } 129 | 130 | /** 131 | * @param localFile: file content, available type is Stream | String | Buffer for server; File | String for client 132 | * @see https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/send 133 | * @see https://github.com/mzabriskie/axios/blob/master/lib/adapters/http.js#L32 134 | */ 135 | putFile (remotePath, localFile, options = {}) { 136 | // optional params 137 | const keys = ['Content-MD5', 'Content-Length', 'Content-Type', 'Content-Secret', 'x-gmkerl-thumb'] 138 | let headers = {} 139 | const optionsLower = {} 140 | for (const key of Object.keys(options)) { 141 | optionsLower[key.toLowerCase()] = options[key] 142 | } 143 | 144 | for (const key of Object.keys(optionsLower)) { 145 | if (isMeta(key) && optionsLower[key]) { 146 | headers[key] = optionsLower[key] 147 | } else { 148 | keys.forEach(key => { 149 | const lower = key.toLowerCase() 150 | const finded = optionsLower[lower] 151 | if (finded) { 152 | headers[key] = finded 153 | } 154 | }) 155 | } 156 | } 157 | 158 | return this.req.put(remotePath, localFile, { 159 | headers 160 | }).then(({headers: responseHeaders, status}) => { 161 | if (status !== 200) { 162 | return Promise.resolve(false) 163 | } 164 | 165 | let params = ['x-upyun-width', 'x-upyun-height', 'x-upyun-file-type', 'x-upyun-frames'] 166 | let result = {} 167 | params.forEach(item => { 168 | let key = item.split('x-upyun-')[1] 169 | if (responseHeaders[item]) { 170 | result[key] = responseHeaders[item] 171 | if (key !== 'file-type') { 172 | result[key] = parseInt(result[key], 10) 173 | } 174 | } 175 | }) 176 | return Promise.resolve(Object.keys(result).length > 0 ? result : true) 177 | }) 178 | } 179 | 180 | initMultipartUpload (remotePath, fileOrPath, options = {}) { 181 | let fileSizePromise 182 | const lowerOptions = key2LowerCase(options) 183 | let contentType = lowerOptions['x-upyun-multi-type'] 184 | 185 | if (isBrowser) { 186 | fileSizePromise = Promise.resolve(fileOrPath.size) 187 | contentType = contentType || fileOrPath.type 188 | } else { 189 | if (Buffer.isBuffer(fileOrPath)) { 190 | fileSizePromise = Promise.resolve(fileOrPath.length) 191 | contentType = contentType || 'application/octet-stream' 192 | } else { 193 | fileSizePromise = utils.getFileSizeAsync(fileOrPath) 194 | contentType = contentType || utils.getContentType(fileOrPath) 195 | } 196 | } 197 | 198 | return fileSizePromise.then((fileSize) => { 199 | Object.assign(lowerOptions, { 200 | 'x-upyun-multi-disorder': true, 201 | 'x-upyun-multi-stage': 'initiate', 202 | 'x-upyun-multi-length': fileSize, 203 | 'x-upyun-multi-type': contentType 204 | }) 205 | 206 | return this.req.put(remotePath, null, { 207 | headers: lowerOptions 208 | }).then(({headers, status}) => { 209 | if (status !== 204) { 210 | return Promise.resolve(false) 211 | } 212 | 213 | let uuid = headers['x-upyun-multi-uuid'] 214 | 215 | return Promise.resolve({ 216 | fileSize, 217 | partCount: Math.ceil(fileSize / PARTSIZE), 218 | uuid 219 | }) 220 | }) 221 | }) 222 | } 223 | 224 | multipartUpload (remotePath, fileOrPath, multiUuid, partId) { 225 | const start = partId * PARTSIZE 226 | let fileSizePromise 227 | // let contentType 228 | 229 | if (isBrowser) { 230 | fileSizePromise = Promise.resolve(fileOrPath.size) 231 | // contentType = fileOrPath.type 232 | } else { 233 | if (Buffer.isBuffer(fileOrPath)) { 234 | fileSizePromise = Promise.resolve(fileOrPath.length) 235 | } else { 236 | fileSizePromise = utils.getFileSizeAsync(fileOrPath) 237 | // contentType = utils.getContentType(fileOrPath) 238 | } 239 | } 240 | 241 | const blockPromise = fileSizePromise.then((fileSize) => { 242 | const end = Math.min(start + PARTSIZE, fileSize) 243 | 244 | return Buffer.isBuffer(fileOrPath) ? fileOrPath.slice(start, end) : utils.readBlockAsync(fileOrPath, start, end) 245 | }) 246 | 247 | return blockPromise.then((block) => { 248 | return this.req.put(remotePath, block, { 249 | headers: { 250 | 'x-upyun-multi-stage': 'upload', 251 | 'x-upyun-multi-uuid': multiUuid, 252 | 'x-upyun-part-id': partId, 253 | } 254 | }).then(({status}) => { 255 | return Promise.resolve(status === 204) 256 | }) 257 | }) 258 | } 259 | 260 | completeMultipartUpload (remotePath, multiUuid) { 261 | return this.req.put(remotePath, null, { 262 | headers: { 263 | 'x-upyun-multi-stage': 'complete', 264 | 'x-upyun-multi-uuid': multiUuid 265 | } 266 | }).then(({status}) => { 267 | return Promise.resolve(status === 204 || status === 201) 268 | }) 269 | } 270 | 271 | makeDir (remotePath) { 272 | return this.req.post(remotePath, null, { 273 | headers: { folder: 'true' } 274 | }).then(({status}) => { 275 | return Promise.resolve(status === 200) 276 | }) 277 | } 278 | 279 | /** 280 | * copy file 281 | * 282 | * {@link https://help.upyun.com/knowledge-base/rest_api/#e5a48de588b6e69687e4bbb6 } 283 | * 284 | * @param {!string} targetPath 285 | * @param {!string} sourcePath 286 | * @param {?object} options={} - 可传入参数 `x-upyun-metadata-directive`, `content-md5`, `content-length` 287 | */ 288 | copy (targetPath, sourcePath, options = {}) { 289 | const lowerOptions = key2LowerCase(options) 290 | 291 | const headers = Object.assign(lowerOptions, { 292 | 'x-upyun-copy-source': path.join('/', this.service.serviceName, sourcePath), 293 | }) 294 | 295 | return this.req.put(targetPath, null, { 296 | headers: headers, 297 | }).then(({status}) => { 298 | return Promise.resolve((status >= 200 && status < 300)) 299 | }) 300 | } 301 | 302 | /** 303 | * move file 304 | * 305 | * {@link https://help.upyun.com/knowledge-base/rest_api/#e7a7bbe58aa8e69687e4bbb6 } 306 | * 307 | * @param {!string} targetPath 308 | * @param {!string} sourcePath 309 | * @param {?object} options={} - 可传入参数 `x-upyun-metadata-directive`, `content-md5`, `content-length` 310 | */ 311 | move (targetPath, sourcePath, options = {}) { 312 | const lowerOptions = key2LowerCase(options) 313 | 314 | const headers = Object.assign(lowerOptions, { 315 | 'x-upyun-move-source': path.join('/', this.service.serviceName, sourcePath), 316 | }) 317 | 318 | return this.req.put(targetPath, null, { 319 | headers: headers, 320 | }).then(({status}) => { 321 | return Promise.resolve((status >= 200 && status < 300)) 322 | }) 323 | } 324 | 325 | headFile (remotePath) { 326 | return this.req.head(remotePath).then(({headers, status}) => { 327 | if (status === 404) { 328 | return Promise.resolve(false) 329 | } 330 | 331 | let params = ['x-upyun-file-type', 'x-upyun-file-size', 'x-upyun-file-date'] 332 | let result = { 333 | 'Content-Md5': headers['content-md5'] || '' 334 | } 335 | 336 | params.forEach(item => { 337 | let key = item.split('x-upyun-file-')[1] 338 | if (headers[item]) { 339 | result[key] = headers[item] 340 | if (key === 'size' || key === 'date') { 341 | result[key] = parseInt(result[key], 10) 342 | } 343 | } 344 | }) 345 | return Promise.resolve(result) 346 | }) 347 | } 348 | 349 | deleteFile (remotePath, isAsync = false) { 350 | const headers = {} 351 | if (isAsync) { 352 | headers['x-upyun-async'] = true 353 | } 354 | return this.req.delete(remotePath, { 355 | headers 356 | }).then(({status}) => { 357 | return Promise.resolve(status === 200) 358 | }) 359 | } 360 | 361 | deleteDir (...args) { 362 | return this.deleteFile.apply(this, args) 363 | } 364 | 365 | getFile (remotePath, saveStream = null) { 366 | if (saveStream && isBrowser) { 367 | throw new Error('upyun - save as stream are only available on the server side.') 368 | } 369 | 370 | return this.req({ 371 | method: 'GET', 372 | url: remotePath, 373 | responseType: saveStream ? 'stream' : null 374 | }).then((response) => { 375 | if (response.status === 404) { 376 | return Promise.resolve(false) 377 | } 378 | 379 | if (!saveStream) { 380 | return Promise.resolve(response.data) 381 | } 382 | 383 | const stream = response.data.pipe(saveStream) 384 | 385 | return new Promise((resolve, reject) => { 386 | stream.on('finish', () => resolve(stream)) 387 | 388 | stream.on('error', reject) 389 | }) 390 | }) 391 | } 392 | 393 | updateMetadata (remotePath, metas, operate = 'merge') { 394 | let metaHeaders = {} 395 | for (let key in metas) { 396 | if (!isMeta(key)) { 397 | metaHeaders['x-upyun-meta-' + key] = metas[key] 398 | } else { 399 | metaHeaders[key] = metas 400 | } 401 | } 402 | 403 | return this.req.patch( 404 | remotePath + '?metadata=' + operate, 405 | null, 406 | { headers: metaHeaders } 407 | ).then(({status}) => { 408 | return Promise.resolve(status === 200) 409 | }) 410 | } 411 | 412 | // be careful: this will download the entire file 413 | getMetadata (remotePath) { 414 | return this.req.get(remotePath).then(({headers, status}) => { 415 | if (status !== 200) { 416 | return Promise.resolve(false) 417 | } 418 | 419 | let result = {} 420 | for (let key in headers) { 421 | if (isMeta(key)) { 422 | result[key] = headers[key] 423 | } 424 | } 425 | 426 | return Promise.resolve(result) 427 | }) 428 | } 429 | 430 | /** 431 | * in browser: type of fileOrPath is File 432 | * in server: type of fileOrPath is string: local file path 433 | */ 434 | blockUpload (remotePath, fileOrPath, options = {}) { 435 | let fileSizePromise 436 | let contentType 437 | if (isBrowser) { 438 | fileSizePromise = Promise.resolve(fileOrPath.size) 439 | contentType = fileOrPath.type 440 | } else { 441 | fileSizePromise = utils.getFileSizeAsync(fileOrPath) 442 | contentType = utils.getContentType(fileOrPath) 443 | } 444 | 445 | return fileSizePromise.then((fileSize) => { 446 | Object.assign(options, { 447 | 'x-upyun-multi-stage': 'initiate', 448 | 'x-upyun-multi-length': fileSize, 449 | 'x-upyun-multi-type': contentType 450 | }) 451 | 452 | const blockSize = 1024 * 1024 453 | const blocks = Math.ceil(fileSize / blockSize) 454 | 455 | return this.req.put(remotePath, null, { 456 | headers: options 457 | }).then(({headers}) => { 458 | let uuid = headers['x-upyun-multi-uuid'] 459 | let nextId = headers['x-upyun-next-part-id'] 460 | 461 | let p = Promise.resolve(nextId) 462 | for (let index = 0; index < blocks; index++) { 463 | p = p.then((nextId) => { 464 | const start = nextId * blockSize 465 | const end = Math.min(start + blockSize, fileSize) 466 | const blockPromise = utils.readBlockAsync(fileOrPath, start, end) 467 | return blockPromise.then((block) => { 468 | return this.req.put(remotePath, block, { 469 | headers: { 470 | 'x-upyun-multi-stage': 'upload', 471 | 'x-upyun-multi-uuid': uuid, 472 | 'x-upyun-part-id': nextId 473 | } 474 | }).then(({headers}) => { 475 | nextId = headers['x-upyun-next-part-id'] 476 | return Promise.resolve(nextId) 477 | }) 478 | }) 479 | }) 480 | } 481 | 482 | return p.then(() => { 483 | return this.req.put(remotePath, null, { 484 | headers: { 485 | 'x-upyun-multi-stage': 'complete', 486 | 'x-upyun-multi-uuid': uuid 487 | } 488 | }).then(({status}) => { 489 | return Promise.resolve(status === 204 || status === 201) 490 | }) 491 | }) 492 | }) 493 | }) 494 | } 495 | 496 | formPutFile (remotePath, localFile, orignParams = {}, opts = {}) { 497 | const params = {} 498 | for (const key of Object.keys(orignParams)) { 499 | params[key.toLowerCase()] = orignParams[key] 500 | } 501 | 502 | if (typeof this.bodySignCallback !== 'function') { 503 | throw new Error('upyun - must setBodySignCallback first!') 504 | } 505 | 506 | params['service'] = this.service.serviceName 507 | params['save-key'] = remotePath 508 | let result = this.bodySignCallback(this.service, params) 509 | result = isPromise(result) ? result : Promise.resolve(result) 510 | 511 | return result.then((bodySign) => { 512 | return formUpload(this.endpoint + '/' + params['service'], localFile, bodySign, opts) 513 | }) 514 | } 515 | 516 | purge (urls) { 517 | if (typeof urls === 'string') { 518 | urls = [urls] 519 | } 520 | const headers = sign.getPurgeHeaderSign(this.service, urls) 521 | return axios.post( 522 | 'http://purge.upyun.com/purge/', 523 | 'purge=' + urls.join('\n'), { 524 | headers, 525 | proxy: this.proxy 526 | } 527 | ).then(({data}) => { 528 | if (Object.keys(data.invalid_domain_of_url).length === 0) { 529 | return true 530 | } else { 531 | throw new Error('some url purge failed ' + data.invalid_domain_of_url.join(' ')) 532 | } 533 | }, (err) => { 534 | throw new Error('upyun - request failed: ' + err.message) 535 | }) 536 | } 537 | } 538 | 539 | function isMeta (key) { 540 | return key.indexOf('x-upyun-meta-') === 0 541 | } 542 | 543 | function defaultGetHeaderSign () { 544 | return sign.getHeaderSign(...arguments) 545 | } 546 | 547 | function key2LowerCase (obj) { 548 | const objLower = {} 549 | for (const key of Object.keys(obj)) { 550 | objLower[key.toLowerCase()] = obj[key] 551 | } 552 | return objLower 553 | } 554 | -------------------------------------------------------------------------------- /upyun/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import fs from 'fs' 4 | import mime from 'mime-types' 5 | 6 | export function readBlockAsync (filePath, start, end) { 7 | const size = end - start 8 | const b = makeBuffer(size) 9 | return new Promise((resolve, reject) => { 10 | fs.open(filePath, 'r', (err, fd) => { 11 | if (err) { 12 | return reject(err) 13 | } 14 | 15 | fs.read(fd, b, 0, size, start, (err, bytesRead, buffer) => { 16 | if (err) { 17 | return reject(err) 18 | } 19 | 20 | return resolve(buffer) 21 | }) 22 | }) 23 | }) 24 | } 25 | 26 | function makeBuffer (size) { 27 | if (Buffer.alloc) { 28 | return Buffer.alloc(size) 29 | } else { 30 | const b = new Buffer(size) 31 | b.fill(0) 32 | return b 33 | } 34 | } 35 | 36 | export function getFileSizeAsync (filePath) { 37 | return new Promise((resolve, reject) => { 38 | fs.stat(filePath, (err, stat) => { 39 | if (err) return reject(err) 40 | 41 | return resolve(stat.size) 42 | }) 43 | }) 44 | } 45 | 46 | export function getContentType (filePath) { 47 | return mime.lookup(filePath) 48 | } 49 | 50 | export default { 51 | readBlockAsync, 52 | getFileSizeAsync, 53 | getContentType, 54 | } 55 | --------------------------------------------------------------------------------