alioss-web-uploader
Version:
Upload file or add object to Ali OSS in browser.
198 lines (177 loc) • 6.39 kB
text/typescript
import {escapeName, isHttpsProtocol, objectName} from "./utils";
import {rstr2b64, utf8Encode} from "./utils/hash/utils";
import request, {RequestOptions, RequestResult, UploadProgressEvent} from "./utils/request";
import {computeSignature} from "./utils/signUtils";
function getOssParams(params, extendKeys=[]){
const out = {};
Object.keys(params).forEach(key => {
const lowerKey = key.toLowerCase();
if (/^x-oss-/.test(lowerKey) || extendKeys.indexOf(key)>-1){
out[lowerKey] = params[key];
}
});
return out;
}
// @link https://help.aliyun.com/document_detail/31837.html?spm=a2c4g.11186623.2.15.169769cbIq4eNn#concept-zt4-cvy-5db
type Region = 'oss-cn-hangzhou' | 'oss-cn-shanghai' | 'oss-cn-qingdao' | 'oss-cn-beijing' | 'oss-cn-zhangjiakou'
| 'oss-cn-huhehaote' | 'oss-cn-shenzhen' | 'oss-cn-heyuan' | 'oss-cn-chengdu' | 'oss-cn-hongkong' | 'oss-us-west-1'
| 'oss-us-east-1' | 'oss-ap-southeast-1' | 'oss-ap-southeast-2' | 'oss-ap-southeast-3' | 'oss-ap-southeast-5'
| 'oss-ap-northeast-1' | 'oss-ap-south-1' | 'oss-eu-central-1' | 'oss-eu-west-1' | 'oss-me-east-1' | string;
export interface ClientOptions {
/**
* access key you create on aliyun console website
*/
accessKeyId: string;
/**
* access secret you create
*/
accessKeySecret: string;
bucket?: string;
/**
* used by temporary authorization
*/
stsToken?: string;
/**
* oss region domain. It takes priority over `region`
*/
endpoint?: string;
/**
* the bucket data region location, default is `oss-cn-hangzhou`
*/
region?: Region;
/**
* access OSS with aliyun internal network or not, default is false. If your servers are running on aliyun too,
* you can set true to save lot of money.
*/
internal?: boolean;
/**
* instance level timeout for all operations, default is 60_000(60s).
*/
timeout?: number;
secure?: boolean;
/**
* default false, access oss with custom domain name. if true, you can fill endpoint field with your custom domain name
*/
cname?: boolean;
// isRequestPay?: boolean;
}
export interface PostObjectOptions {
dir?: string;
policy?: string | {expiration: string; conditions: any[]};
signature?: string;
timeout?: number;
onProgress?: (e: UploadProgressEvent) => void;
onSuccess?: (result: RequestResult, xhr: XMLHttpRequest) => void;
onError?: (e: Error) => void;
onAbort?: () => void;
success_action_status?: 200|201|204;
success_action_redirect?: string;
'x-oss-object-acl'?: 'private' | 'public-read' | 'public-read-write';
headers?: {[key: string]: string | number},
[key: string]: any;
}
class Client {
protected opts: ClientOptions & { host: string };
constructor(options: ClientOptions) {
if (!(this instanceof Client)) {
return new Client(options);
}
const opts = {
endpoint: null,
region: 'oss-cn-hangzhou',
timeout: 300_000,
internal: false,
// cname: false,
secure: undefined,
...options,
host: '',
};
if (opts.endpoint) {
// if set custom endpoint
} else if (opts.region && opts.bucket) {
opts.endpoint = opts.bucket;
if (opts.internal) opts.region += '-internal';
opts.endpoint += `.${opts.region}.aliyuncs.com`
} else {
throw new Error('require endpoint or region/bucket in options');
}
// 一般情况下不需要设置 `secure` 参数,唯一需要用到的场景可能就是在 http 页面中使用 https 连接了
opts.host += `http${opts.secure === true || isHttpsProtocol() ? 's' : ''}://${opts.endpoint}`;
this.opts = opts;
}
/*
public putObject(name, file: File|BlobPart, options){
}
*/
/**
* post object as form/multi-part
* @param name
* @param file
* @param options
*/
public postObject(name: string, file: File | Blob, options: PostObjectOptions={}) {
let policyBase64;
const data = new FormData();
Object.keys(options.headers || {}).forEach(key => {
data.append(key, options.headers[key].toString());
});
const objectKey = objectName((options.dir || '').replace(/^(.+?)\/*$/, '$1/') + objectName(name));
data.append('key', objectKey);
if (this.opts.accessKeyId && this.opts.accessKeySecret){
if (typeof options.policy === 'string'){
policyBase64 = options.policy;
} else{
const policy = {
"expiration": new Date(+new Date() + 24 * 3600 * 1000).toISOString(),
"conditions": [
{"bucket": this.opts.bucket},
{"key": objectKey}, // equals to ["eq", "$key", objectName(name)],
["content-length-range", 0, 1024 * 1024 * 1024],
],
...options.policy,
};
policyBase64 = rstr2b64(utf8Encode(JSON.stringify(policy)));
}
const signature = options.signature || computeSignature(this.opts.accessKeySecret, policyBase64);
data.append('OSSAccessKeyId', this.opts.accessKeyId);
data.append('policy', policyBase64);
data.append('Signature', signature);
if (this.opts.stsToken){
data.append('x-oss-security-token', this.opts.stsToken);
}
}
const ossParam = getOssParams(options, ['success_action_status', 'success_action_redirect']);
Object.keys(ossParam).forEach(k => data.append(k, ossParam[k]));
data.append('file', file);
const emptyFunc = function () {};
const successCallback = options.onSuccess || emptyFunc;
const reqOptions: RequestOptions = {
method: 'POST',
data,
timeout: options.timeout || this.opts.timeout,
onSuccess: (result, xhr) => successCallback(result, xhr),
onError: options.onError || function (e) {
console.error(e);
},
onAbort: options.onAbort || emptyFunc,
// withCredentials: true,
};
if (options.onProgress) reqOptions.onProgress = options.onProgress;
return request(this.opts.host, reqOptions);
}
/**
* Get the Object url.
* If provide baseUrl, will use baseUrl instead the default bucket and endpoint .
* @param name
* @param baseUrl
*/
public generateObjectUrl(name: string, baseUrl?: string) {
if (!baseUrl) {
baseUrl = this.opts.host + '/';
} else if (baseUrl[baseUrl.length - 1] !== '/') {
baseUrl += '/';
}
return baseUrl + escapeName(objectName(name));
}
}
export default Client;