├── .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 | [](https://www.npmjs.org/package/upyun)
3 | [](https://travis-ci.org/upyun/node-sdk)
4 | [](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 |
--------------------------------------------------------------------------------