filecache
This commit is contained in:
+6
-3
@@ -7,9 +7,12 @@ const BASE_DOMAIN = 'www.axzc.xyz'
|
|||||||
// const CHAT_URL = `https://${BASE_DOMAIN}/chat`
|
// const CHAT_URL = `https://${BASE_DOMAIN}/chat`
|
||||||
// const API_URL = `https://${BASE_DOMAIN}/api`
|
// const API_URL = `https://${BASE_DOMAIN}/api`
|
||||||
// const WS_URL = `wss://${BASE_DOMAIN}/msg_gateway`
|
// const WS_URL = `wss://${BASE_DOMAIN}/msg_gateway`
|
||||||
const CHAT_URL = `http://${BASE_DOMAIN}/api`
|
// const CHAT_URL = `http://${BASE_DOMAIN}/api`
|
||||||
const API_URL = `http://${BASE_DOMAIN}/imapi`
|
// const API_URL = `http://${BASE_DOMAIN}/imapi`
|
||||||
const WS_URL = `ws://${BASE_DOMAIN}/ws`
|
// const WS_URL = `ws://${BASE_DOMAIN}/ws`
|
||||||
|
const CHAT_URL = `http://103.39.222.184:8585/api`
|
||||||
|
const API_URL = `http://103.39.222.184:10002`
|
||||||
|
const WS_URL = `ws://103.39.222.184:10001`
|
||||||
|
|
||||||
const version = '2.0.6'
|
const version = '2.0.6'
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<u-avatar @longpress="longpress" @click="click" @onError="errorHandle" :src="getAvatarUrl" :text="avatarText"
|
<u-avatar @longpress="longpress" @click="click" @onError="errorHandle" :src="cachesrc" :text="avatarText"
|
||||||
bg-color="#cdcdcd" :defaultUrl="getDdefaultUrl" :shape="shape" :size="size" mode="aspectFill" font-size="14">
|
bg-color="#cdcdcd" :defaultUrl="getDdefaultUrl" :shape="shape" :size="size" mode="aspectFill" font-size="14">
|
||||||
</u-avatar>
|
</u-avatar>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import defaultGroupIcon from "@/static/images/contact_my_group.png";
|
import defaultGroupIcon from "@/static/images/contact_my_group.png";
|
||||||
|
import defaultUserIcon from "@/static/images/user/avatar.png";
|
||||||
import defaultNotifyIcon from "@/static/images/default_notify_icon.png";
|
import defaultNotifyIcon from "@/static/images/default_notify_icon.png";
|
||||||
import util from "@/util";
|
import util from "@/util";
|
||||||
|
import md5 from "md5";
|
||||||
export default {
|
export default {
|
||||||
name: "MyAvatar",
|
name: "MyAvatar",
|
||||||
props: {
|
props: {
|
||||||
@@ -34,26 +36,37 @@
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
avatarText: undefined,
|
avatarText: undefined,
|
||||||
|
cachesrc:"",
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
getAvatarUrl() {
|
|
||||||
if (this.src) {
|
|
||||||
return util.cdn(this.src);
|
|
||||||
}
|
|
||||||
if (this.isGroup) {
|
|
||||||
return defaultGroupIcon;
|
|
||||||
}
|
|
||||||
if (this.isNotify) {
|
|
||||||
return defaultNotifyIcon;
|
|
||||||
}
|
|
||||||
this.avatarText = this.desc ? this.desc.slice(0, 1) : "未知";
|
|
||||||
return "";
|
|
||||||
},
|
|
||||||
getDdefaultUrl() {
|
getDdefaultUrl() {
|
||||||
return this.isGroup ? defaultGroupIcon : undefined;
|
return this.isGroup ? defaultGroupIcon : defaultUserIcon;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
created() {
|
||||||
|
//_this.cachesrc = plus.io.convertAbsoluteFileSystem();
|
||||||
|
const _this = this;
|
||||||
|
if (this.src) {
|
||||||
|
util.cacheFile(util.cdn(this.src),'avatar').then(res=>{
|
||||||
|
_this.avatarText=""
|
||||||
|
_this.cachesrc = res;
|
||||||
|
//_this.cachesrc = plus.io.convertAbsoluteFileSystem(res);
|
||||||
|
//console.log(_this.cachesrc);
|
||||||
|
});
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
if (this.isGroup) {
|
||||||
|
_this.cachesrc = defaultGroupIcon;
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
if (this.isNotify) {
|
||||||
|
_this.cachesrc = defaultNotifyIcon;
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
this.avatarText = this.desc ? this.desc.slice(0, 1) : "未知";
|
||||||
|
return "";
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
errorHandle() {
|
errorHandle() {
|
||||||
this.avatarText = this.desc ? this.desc.slice(0, 1) : "未知";
|
this.avatarText = this.desc ? this.desc.slice(0, 1) : "未知";
|
||||||
@@ -70,8 +83,9 @@
|
|||||||
this.$emit("longpress");
|
this.$emit("longpress");
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
src() {
|
src(nv,ov) {
|
||||||
this.redirectShow();
|
this.redirectShow();
|
||||||
},
|
},
|
||||||
desc() {
|
desc() {
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ const app = new Vue({
|
|||||||
...App,
|
...App,
|
||||||
});
|
});
|
||||||
app.REQUEST_TRACE = process.env.NODE_ENV == 'development';
|
app.REQUEST_TRACE = process.env.NODE_ENV == 'development';
|
||||||
app.REQUEST_TRACE = false;
|
//app.REQUEST_TRACE = false;
|
||||||
|
|
||||||
// 引入请求封装
|
// 引入请求封装
|
||||||
import request from "./util/request/index";
|
import request from "./util/request/index";
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<view class="page_container">
|
<view class="page_container">
|
||||||
<view class="login">
|
<view class="login">
|
||||||
<view class="logo">
|
<view class="logo">
|
||||||
<img src="static/images/about_logo.png" alt="" />
|
<img :src="cdn(config.app_logo)" alt="" />
|
||||||
<view class="title">欢迎使用{{ config.name }}</view>
|
<view class="title">欢迎使用{{ config.name }}</view>
|
||||||
</view>
|
</view>
|
||||||
<u-tabs v-if="1 == 2" :list="list" :current="active" @click="click"></u-tabs>
|
<u-tabs v-if="1 == 2" :list="list" :current="active" @click="click"></u-tabs>
|
||||||
@@ -67,6 +67,7 @@ import AreaPicker from "@/components/AreaPicker";
|
|||||||
import { checkLoginError } from "@/util/common";
|
import { checkLoginError } from "@/util/common";
|
||||||
import { SmsUserFor } from "@/constant";
|
import { SmsUserFor } from "@/constant";
|
||||||
import IMSDK from "openim-uniapp-polyfill";
|
import IMSDK from "openim-uniapp-polyfill";
|
||||||
|
import util from "@/util/index.js"
|
||||||
|
|
||||||
let timer;
|
let timer;
|
||||||
|
|
||||||
@@ -119,6 +120,7 @@ export default {
|
|||||||
this.init();
|
this.init();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
...util,
|
||||||
click({ index }) {
|
click({ index }) {
|
||||||
this.active = index;
|
this.active = index;
|
||||||
},
|
},
|
||||||
@@ -148,7 +150,7 @@ export default {
|
|||||||
this.eying = !this.eying;
|
this.eying = !this.eying;
|
||||||
},
|
},
|
||||||
toRegisterOrForget(isRegister) {
|
toRegisterOrForget(isRegister) {
|
||||||
uni.$u.route("/pages/login/common/index", {
|
uni.$u.route("/pages/common/login/index", {
|
||||||
isRegister,
|
isRegister,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -279,7 +281,7 @@ export default {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: flex-start;
|
align-items: center;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
width: 160rpx;
|
width: 160rpx;
|
||||||
|
|||||||
@@ -23,13 +23,16 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<canvas canvas-id="qrcode_canvas" id="qrcode_canvas" style="width: 600rpx; height: 600rpx"></canvas>
|
<view id="qrcode_canvas_container" style="width: 600rpx; height: 600rpx">
|
||||||
|
<canvas canvas-id="qrcode_canvas" id="qrcode_canvas" style="width: 600rpx; height: 600rpx"></canvas>
|
||||||
|
</view>
|
||||||
|
<u-gap></u-gap>
|
||||||
<view style="color: #b4b4b4;">扫一扫上面的二维码图案,加我为朋友</view>
|
<view style="color: #b4b4b4;">扫一扫上面的二维码图案,加我为朋友</view>
|
||||||
</view>
|
</view>
|
||||||
<view style="width: 80%;display: flex;align-items: center;justify-content: center;height: 20%;">
|
<view style="width: 80%;display: flex;align-items: center;justify-content: center;height: 20%;">
|
||||||
<u-button type="default" plain :hairline="false" iconColor="#9aa2b2" @click="scan">扫一扫</u-button>
|
<u-button @click="scan" type="default" plain :hairline="false" color="#506388" iconColor="#9aa2b2">扫一扫</u-button>
|
||||||
<u-button type="default" plain :hairline="false" iconColor="#9aa2b2">换个样式</u-button>
|
<u-button @click="createQrcode" type="default" plain :hairline="false" color="#506388" iconColor="#9aa2b2">换个样式</u-button>
|
||||||
<u-button @click="save" type="default" plain :hairline="false" iconColor="#9aa2b2">保存图片</u-button>
|
<u-button @click="save" type="default" plain :hairline="false" color="#506388" iconColor="#9aa2b2">保存图片</u-button>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
@@ -40,7 +43,6 @@
|
|||||||
import UserBase from '@/components/User.vue';
|
import UserBase from '@/components/User.vue';
|
||||||
import util from "@/util";
|
import util from "@/util";
|
||||||
import { mapGetters } from "vuex";
|
import { mapGetters } from "vuex";
|
||||||
import md5 from "md5";
|
|
||||||
export default {
|
export default {
|
||||||
mixins:[UserBase],
|
mixins:[UserBase],
|
||||||
components: {
|
components: {
|
||||||
@@ -55,12 +57,21 @@
|
|||||||
showName:"",
|
showName:"",
|
||||||
faceURL:"",
|
faceURL:"",
|
||||||
code:"",
|
code:"",
|
||||||
}
|
},
|
||||||
|
qrcodeSize:"360",
|
||||||
|
qrcodeStyle:[]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed:{
|
computed:{
|
||||||
...mapGetters(["storeFriendList","storeGroupList","config"]),
|
...mapGetters(["storeFriendList","storeGroupList","config"]),
|
||||||
},
|
},
|
||||||
|
watch:{
|
||||||
|
qrcodeSize(nv,ov){
|
||||||
|
if(nv && this.qrcodeUrl){
|
||||||
|
this.createQrcode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
onLoad(opt) {
|
onLoad(opt) {
|
||||||
if(opt.sourceInfo){
|
if(opt.sourceInfo){
|
||||||
this.source = JSON.parse(opt.sourceInfo);
|
this.source = JSON.parse(opt.sourceInfo);
|
||||||
@@ -72,14 +83,36 @@
|
|||||||
code:"",
|
code:"",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
console.log(this.config);
|
|
||||||
if(this.source.type == "user"){
|
if(this.source.type == "user"){
|
||||||
this.qrcodeUrl = `${this.config.website}/u/${this.source.code}`;
|
this.qrcodeUrl = `${this.config.website}/u/${this.source.code}`;
|
||||||
}else{
|
}else{
|
||||||
this.qrcodeUrl = `${this.config.website}/g/${this.source.code}`;
|
this.qrcodeUrl = `${this.config.website}/g/${this.source.code}`;
|
||||||
}
|
}
|
||||||
console.log(this.qrcodeUrl);
|
this.qrcodeStyle.push({
|
||||||
this.createQrcode();
|
background: "#fff", // 背景色
|
||||||
|
foreground: '#000000', // 前景色
|
||||||
|
pdground: '#000000', // 定位角点颜色
|
||||||
|
correctLevel: 3, // 容错级别
|
||||||
|
image: this.config.app_logo, // 二维码图标
|
||||||
|
imageSize: 40, // 二维码图标大小
|
||||||
|
});
|
||||||
|
this.qrcodeStyle.push({
|
||||||
|
background: "#fff", // 背景色
|
||||||
|
foreground: '#000000', // 前景色
|
||||||
|
pdground: '#000000', // 定位角点颜色
|
||||||
|
correctLevel: 3, // 容错级别
|
||||||
|
image: this.source.faceURL, // 二维码图标
|
||||||
|
imageSize: 40, // 二维码图标大小
|
||||||
|
});
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
const _this = this;
|
||||||
|
uni.createSelectorQuery().in(this).select("#qrcode_canvas_container")
|
||||||
|
.boundingClientRect((data) => {
|
||||||
|
_this.qrcodeSize = data.width
|
||||||
|
|
||||||
|
})
|
||||||
|
.exec();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...util,
|
...util,
|
||||||
@@ -98,26 +131,47 @@
|
|||||||
},
|
},
|
||||||
createQrcode() {
|
createQrcode() {
|
||||||
const _this = this;
|
const _this = this;
|
||||||
|
const style = this.qrcodeStyle[Math.floor(Math.random() * this.qrcodeStyle.length)];
|
||||||
|
style.imageSize = parseInt(this.qrcodeSize * 0.2);
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
new QRCode({
|
var createFn = (icon)=>{
|
||||||
context: _this, // 上下文环境
|
console.log(icon)
|
||||||
canvasId: 'qrcode_canvas', // canvas-id
|
new QRCode({
|
||||||
usingComponents: true, // 是否是自定义组件
|
context: _this, // 上下文环境
|
||||||
showLoading: false, // 是否显示loading
|
canvasId: 'qrcode_canvas', // canvas-id
|
||||||
loadingText: "", // loading文字
|
usingComponents: true, // 是否是自定义组件
|
||||||
text: `${this.qrcodeUrl}`, // 生成内容
|
showLoading: false, // 是否显示loading
|
||||||
size: 320, // 二维码大小
|
loadingText: "", // loading文字
|
||||||
background: "#fff", // 背景色
|
text: `${_this.qrcodeUrl}`, // 生成内容
|
||||||
foreground: '#000000', // 前景色
|
size: _this.qrcodeSize, // 二维码大小
|
||||||
pdground: '#000000', // 定位角点颜色
|
background: style.background, // 背景色
|
||||||
correctLevel: 3, // 容错级别
|
foreground: style.foreground, // 前景色
|
||||||
image: "", // 二维码图标
|
pdground: style.pdground, // 定位角点颜色
|
||||||
imageSize: 40, // 二维码图标大小
|
correctLevel: 3, // 容错级别
|
||||||
cbResult: function(res) { // 生成二维码的回调
|
image: icon || "", // 二维码图标
|
||||||
_this.qrcode_src = (res)
|
imageSize: style.imageSize || 40, // 二维码图标大小
|
||||||
//resolve(res);
|
cbResult: function(res) { // 生成二维码的回调
|
||||||
},
|
_this.qrcode_src = (res)
|
||||||
});
|
//resolve(res);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!style.image){
|
||||||
|
createFn();
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
if(style.image.startsWith("/static/images")){
|
||||||
|
createFn(style.image);
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
if(!style.image.startsWith("http")){
|
||||||
|
style.image = util.cdn(style.image);
|
||||||
|
}
|
||||||
|
util.cacheFile(style.image,'avatar').then(fn=>{
|
||||||
|
createFn(fn);
|
||||||
|
});
|
||||||
|
return ;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,7 @@
|
|||||||
onReady() {},
|
onReady() {},
|
||||||
methods: {
|
methods: {
|
||||||
back() {
|
back() {
|
||||||
uni.$u.route("/pages/login/common/index", {
|
uni.$u.route("/pages/common/login/index", {
|
||||||
isRegister: this.isRegister,
|
isRegister: this.isRegister,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -156,7 +156,7 @@
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(({data,errCode,errMsg}) => {
|
.catch(({data,errCode,errMsg}) => {
|
||||||
console.log(errCode,errMsg);
|
console.log(errCode,errMsg,data);
|
||||||
uni.$u.toast(errMsg);
|
uni.$u.toast(errMsg);
|
||||||
this.updateOneMessage({
|
this.updateOneMessage({
|
||||||
message: data,
|
message: data,
|
||||||
|
|||||||
@@ -31,11 +31,7 @@
|
|||||||
return {
|
return {
|
||||||
loadingWidth: "120px",
|
loadingWidth: "120px",
|
||||||
maxHeight:'120px',
|
maxHeight:'120px',
|
||||||
src:"",
|
src:""
|
||||||
coverCachePath:"",
|
|
||||||
coverDownloading:false,
|
|
||||||
coverExists:false,
|
|
||||||
coverDownloadProgress:"",
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -52,40 +48,11 @@
|
|||||||
methods: {
|
methods: {
|
||||||
async init(){
|
async init(){
|
||||||
const self = this;
|
const self = this;
|
||||||
let url = "";
|
|
||||||
// 如果有远程 snapshotUrl,则下载到 coverCachePath
|
|
||||||
const snapshotUrl = this.message.FaceElem.data;
|
const snapshotUrl = this.message.FaceElem.data;
|
||||||
const key = md5(snapshotUrl || '');
|
// 检查封面是否存在
|
||||||
this.coverCachePath = `_doc/${this.conversationID}/face_${key}.jpg`;
|
util.cacheFile(snapshotUrl,'face').then((fn)=>{
|
||||||
if (typeof plus === 'undefined' || !this.coverCachePath) return;
|
self.src = fn;
|
||||||
try {
|
});
|
||||||
// 检查封面是否存在
|
|
||||||
const coverExists = await util.fileExsit(self.coverCachePath);
|
|
||||||
this.coverExists = !!coverExists;
|
|
||||||
if (this.coverExists) {
|
|
||||||
this.src = this.coverCachePath;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!snapshotUrl) {
|
|
||||||
this.src="/static/images/sync_error.png";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.coverDownloading = true;
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
util.downloadFile(snapshotUrl, self.coverCachePath, function(localPath) {
|
|
||||||
self.coverDownloading = false;
|
|
||||||
self.coverExists = true;
|
|
||||||
resolve(localPath);
|
|
||||||
}, function(err) {
|
|
||||||
self.coverDownloading = false;
|
|
||||||
reject(err);
|
|
||||||
}, function(progress) {
|
|
||||||
self.coverDownloadProgress = progress;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
this.coverDownloading = false;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onLoaded() {
|
onLoaded() {
|
||||||
this.loadingWidth = "auto";
|
this.loadingWidth = "auto";
|
||||||
|
|||||||
@@ -28,7 +28,6 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import util from "@/util"
|
import util from "@/util"
|
||||||
import md5 from "md5";
|
|
||||||
export default {
|
export default {
|
||||||
name: "LocationMessageRender",
|
name: "LocationMessageRender",
|
||||||
components: {},
|
components: {},
|
||||||
@@ -78,23 +77,14 @@
|
|||||||
},
|
},
|
||||||
async init(){
|
async init(){
|
||||||
const self = this;
|
const self = this;
|
||||||
let url = "";
|
|
||||||
// 如果有远程 snapshotUrl,则下载到 coverCachePath
|
// 如果有远程 snapshotUrl,则下载到 coverCachePath
|
||||||
const snapshotUrl = this.apisrc ;
|
const snapshotUrl = this.apisrc ;
|
||||||
const key = md5(snapshotUrl || '');
|
|
||||||
const dir_name = `${this.conversationID}`;
|
const dir_name = `${this.conversationID}`;
|
||||||
const save_file_name = `loc_${key}.png`;
|
|
||||||
const coverCachePath = `${dir_name}/${save_file_name}`;
|
|
||||||
if (typeof plus === 'undefined' || !coverCachePath) return;
|
|
||||||
self.coverDownloading = true;
|
self.coverDownloading = true;
|
||||||
util.cacheFile(snapshotUrl,coverCachePath,(fn)=>{
|
util.cacheFile(snapshotUrl,dir_name).then((fn)=>{
|
||||||
self.coverDownloading = false;
|
self.coverDownloading = false;
|
||||||
self.getImageInfo(fn);
|
self.getImageInfo(fn);
|
||||||
//console.log(fn);
|
//console.log(fn);
|
||||||
},(e)=>{
|
|
||||||
console.log(e);
|
|
||||||
},(e)=>{
|
|
||||||
console.log(e);
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
clickMediaItem() {
|
clickMediaItem() {
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
conversationID:String,
|
conversationID:String,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
//console.log(this.message);
|
console.log(this.message);
|
||||||
return {
|
return {
|
||||||
loadingWidth: "120px",
|
loadingWidth: "120px",
|
||||||
src:"",
|
src:"",
|
||||||
@@ -60,23 +60,18 @@
|
|||||||
let url = "";
|
let url = "";
|
||||||
// 如果有远程 snapshotUrl,则下载到 coverCachePath
|
// 如果有远程 snapshotUrl,则下载到 coverCachePath
|
||||||
const snapshotUrl = (this.message.pictureElem.snapshotPicture?.url ?? this.message.pictureElem.sourcePath );
|
const snapshotUrl = (this.message.pictureElem.snapshotPicture?.url ?? this.message.pictureElem.sourcePath );
|
||||||
const key = md5(snapshotUrl || '');
|
//console.log(snapshotUrl);
|
||||||
this.coverCachePath = `${this.conversationID}/img_${key}.jpg`;
|
util.cacheFile(snapshotUrl,`${this.conversationID}`).then((fn)=>{
|
||||||
util.cacheFile(snapshotUrl,this.coverCachePath,(e)=>{
|
|
||||||
self.coverDownloading = false;
|
self.coverDownloading = false;
|
||||||
self.src = coverCachePath;
|
self.src = fn;
|
||||||
console.log(e);
|
console.log(fn);
|
||||||
},(e)=>{
|
|
||||||
console.log(e);
|
|
||||||
},(e)=>{
|
|
||||||
console.log(e);
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
clickMediaItem() {
|
clickMediaItem() {
|
||||||
uni.previewImage({
|
uni.previewImage({
|
||||||
current: 0,
|
current: 0,
|
||||||
//urls: [this.message.pictureElem.sourcePicture.url],
|
//urls: [this.message.pictureElem.sourcePicture.url],
|
||||||
urls: ["_doc/"+this.coverCachePath],
|
urls: [this.src],
|
||||||
indicator: "none",
|
indicator: "none",
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -116,21 +116,12 @@
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async init(){
|
async init(){
|
||||||
// 如果有远程 snapshotUrl,则下载到 coverCachePath
|
|
||||||
const snapshotUrl = this.message?.videoElem?.snapshotUrl;
|
const snapshotUrl = this.message?.videoElem?.snapshotUrl;
|
||||||
const key = md5(this.message?.videoElem?.videoUrl || '');
|
|
||||||
const coverCachePath = `${this.conversationID}/cover_${key}.jpg`;
|
|
||||||
this.videoCachePath = `${this.conversationID}/${key}.mp4`;
|
|
||||||
this.videoExists = await util.fileExists(this.videoCachePath);
|
|
||||||
self.coverDownloading = true;
|
self.coverDownloading = true;
|
||||||
util.cacheFile(snapshotUrl,coverCachePath,(e)=>{
|
util.cacheFile(snapshotUrl,`${this.conversationID}`).then((fn)=>{
|
||||||
self.coverDownloading = false;
|
self.coverDownloading = false;
|
||||||
self.src = coverCachePath;
|
self.src = fn;
|
||||||
console.log(e);
|
console.log(fn);
|
||||||
},(e)=>{
|
|
||||||
console.log(e);
|
|
||||||
},(e)=>{
|
|
||||||
console.log(e);
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
clickMediaItem() {
|
clickMediaItem() {
|
||||||
@@ -145,10 +136,6 @@
|
|||||||
},
|
},
|
||||||
onOverlayClick() {
|
onOverlayClick() {
|
||||||
// 点击覆盖层:如果视频已缓存则直接播放,否则开始下载
|
// 点击覆盖层:如果视频已缓存则直接播放,否则开始下载
|
||||||
if (this.videoExists) {
|
|
||||||
this.playVideo("_doc/"+this.videoCachePath);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const url = this.message?.videoElem?.videoUrl || this.message?.videoElem?.videoPath;
|
const url = this.message?.videoElem?.videoUrl || this.message?.videoElem?.videoPath;
|
||||||
if (!url) {
|
if (!url) {
|
||||||
uni.showToast({ title: '无可下载的视频' });
|
uni.showToast({ title: '无可下载的视频' });
|
||||||
@@ -156,15 +143,12 @@
|
|||||||
}
|
}
|
||||||
this.videoDownloading = true;
|
this.videoDownloading = true;
|
||||||
this.videoDownloadProgress = 0;
|
this.videoDownloadProgress = 0;
|
||||||
util.downloadFile(url, this.videoCachePath, (localPath) => {
|
util.downloadFile(url, `${this.conversationID}`, (prog) => {
|
||||||
|
//this.videoDownloadProgress = prog;
|
||||||
|
}).then((fn) => {
|
||||||
this.videoDownloading = false;
|
this.videoDownloading = false;
|
||||||
this.videoExists = true;
|
this.videoExists = true;
|
||||||
this.playVideo("_doc/"+localPath);
|
this.playVideo(fn);
|
||||||
}, (err) => {
|
|
||||||
this.videoDownloading = false;
|
|
||||||
uni.showToast({ title: '下载失败' });
|
|
||||||
}, (prog) => {
|
|
||||||
this.videoDownloadProgress = prog;
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -47,20 +47,10 @@
|
|||||||
//console.log(this.message);
|
//console.log(this.message);
|
||||||
const self = this;
|
const self = this;
|
||||||
let audio = this.message.soundElem;
|
let audio = this.message.soundElem;
|
||||||
//soundPath
|
|
||||||
// 如果有远程 snapshotUrl,则下载到 cachePath
|
|
||||||
const snapshotUrl = audio.sourceUrl;
|
const snapshotUrl = audio.sourceUrl;
|
||||||
const key = md5(snapshotUrl || '');
|
util.cacheFile(snapshotUrl,`${this.conversationID}`).then((fn)=>{
|
||||||
const save_file_name = `audio_${key}.${audio.soundType}`;
|
|
||||||
const cachePath = `${this.conversationID}/${save_file_name}`;
|
|
||||||
if (typeof plus === 'undefined' || !cachePath) return;
|
|
||||||
util.cacheFile(snapshotUrl,cachePath,(fn)=>{
|
|
||||||
self.src = fn;
|
self.src = fn;
|
||||||
//console.log(fn);
|
//console.log(fn);
|
||||||
},(e)=>{
|
|
||||||
console.log(e);
|
|
||||||
},(e)=>{
|
|
||||||
console.log(e);
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
handleAudio(){
|
handleAudio(){
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<view class="page_container">
|
<view class="page_container">
|
||||||
<custom-nav-bar title="关于我们" />
|
<custom-nav-bar title="关于我们" />
|
||||||
<view class="logo_area">
|
<view class="logo_area">
|
||||||
<image src="@/static/images/about_logo.png" mode=""></image>
|
<image :src="cdn(config.app_logo)" mode=""></image>
|
||||||
<view>{{ appversion }}</view>
|
<view>{{ appversion }}</view>
|
||||||
|
|
||||||
<info-item @click="checkUpdate" class="check" title="检测更新" />
|
<info-item @click="checkUpdate" class="check" title="检测更新" />
|
||||||
@@ -31,6 +31,7 @@
|
|||||||
import InfoItem from "../selfInfo/InfoItem.vue";
|
import InfoItem from "../selfInfo/InfoItem.vue";
|
||||||
import {checkUpgrade} from "@/api/login.js"
|
import {checkUpgrade} from "@/api/login.js"
|
||||||
import { mapGetters } from "vuex";
|
import { mapGetters } from "vuex";
|
||||||
|
import util from "@/util/index.js"
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
CustomNavBar,
|
CustomNavBar,
|
||||||
@@ -57,6 +58,7 @@
|
|||||||
IMSDK.unsubscribe('uploadLogsProgress', this.uploadHandler);
|
IMSDK.unsubscribe('uploadLogsProgress', this.uploadHandler);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
...util,
|
||||||
uploadLog() {
|
uploadLog() {
|
||||||
this.show = false
|
this.show = false
|
||||||
IMSDK.asyncApi('uploadLogs',IMSDK.uuid(), {
|
IMSDK.asyncApi('uploadLogs',IMSDK.uuid(), {
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 5.9 KiB |
@@ -0,0 +1,81 @@
|
|||||||
|
{
|
||||||
|
"id": "file_cache",
|
||||||
|
"displayName": "file_cache",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "file_cache",
|
||||||
|
"keywords": [
|
||||||
|
"file_cache"
|
||||||
|
],
|
||||||
|
"repository": "",
|
||||||
|
"engines": {
|
||||||
|
"HBuilderX": "^3.6.8"
|
||||||
|
},
|
||||||
|
"dcloudext": {
|
||||||
|
"type": "uts",
|
||||||
|
"sale": {
|
||||||
|
"regular": {
|
||||||
|
"price": "0.00"
|
||||||
|
},
|
||||||
|
"sourcecode": {
|
||||||
|
"price": "0.00"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"contact": {
|
||||||
|
"qq": ""
|
||||||
|
},
|
||||||
|
"declaration": {
|
||||||
|
"ads": "",
|
||||||
|
"data": "",
|
||||||
|
"permissions": ""
|
||||||
|
},
|
||||||
|
"npmurl": ""
|
||||||
|
},
|
||||||
|
"uni_modules": {
|
||||||
|
"dependencies": [],
|
||||||
|
"encrypt": [],
|
||||||
|
"platforms": {
|
||||||
|
"cloud": {
|
||||||
|
"tcb": "u",
|
||||||
|
"aliyun": "u"
|
||||||
|
},
|
||||||
|
"client": {
|
||||||
|
"Vue": {
|
||||||
|
"vue2": "u",
|
||||||
|
"vue3": "u"
|
||||||
|
},
|
||||||
|
"App": {
|
||||||
|
"app-android": "u",
|
||||||
|
"app-ios": "u"
|
||||||
|
},
|
||||||
|
"H5-mobile": {
|
||||||
|
"Safari": "u",
|
||||||
|
"Android Browser": "u",
|
||||||
|
"微信浏览器(Android)": "u",
|
||||||
|
"QQ浏览器(Android)": "u"
|
||||||
|
},
|
||||||
|
"H5-pc": {
|
||||||
|
"Chrome": "u",
|
||||||
|
"IE": "u",
|
||||||
|
"Edge": "u",
|
||||||
|
"Firefox": "u",
|
||||||
|
"Safari": "u"
|
||||||
|
},
|
||||||
|
"小程序": {
|
||||||
|
"微信": "u",
|
||||||
|
"阿里": "u",
|
||||||
|
"百度": "u",
|
||||||
|
"字节跳动": "u",
|
||||||
|
"QQ": "u",
|
||||||
|
"钉钉": "u",
|
||||||
|
"快手": "u",
|
||||||
|
"飞书": "u",
|
||||||
|
"京东": "u"
|
||||||
|
},
|
||||||
|
"快应用": {
|
||||||
|
"华为": "u",
|
||||||
|
"联盟": "u"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# uni-network
|
||||||
|
### 开发文档
|
||||||
|
[UTS 语法](https://uniapp.dcloud.net.cn/tutorial/syntax-uts.html)
|
||||||
|
[UTS API插件](https://uniapp.dcloud.net.cn/plugin/uts-plugin.html)
|
||||||
|
[UTS 组件插件](https://uniapp.dcloud.net.cn/plugin/uts-component.html)
|
||||||
|
[Hello UTS](https://gitcode.net/dcloud/hello-uts)
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"dependencies": [
|
||||||
|
"com.squareup.okhttp3:okhttp:3.12.12"
|
||||||
|
],
|
||||||
|
"minSdkVersion": "19"
|
||||||
|
}
|
||||||
@@ -0,0 +1,495 @@
|
|||||||
|
import { RequestOptions, RequestSuccess, RequestTask, UploadFileOptions, UploadFile, UploadTask, OnProgressUpdateResult, UploadFileSuccess, UploadFileProgressUpdateCallback, DownloadFile, DownloadTask, DownloadFileOptions, OnProgressDownloadResult, DownloadFileProgressUpdateCallback, DownloadFileSuccess } from '../interface'
|
||||||
|
import { RequestFailImpl, UploadFileFailImpl, DownloadFileFailImpl, getErrcode } from '../unierror';
|
||||||
|
import { NetworkManager, NetworkRequestListener, NetworkUploadFileListener, NetworkDownloadFileListener } from './network/NetworkManager.uts'
|
||||||
|
import Pattern from 'java.util.regex.Pattern';
|
||||||
|
import Locale from 'java.util.Locale';
|
||||||
|
import TextUtils from 'android.text.TextUtils';
|
||||||
|
import { StatusCode } from './network/StatusCode.uts'
|
||||||
|
import Key from 'kotlinx.coroutines.CoroutineExceptionHandler.Key';
|
||||||
|
import JSONObject from 'com.alibaba.fastjson.JSONObject';
|
||||||
|
import ArrayList from 'java.util.ArrayList';
|
||||||
|
import ProgressListener from 'android.os.RecoverySystem.ProgressListener';
|
||||||
|
import Handler from 'android.os.Handler';
|
||||||
|
import Looper from 'android.os.Looper';
|
||||||
|
import Class from 'java.lang.Class';
|
||||||
|
import Type from 'java.lang.reflect.Type';
|
||||||
|
import Gson from "io.dcloud.uts.gson.Gson";
|
||||||
|
import ByteBuffer from 'java.nio.ByteBuffer';
|
||||||
|
import { NetworkUtil } from './network/utils/NetworkUtil.uts';
|
||||||
|
|
||||||
|
let charsetPattern = Pattern.compile('charset=([a-z0-9-]+)')
|
||||||
|
|
||||||
|
class RunnableTask extends Runnable {
|
||||||
|
private callback : () => void | null;
|
||||||
|
private looper : Looper | null = null;
|
||||||
|
constructor(looper : Looper | null, callback : () => void) {
|
||||||
|
super();
|
||||||
|
this.looper = looper;
|
||||||
|
this.callback = callback
|
||||||
|
}
|
||||||
|
|
||||||
|
override run() {
|
||||||
|
this.callback?.()
|
||||||
|
}
|
||||||
|
|
||||||
|
public execute() {
|
||||||
|
if (this.looper == null) {
|
||||||
|
this.run();
|
||||||
|
} else {
|
||||||
|
new Handler(this.looper!!).post(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class RequestNetworkListener<T> extends NetworkRequestListener {
|
||||||
|
private param : RequestOptions<T> | null = null;
|
||||||
|
private headers : UTSJSONObject = {};
|
||||||
|
private looper : Looper | null = null;
|
||||||
|
private type : Type | null = null;
|
||||||
|
private clzName : string | null = null;
|
||||||
|
constructor(param : RequestOptions<T>, type : Type, clzName : string) {
|
||||||
|
super();
|
||||||
|
this.param = param;
|
||||||
|
this.type = type;
|
||||||
|
this.clzName = clzName;
|
||||||
|
this.looper = Looper.myLooper();
|
||||||
|
}
|
||||||
|
override onStart() : void {
|
||||||
|
}
|
||||||
|
|
||||||
|
override onHeadersReceived(statusCode : number, headers : MutableMap<String, MutableList<String>>) : void {
|
||||||
|
let simpleHeaders = {};
|
||||||
|
if (headers != null) {
|
||||||
|
let it = headers.iterator();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
let entry = it.next();
|
||||||
|
let key = entry.key;
|
||||||
|
let value = entry.value;
|
||||||
|
|
||||||
|
let tmpKey = '_';
|
||||||
|
if (key == null) {
|
||||||
|
key = tmpKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.size == 0) {
|
||||||
|
continue;
|
||||||
|
} else if (value.size == 1) {
|
||||||
|
simpleHeaders[key] = value.get(0);
|
||||||
|
} else {
|
||||||
|
simpleHeaders[key] = value.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.headers = simpleHeaders;
|
||||||
|
}
|
||||||
|
|
||||||
|
override onProgress(progress : number) : void {
|
||||||
|
}
|
||||||
|
|
||||||
|
override onComplete(option : UTSJSONObject) : void {
|
||||||
|
let kParam = this.param;
|
||||||
|
let result = {};
|
||||||
|
if (kParam != null) {
|
||||||
|
if (option == null || '-1' == option['statusCode']) {
|
||||||
|
if (this.headers != null) {
|
||||||
|
result['header'] = this.headers;
|
||||||
|
}
|
||||||
|
let exception = option['cause']! as Exception;
|
||||||
|
const originalErrMsg = option['errorMsg']! as string;
|
||||||
|
let cause = exception.cause.toString();
|
||||||
|
if(exception.cause == null) {
|
||||||
|
cause = originalErrMsg
|
||||||
|
}
|
||||||
|
let errMsg = originalErrMsg
|
||||||
|
let errCode = (option['errorCode']! as string).toInt();
|
||||||
|
if (errMsg.indexOf("timeout") != -1) {
|
||||||
|
errCode = 5;
|
||||||
|
errMsg = "time out";
|
||||||
|
} else if (cause.contains("Connection refused")) {
|
||||||
|
errCode = 1000;
|
||||||
|
} else if (cause.contains("Network is unreachable")) {
|
||||||
|
errCode = 600003;
|
||||||
|
} else if (cause.contains("invalid URL")) {
|
||||||
|
errCode = 600009;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let failResult = new RequestFailImpl(getErrcode(errCode));
|
||||||
|
failResult.cause = new SourceError(cause);
|
||||||
|
new RunnableTask(this.looper, () => {
|
||||||
|
if (kParam != null) {
|
||||||
|
let fail = kParam.fail;
|
||||||
|
if (fail != null) {
|
||||||
|
fail(failResult);
|
||||||
|
}
|
||||||
|
let complete = kParam.complete;
|
||||||
|
if (complete != null) {
|
||||||
|
complete(failResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).execute();
|
||||||
|
} else {
|
||||||
|
result['statusCode'] = option['statusCode'];
|
||||||
|
if (option['originalData'] == null) {
|
||||||
|
if ("java.lang.Object".equals(this.clzName, true)) {
|
||||||
|
let errMsg = option['errorMsg'];
|
||||||
|
if (errMsg != null) {
|
||||||
|
let errMsgJson = JSON.parse((option['errorMsg']! as string));
|
||||||
|
if (errMsgJson != null) {
|
||||||
|
result['data'] = errMsgJson;
|
||||||
|
} else {
|
||||||
|
result['data'] = errMsg;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result['data'] = "error";
|
||||||
|
}
|
||||||
|
} else if("java.lang.String".equals(this.clzName, true)){
|
||||||
|
result['data'] = option['errorMsg'] ?? "error";
|
||||||
|
} else if("io.dcloud.uts.ArrayBuffer".equals(this.clzName, true)){
|
||||||
|
let textDecoder : TextEncoder = new TextEncoder()
|
||||||
|
let error :string= (option['errorMsg'] ?? "error") as string
|
||||||
|
let uint8Array = textDecoder.encode(error)
|
||||||
|
result['data'] = uint8Array.buffer
|
||||||
|
}else {
|
||||||
|
let errMsg = option['errorMsg'];
|
||||||
|
if (errMsg != null) {
|
||||||
|
let errMsgJson = JSON.parse<T>(errMsg as string, this.type);
|
||||||
|
if (errMsgJson != null) {
|
||||||
|
result['data'] = errMsgJson;
|
||||||
|
} else {
|
||||||
|
let failResult = new RequestFailImpl(getErrcode(100002));
|
||||||
|
failResult.data = errMsg
|
||||||
|
failResult.cause = new SourceError("Error message parsing failed")
|
||||||
|
new RunnableTask(this.looper, () => {
|
||||||
|
if (kParam != null) {
|
||||||
|
let fail = kParam.fail;
|
||||||
|
if (fail != null) {
|
||||||
|
fail(failResult);
|
||||||
|
}
|
||||||
|
let complete = kParam.complete;
|
||||||
|
if (complete != null) {
|
||||||
|
complete(failResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).execute();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let charset = "";
|
||||||
|
let headers = this.headers.toJSONObject() as JSONObject;
|
||||||
|
if (headers != null) {
|
||||||
|
for (key in headers.keys) {
|
||||||
|
if (key.equals("Content-Type", true)) {
|
||||||
|
charset = headers[key] as string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let data :any | null;
|
||||||
|
if("io.dcloud.uts.ArrayBuffer".equals(this.clzName, true)){
|
||||||
|
let by= option['originalData'] as ByteArray
|
||||||
|
data =ArrayBuffer.fromByteBuffer(ByteBuffer.wrap(by))
|
||||||
|
}else{
|
||||||
|
let strData = this.readAsString(option['originalData'] as ByteArray, charset);
|
||||||
|
|
||||||
|
let type = kParam.responseType != null ? kParam.responseType : kParam.dataType;
|
||||||
|
if (type == null) {
|
||||||
|
type = charset;
|
||||||
|
}
|
||||||
|
if (kParam.method == "HEAD") {
|
||||||
|
type = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
data = this.parseData(strData, type);
|
||||||
|
if (data == null) {
|
||||||
|
let failResult = new RequestFailImpl(getErrcode(100001));
|
||||||
|
|
||||||
|
new RunnableTask(this.looper, () => {
|
||||||
|
if (kParam != null) {
|
||||||
|
let fail = kParam.fail;
|
||||||
|
if (fail != null) {
|
||||||
|
fail(failResult);
|
||||||
|
}
|
||||||
|
let complete = kParam.complete;
|
||||||
|
if (complete != null) {
|
||||||
|
complete(failResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).execute();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
result['data'] = data;
|
||||||
|
}
|
||||||
|
result['statusText'] = StatusCode.getStatus(option['statusCode'] as string);
|
||||||
|
if (this.headers != null) {
|
||||||
|
result['header'] = this.headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
let tmp : RequestSuccess<T> = {
|
||||||
|
data: result['data'] as T,
|
||||||
|
statusCode: (result['statusCode'] as string).toInt(),
|
||||||
|
header: result['header']!,
|
||||||
|
cookies: NetworkUtil.parseCookie(this.headers)
|
||||||
|
};
|
||||||
|
new RunnableTask(this.looper, () => {
|
||||||
|
if (kParam != null) {
|
||||||
|
let success = kParam.success;
|
||||||
|
if (success != null) {
|
||||||
|
success(tmp);
|
||||||
|
}
|
||||||
|
let complete = kParam.complete;
|
||||||
|
if (complete != null) {
|
||||||
|
complete(tmp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readAsString(byteArray : ByteArray, type : string) : string {
|
||||||
|
let charsetType = "utf-8";
|
||||||
|
|
||||||
|
if (type != null) {
|
||||||
|
let matcher = charsetPattern.matcher(type.lowercase(Locale.ENGLISH));
|
||||||
|
if (matcher.find()) {
|
||||||
|
charsetType = matcher.group(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return new String(byteArray, charset(charsetType));
|
||||||
|
} catch (e : Exception) {
|
||||||
|
return new String(byteArray);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseData(data : string, type : string) : any | null {
|
||||||
|
if ("java.lang.Object".equals(this.clzName, true)) {
|
||||||
|
if (type.indexOf("json") != -1) {
|
||||||
|
return JSON.parse(data);
|
||||||
|
} else if (type == 'jsonp') {
|
||||||
|
if (TextUtils.isEmpty(data)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
let start = data.indexOf('(') + 1;
|
||||||
|
let end = data.indexOf(')');
|
||||||
|
if (start == 0 || start >= end) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
let tmp = data.substring(start, end);
|
||||||
|
return JSON.parse(tmp);
|
||||||
|
} else {
|
||||||
|
let jsonData: any | null = null;
|
||||||
|
try{
|
||||||
|
const gson = new Gson();
|
||||||
|
jsonData = gson.fromJson<any>(data, this.type);
|
||||||
|
}catch(e){
|
||||||
|
}
|
||||||
|
return jsonData ?? data;
|
||||||
|
}
|
||||||
|
} else if ("java.lang.String".equals(this.clzName, true)){
|
||||||
|
return data;
|
||||||
|
} else {
|
||||||
|
return JSON.parse<T>(data, this.type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseCookie(header : UTSJSONObject | null) : string[] {
|
||||||
|
if (header == null) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
let cookiesStr = header.getString('Set-Cookie')
|
||||||
|
if (cookiesStr == null) {
|
||||||
|
cookiesStr = header.getString('set-cookie')
|
||||||
|
}
|
||||||
|
if (cookiesStr == null) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
let cookiesArr = new Array<string>()
|
||||||
|
if (cookiesStr.charAt(0) == "[" && cookiesStr.charAt(cookiesStr.length - 1) == "]") {
|
||||||
|
cookiesStr = cookiesStr.slice(1, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCookiesArr = cookiesStr.split(';')
|
||||||
|
for (let i = 0; i < handleCookiesArr.length; i++) {
|
||||||
|
if (handleCookiesArr[i].indexOf('Expires=') != -1 || handleCookiesArr[i].indexOf('expires=') != -1) {
|
||||||
|
cookiesArr.push(handleCookiesArr[i].replace(',', ''))
|
||||||
|
} else {
|
||||||
|
cookiesArr.push(handleCookiesArr[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cookiesArr = cookiesArr.join(';').split(',')
|
||||||
|
|
||||||
|
return cookiesArr
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class UploadNetworkListener implements NetworkUploadFileListener {
|
||||||
|
private param : UploadFileOptions | null = null;
|
||||||
|
public progressListeners = new ArrayList<UploadFileProgressUpdateCallback>();
|
||||||
|
private looper : Looper | null = null;
|
||||||
|
constructor(param : UploadFileOptions) {
|
||||||
|
this.param = param;
|
||||||
|
this.looper = Looper.myLooper();
|
||||||
|
}
|
||||||
|
|
||||||
|
onProgress(progressUpdate : OnProgressUpdateResult) {
|
||||||
|
if (this.progressListeners.size != 0) {
|
||||||
|
new RunnableTask(this.looper, () => {
|
||||||
|
for (let i : Int = 0; i < this.progressListeners.size; i++) {
|
||||||
|
let listener = this.progressListeners.get(i);
|
||||||
|
listener(progressUpdate);
|
||||||
|
}
|
||||||
|
}).execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onComplete(option : UTSJSONObject) {
|
||||||
|
let kParam = this.param;
|
||||||
|
if (kParam != null) {
|
||||||
|
const errorMsg = option["errorMsg"];
|
||||||
|
if (errorMsg != null) {
|
||||||
|
let errCode = (option['errorCode']! as string).toInt();
|
||||||
|
let failResult = new UploadFileFailImpl(getErrcode(errCode));
|
||||||
|
let cause = option['cause'];
|
||||||
|
if (cause != null) {
|
||||||
|
failResult.cause = cause as SourceError;
|
||||||
|
}
|
||||||
|
|
||||||
|
new RunnableTask(this.looper, () => {
|
||||||
|
if (kParam != null) {
|
||||||
|
let fail = kParam.fail;
|
||||||
|
if (fail != null) {
|
||||||
|
fail(failResult);
|
||||||
|
}
|
||||||
|
let complete = kParam.complete;
|
||||||
|
if (complete != null) {
|
||||||
|
complete(failResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).execute();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
let kData = option["data"];
|
||||||
|
let data = "";
|
||||||
|
if (kData != null) {
|
||||||
|
data = kData as string;
|
||||||
|
}
|
||||||
|
let statusCode = (option['statusCode']! as string).toInt();
|
||||||
|
let successResult : UploadFileSuccess = {
|
||||||
|
data: data,
|
||||||
|
statusCode: statusCode
|
||||||
|
}
|
||||||
|
new RunnableTask(this.looper, () => {
|
||||||
|
if (kParam != null) {
|
||||||
|
let success = kParam.success;
|
||||||
|
if (success != null) {
|
||||||
|
success(successResult);
|
||||||
|
}
|
||||||
|
let complete = kParam.complete;
|
||||||
|
if (complete != null) {
|
||||||
|
complete(successResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class DownloadNetworkListener implements NetworkDownloadFileListener {
|
||||||
|
private param : DownloadFileOptions | null = null;
|
||||||
|
public progressListeners = new ArrayList<DownloadFileProgressUpdateCallback>();
|
||||||
|
private looper : Looper | null = null;
|
||||||
|
constructor(param : DownloadFileOptions) {
|
||||||
|
this.param = param;
|
||||||
|
this.looper = Looper.myLooper();
|
||||||
|
}
|
||||||
|
|
||||||
|
onProgress(progressUpdate : OnProgressDownloadResult) {
|
||||||
|
if (this.progressListeners.size != 0) {
|
||||||
|
new RunnableTask(this.looper, () => {
|
||||||
|
for (let i : Int = 0; i < this.progressListeners.size; i++) {
|
||||||
|
let listener = this.progressListeners.get(i);
|
||||||
|
listener(progressUpdate);
|
||||||
|
}
|
||||||
|
}).execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onComplete(option : UTSJSONObject) {
|
||||||
|
let kParam = this.param;
|
||||||
|
if (kParam != null) {
|
||||||
|
let errMsg = option['errorMsg'];
|
||||||
|
if (errMsg != null) {
|
||||||
|
let errCode = (option['errorCode']! as string).toInt();
|
||||||
|
let failResult = new DownloadFileFailImpl(getErrcode(errCode));
|
||||||
|
failResult.errMsg = errMsg as string;
|
||||||
|
let cause = option['cause'];
|
||||||
|
if (cause != null) {
|
||||||
|
failResult.cause = cause as SourceError;
|
||||||
|
}
|
||||||
|
|
||||||
|
new RunnableTask(this.looper, () => {
|
||||||
|
if (kParam != null) {
|
||||||
|
let fail = kParam.fail;
|
||||||
|
if (fail != null) {
|
||||||
|
fail(failResult);
|
||||||
|
}
|
||||||
|
let complete = kParam.complete;
|
||||||
|
if (complete != null) {
|
||||||
|
complete(failResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).execute();
|
||||||
|
} else {
|
||||||
|
let kTempFilePath = option["tempFilePath"];
|
||||||
|
let tempFilePath = "";
|
||||||
|
if (kTempFilePath != null) {
|
||||||
|
tempFilePath = kTempFilePath as string;
|
||||||
|
}
|
||||||
|
let statusCode = (option['statusCode']! as string).toInt();
|
||||||
|
let successResult : DownloadFileSuccess = {
|
||||||
|
tempFilePath: tempFilePath,
|
||||||
|
statusCode: statusCode
|
||||||
|
}
|
||||||
|
|
||||||
|
new RunnableTask(this.looper, () => {
|
||||||
|
if (kParam != null) {
|
||||||
|
let success = kParam.success;
|
||||||
|
if (success != null) {
|
||||||
|
success(successResult);
|
||||||
|
}
|
||||||
|
let complete = kParam.complete;
|
||||||
|
if (complete != null) {
|
||||||
|
complete(successResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@UTSAndroid.keyword("inline")
|
||||||
|
@UTSAndroid.keyword('reified')
|
||||||
|
export function request<T>(options : RequestOptions<T>) : RequestTask {
|
||||||
|
const type = UTSAndroid.getGenericType<T>();
|
||||||
|
const clzName = UTSAndroid.getGenericClassName<T>();
|
||||||
|
return NetworkManager.getInstance().request<T>(options, new RequestNetworkListener<T>(options, type, clzName));
|
||||||
|
}
|
||||||
|
|
||||||
|
export const uploadFile : UploadFile = (options : UploadFileOptions) : UploadTask => {
|
||||||
|
return NetworkManager.getInstance().uploadFile(options, new UploadNetworkListener(options));
|
||||||
|
}
|
||||||
|
|
||||||
|
export const downloadFile : DownloadFile = (options : DownloadFileOptions) : DownloadTask => {
|
||||||
|
return NetworkManager.getInstance().downloadFile(options, new DownloadNetworkListener(options));
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,32 @@
|
|||||||
|
import Dns from 'okhttp3.Dns';
|
||||||
|
import UnknownHostException from 'java.net.UnknownHostException';
|
||||||
|
import InetAddress from 'java.net.InetAddress';
|
||||||
|
import Inet4Address from 'java.net.Inet4Address';
|
||||||
|
|
||||||
|
export class OKDns implements Dns {
|
||||||
|
|
||||||
|
public override lookup(hostName: string): kotlin.collections.MutableList<InetAddress> {
|
||||||
|
if (hostName == null) {
|
||||||
|
throw UnknownHostException("hostname == null");
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
let inetAddressesList: Array<InetAddress> = [];
|
||||||
|
let inetAddresses = InetAddress.getAllByName(hostName);
|
||||||
|
for (inetAddress in inetAddresses) {
|
||||||
|
if (inetAddress instanceof Inet4Address) {
|
||||||
|
inetAddressesList.unshift(inetAddress)
|
||||||
|
} else {
|
||||||
|
inetAddressesList.push(inetAddress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return inetAddressesList;
|
||||||
|
} catch (e: Exception) {
|
||||||
|
let unknownHostException = new UnknownHostException("error");
|
||||||
|
unknownHostException.initCause(e);
|
||||||
|
throw unknownHostException;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class StatusCode {
|
||||||
|
public static statusCodeMap : Map<string, string> | null = null;
|
||||||
|
|
||||||
|
private static initStatusCodeMap() {
|
||||||
|
let map = new Map<string, string>();
|
||||||
|
this.statusCodeMap = map;
|
||||||
|
map.set('100', "Continue");
|
||||||
|
map.set('101', "Switching Protocol");
|
||||||
|
map.set('200', "OK");
|
||||||
|
map.set('201', "Created");
|
||||||
|
map.set('202', "Accepted");
|
||||||
|
map.set('203', "Non-Authoritative Information");
|
||||||
|
map.set('204', "No Content");
|
||||||
|
map.set('205', "Reset Content");
|
||||||
|
map.set('206', "Partial Content");
|
||||||
|
map.set('300', "Multiple Choice");
|
||||||
|
map.set('301', "Moved Permanently");
|
||||||
|
map.set('302', "Found");
|
||||||
|
map.set('303', "See Other");
|
||||||
|
map.set('304', "Not Modified");
|
||||||
|
map.set('305', "Use Proxy");
|
||||||
|
map.set('306', "unused");
|
||||||
|
map.set('307', "Temporary Redirect");
|
||||||
|
map.set('308', "Permanent Redirect");
|
||||||
|
map.set('400', "Bad Request");
|
||||||
|
map.set('401', "Unauthorized");
|
||||||
|
map.set('402', "Payment Required");
|
||||||
|
map.set('403', "Forbidden");
|
||||||
|
map.set('404', "Not Found");
|
||||||
|
map.set('405', "Method Not Allowed");
|
||||||
|
map.set('406', "Not Acceptable");
|
||||||
|
map.set('407', "Proxy Authentication Required");
|
||||||
|
map.set('408', "Request Timeout");
|
||||||
|
map.set('409', "Conflict");
|
||||||
|
map.set('410', "Gone");
|
||||||
|
map.set('411', "Length Required");
|
||||||
|
map.set('412', "Precondition Failed");
|
||||||
|
map.set('413', "Payload Too Large");
|
||||||
|
map.set('414', "URI Too Long");
|
||||||
|
map.set('415', "Unsupported Media Type");
|
||||||
|
map.set('416', "Requested Range Not Satisfiable");
|
||||||
|
map.set('417', "Expectation Failed");
|
||||||
|
map.set('418', "I'm a teapot");
|
||||||
|
map.set('421', "Misdirected Request");
|
||||||
|
map.set('426', "Upgrade Required");
|
||||||
|
map.set('428', "Precondition Required");
|
||||||
|
map.set('429', "Too Many Requests");
|
||||||
|
map.set('431', "Request Header Fields Too Large");
|
||||||
|
map.set('500', "Internal Server Error");
|
||||||
|
map.set('501', "Not Implemented");
|
||||||
|
map.set('502', "Bad Gateway");
|
||||||
|
map.set('503', "Service Unavailable");
|
||||||
|
map.set('504', "Gateway Timeout");
|
||||||
|
map.set('505', "HTTP Version Not Supported");
|
||||||
|
map.set('506', "Variant Also Negotiates");
|
||||||
|
map.set('507', "Variant Also Negotiates");
|
||||||
|
map.set('511', "Network Authentication Required");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getStatus(code : string) : string {
|
||||||
|
let map = this.statusCodeMap;
|
||||||
|
if (map == null) {
|
||||||
|
this.initStatusCodeMap();
|
||||||
|
}
|
||||||
|
let tmp = this.statusCodeMap!;
|
||||||
|
if (!(tmp.has(code))) {
|
||||||
|
return 'unknown status';
|
||||||
|
} else {
|
||||||
|
return tmp.get(code)! as string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export {
|
||||||
|
StatusCode
|
||||||
|
}
|
||||||
+507
@@ -0,0 +1,507 @@
|
|||||||
|
import { DownloadFileOptions, DownloadTask, DownloadFileProgressUpdateCallback, OnProgressDownloadResult } from '../../../interface.uts';
|
||||||
|
import { NetworkDownloadFileListener } from '../NetworkManager.uts'
|
||||||
|
import OkHttpClient from 'okhttp3.OkHttpClient';
|
||||||
|
import TimeUnit from 'java.util.concurrent.TimeUnit';
|
||||||
|
import ExecutorService from 'java.util.concurrent.ExecutorService';
|
||||||
|
import Executors from 'java.util.concurrent.Executors';
|
||||||
|
import Dispatcher from 'okhttp3.Dispatcher';
|
||||||
|
import Callback from 'okhttp3.Callback';
|
||||||
|
import Response from 'okhttp3.Response';
|
||||||
|
import Request from 'okhttp3.Request';
|
||||||
|
import Call from 'okhttp3.Call';
|
||||||
|
import IOException from 'java.io.IOException';
|
||||||
|
import ResponseBody from 'okhttp3.ResponseBody';
|
||||||
|
import File from 'java.io.File';
|
||||||
|
import BufferedSink from 'okio.BufferedSink';
|
||||||
|
import BufferedSource from 'okio.BufferedSource';
|
||||||
|
import Okio from 'okio.Okio';
|
||||||
|
import TextUtils from 'android.text.TextUtils';
|
||||||
|
import StringTokenizer from 'java.util.StringTokenizer';
|
||||||
|
import MimeTypeMap from 'android.webkit.MimeTypeMap';
|
||||||
|
import URLDecoder from 'java.net.URLDecoder';
|
||||||
|
import CookieManager from 'android.webkit.CookieManager';
|
||||||
|
import KotlinArray from 'kotlin.Array';
|
||||||
|
import Context from 'android.content.Context';
|
||||||
|
import Environment from 'android.os.Environment';
|
||||||
|
import { CookieInterceptor } from '../interceptor/CookieInterceptor.uts'
|
||||||
|
|
||||||
|
class NetworkDownloadTaskImpl implements DownloadTask {
|
||||||
|
private call : Call | null = null;
|
||||||
|
private listener : NetworkDownloadFileListener | null = null;
|
||||||
|
constructor(call : Call | null, listener : NetworkDownloadFileListener) {
|
||||||
|
this.call = call;
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abort() {
|
||||||
|
if (this.call != null) {
|
||||||
|
this.call?.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public onProgressUpdate(option : DownloadFileProgressUpdateCallback) {
|
||||||
|
const kListener = this.listener;
|
||||||
|
if (kListener != null) {
|
||||||
|
kListener.progressListeners.add(option);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export class DownloadController {
|
||||||
|
private static instance : DownloadController | null = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传的线程池
|
||||||
|
*/
|
||||||
|
private downloadExecutorService : ExecutorService | null = null;
|
||||||
|
|
||||||
|
public static getInstance() : DownloadController {
|
||||||
|
if (this.instance == null) {
|
||||||
|
this.instance = new DownloadController();
|
||||||
|
}
|
||||||
|
return this.instance!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public downloadFile(options : DownloadFileOptions, listener : NetworkDownloadFileListener) : DownloadTask {
|
||||||
|
const client = this.createDownloadClient(options);
|
||||||
|
let request = this.createDownloadRequest(options, listener);
|
||||||
|
if (request == null) {
|
||||||
|
return new NetworkDownloadTaskImpl(null, listener);
|
||||||
|
}
|
||||||
|
let call : Call = client.newCall(request);
|
||||||
|
call.enqueue(new SimpleDownloadCallback(listener, options.filePath ?? ""));
|
||||||
|
|
||||||
|
let task = new NetworkDownloadTaskImpl(call, listener);
|
||||||
|
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private createDownloadClient(option : DownloadFileOptions) : OkHttpClient {
|
||||||
|
|
||||||
|
let clientBuilder = OkHttpClient.Builder();
|
||||||
|
const timeout : Long = option.timeout != null ? option.timeout!.toLong() : 120000;
|
||||||
|
clientBuilder.connectTimeout(timeout, TimeUnit.MILLISECONDS);
|
||||||
|
clientBuilder.readTimeout(timeout, TimeUnit.MILLISECONDS);
|
||||||
|
clientBuilder.writeTimeout(timeout, TimeUnit.MILLISECONDS);
|
||||||
|
clientBuilder.addInterceptor(new CookieInterceptor());
|
||||||
|
|
||||||
|
if (this.downloadExecutorService == null) {
|
||||||
|
this.downloadExecutorService = Executors.newFixedThreadPool(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
clientBuilder.dispatcher(new Dispatcher(this.downloadExecutorService));
|
||||||
|
|
||||||
|
return clientBuilder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private createDownloadRequest(options : DownloadFileOptions, listener : NetworkDownloadFileListener) : Request | null {
|
||||||
|
let requestBilder = new Request.Builder();
|
||||||
|
try {
|
||||||
|
requestBilder.url(options.url);
|
||||||
|
} catch (exception : Exception) {
|
||||||
|
let option: UTSJSONObject = {};
|
||||||
|
option['statusCode'] = '-1';
|
||||||
|
option['errorCode'] = '-1';
|
||||||
|
option['errorMsg'] = "invalid URL";
|
||||||
|
let cause = exception.cause.toString();
|
||||||
|
option['cause'] = new SourceError(cause);
|
||||||
|
if (listener != null) {
|
||||||
|
listener.onComplete(option);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ua = UTSAndroid.getWebViewInfo(UTSAndroid.getAppContext()!)["ua"].toString();
|
||||||
|
requestBilder.header("User-Agent", ua);
|
||||||
|
|
||||||
|
const headers = options.header?.toMap();
|
||||||
|
if (headers != null) {
|
||||||
|
for (entry in headers) {
|
||||||
|
const key = entry.key;
|
||||||
|
const value = entry.value;
|
||||||
|
|
||||||
|
if (value != null) {
|
||||||
|
requestBilder.addHeader(key, "" + value);
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestBilder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class SimpleDownloadCallback implements Callback {
|
||||||
|
private downloadFilePath = "/uni-download/";
|
||||||
|
private listener : NetworkDownloadFileListener | null = null;
|
||||||
|
private specifyPath = "";
|
||||||
|
constructor(listener : NetworkDownloadFileListener, specifyPath : string) {
|
||||||
|
this.listener = listener;
|
||||||
|
if(specifyPath.startsWith("unifile://")){
|
||||||
|
this.specifyPath = UTSAndroid.convert2AbsFullPath(specifyPath);
|
||||||
|
}else{
|
||||||
|
this.specifyPath = specifyPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override onFailure(call : Call, exception : IOException) {
|
||||||
|
let option: UTSJSONObject = {};
|
||||||
|
option['statusCode'] = '-1';
|
||||||
|
option['errorCode'] = '-1';
|
||||||
|
option['errorMsg'] = exception.message;
|
||||||
|
let cause = exception.cause.toString();
|
||||||
|
option['cause'] = new SourceError(cause);
|
||||||
|
this.listener?.onComplete(option);
|
||||||
|
}
|
||||||
|
override onResponse(call : Call, response : Response) {
|
||||||
|
if (response.isSuccessful()) {
|
||||||
|
|
||||||
|
const source = response.body()?.source()
|
||||||
|
if (source != null) {
|
||||||
|
let mime_type = response.body()?.contentType();
|
||||||
|
let extension = "data";
|
||||||
|
if (mime_type != null) {
|
||||||
|
let mime_type1 = mime_type.toString(); // 例如: "application/json; charset=utf-8"
|
||||||
|
extension = mime_type.subtype(); // 子类型,例如: "json"
|
||||||
|
}
|
||||||
|
const tempFile = this.getTempFile()
|
||||||
|
let tempSink : BufferedSink | null = null;
|
||||||
|
let tempSource : BufferedSource | null = null;
|
||||||
|
let targetSink : BufferedSink | null = null;
|
||||||
|
try {
|
||||||
|
tempSink = Okio.buffer(Okio.sink(tempFile));
|
||||||
|
let totalBytesRead : Long = 0;
|
||||||
|
const contentLength = response.body()!!.contentLength();
|
||||||
|
const bufferSize : Int = 8 * 1024;
|
||||||
|
const buffer = ByteArray(bufferSize);
|
||||||
|
|
||||||
|
|
||||||
|
do {
|
||||||
|
let bytesRead = source.read(buffer);
|
||||||
|
if (bytesRead == -1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
tempSink.write(buffer, 0, bytesRead)
|
||||||
|
totalBytesRead += bytesRead.toLong();
|
||||||
|
const progress = (totalBytesRead.toFloat() / contentLength) * 100
|
||||||
|
let downloadProgressUpdate : OnProgressDownloadResult = {
|
||||||
|
progress: progress,
|
||||||
|
totalBytesWritten: totalBytesRead,
|
||||||
|
totalBytesExpectedToWrite: contentLength
|
||||||
|
}
|
||||||
|
this.listener?.onProgress(downloadProgressUpdate);
|
||||||
|
|
||||||
|
} while (true)
|
||||||
|
|
||||||
|
tempSink.flush()
|
||||||
|
|
||||||
|
tempSource = Okio.buffer(Okio.source(tempFile));
|
||||||
|
this.specifyPath = this.specifyPath.replace('{{ext}}',extension);
|
||||||
|
let targetFile = this.getFile(response)
|
||||||
|
targetSink = Okio.buffer(Okio.sink(targetFile));
|
||||||
|
targetSink.writeAll(tempSource);
|
||||||
|
targetSink.flush();
|
||||||
|
|
||||||
|
let option = {};
|
||||||
|
option['statusCode'] = response.code() + "";
|
||||||
|
if (targetFile.exists()) {
|
||||||
|
option['tempFilePath'] = targetFile.getPath();
|
||||||
|
}
|
||||||
|
this.listener?.onComplete(option);
|
||||||
|
} catch (e: Exception){
|
||||||
|
let option: UTSJSONObject = {};
|
||||||
|
const code = "-1"
|
||||||
|
const errorMsg = e.message
|
||||||
|
option['statusCode'] = code;
|
||||||
|
option['errorCode'] = code;
|
||||||
|
option['errorMsg'] = errorMsg;
|
||||||
|
const sourceError = new SourceError(errorMsg ?? "");
|
||||||
|
option['cause'] = sourceError;
|
||||||
|
this.listener?.onComplete(option);
|
||||||
|
} finally {
|
||||||
|
tempSink?.close()
|
||||||
|
targetSink?.close()
|
||||||
|
tempSource?.close()
|
||||||
|
source?.close()
|
||||||
|
tempFile.delete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
let option: UTSJSONObject = {};
|
||||||
|
const code = response.code() + "";
|
||||||
|
const errorMsg = response.body()?.string();
|
||||||
|
option['statusCode'] = code;
|
||||||
|
option['errorCode'] = code;
|
||||||
|
option['errorMsg'] = errorMsg;
|
||||||
|
const sourceError = new SourceError(errorMsg ?? "");
|
||||||
|
sourceError.code = response.code();
|
||||||
|
option['cause'] = sourceError;
|
||||||
|
this.listener?.onComplete(option);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//ext:string
|
||||||
|
getTempFile() : File {
|
||||||
|
return new File(UTSAndroid.getAppContext()!.getExternalCacheDir(), "temp_" + System.currentTimeMillis())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
getRealPath() : string {
|
||||||
|
var path = UTSAndroid.getAppTempPath() ?? "";
|
||||||
|
return path + this.downloadFilePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
getFile(response : Response) : File {
|
||||||
|
let targetPath = "";
|
||||||
|
if (this.specifyPath != "") {
|
||||||
|
const sourcePath = UTSAndroid.convert2AbsFullPath("/");
|
||||||
|
const sourceFileDir = new File(sourcePath);
|
||||||
|
if (this.isDescendant(sourceFileDir, new File(this.specifyPath))) {
|
||||||
|
let option = {};
|
||||||
|
option['statusCode'] = '-1';
|
||||||
|
option['errorCode'] = '602001';
|
||||||
|
option['errorMsg'] = "This path is not supported";
|
||||||
|
option['cause'] = null;
|
||||||
|
this.listener?.onComplete(option);
|
||||||
|
return new File("")
|
||||||
|
}
|
||||||
|
|
||||||
|
const pos = this.specifyPath.lastIndexOf("/")
|
||||||
|
if (pos == this.specifyPath.length - 1) {
|
||||||
|
//如果filePath是目录
|
||||||
|
if (this.isAbsolute(this.specifyPath)) {
|
||||||
|
targetPath = this.specifyPath;
|
||||||
|
} else {
|
||||||
|
targetPath = UTSAndroid.getAppTempPath()!! + "/" + this.specifyPath
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let path = "";
|
||||||
|
if (this.isAbsolute(this.specifyPath)) {
|
||||||
|
path = this.specifyPath;
|
||||||
|
} else {
|
||||||
|
path = UTSAndroid.getAppTempPath()!! + "/" + this.specifyPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
var file = new File(path)
|
||||||
|
const parentFile = file.getParentFile()
|
||||||
|
if (parentFile != null) {
|
||||||
|
if (!parentFile.exists()) {
|
||||||
|
parentFile.mkdirs()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (file.exists() && file.isDirectory()) {
|
||||||
|
let option = {};
|
||||||
|
option['statusCode'] = '-1';
|
||||||
|
option['errorCode'] = '602001';
|
||||||
|
option['errorMsg'] = "The target file path is already a directory file, and file creation failed.";
|
||||||
|
option['cause'] = null;
|
||||||
|
this.listener?.onComplete(option);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.exists()) {
|
||||||
|
const index = path.lastIndexOf(".");
|
||||||
|
let tFileName = path;
|
||||||
|
let tFileType = "";
|
||||||
|
if (index >= 0) {
|
||||||
|
tFileName = path.substring(0, index)
|
||||||
|
tFileType = path.substring(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
var number = 1
|
||||||
|
while (new File(path).exists()) {
|
||||||
|
path = tFileName + "(" + number + ")" + tFileType;
|
||||||
|
number++
|
||||||
|
}
|
||||||
|
file = new File(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!file.exists()) {
|
||||||
|
try {
|
||||||
|
file.createNewFile()
|
||||||
|
} catch (exception : Exception) {
|
||||||
|
let option = {};
|
||||||
|
option['statusCode'] = '-1';
|
||||||
|
option['errorCode'] = '602001';
|
||||||
|
option['errorMsg'] = exception.message;
|
||||||
|
let cause = exception.cause.toString();
|
||||||
|
option['cause'] = new SourceError(cause);
|
||||||
|
this.listener?.onComplete(option);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return file
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
targetPath = this.getRealPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let fileName = "";
|
||||||
|
let remoteFileName = response.header("content-disposition");
|
||||||
|
if (!TextUtils.isEmpty(remoteFileName)) {
|
||||||
|
// form-data; name="file"; filename="xxx.pdf"
|
||||||
|
const segments : KotlinArray<String | null> | null = this.stringSplit(remoteFileName, ";")
|
||||||
|
if (segments != null) {
|
||||||
|
for (let i : Int = 0; i < segments.size; i++) {
|
||||||
|
const segment = segments[i];
|
||||||
|
if (segment != null) {
|
||||||
|
if (segment.contains("filename")) {
|
||||||
|
const pair = this.stringSplit(segment.trim(), "=") //目前认为只存在一个键值对
|
||||||
|
if (pair != null && pair.size > 1) {
|
||||||
|
let key = pair[0];
|
||||||
|
let value = pair[1];
|
||||||
|
const reg = new RegExp("^\"|\"$","g")
|
||||||
|
if (key != null) {
|
||||||
|
key = key.replace(reg, "");
|
||||||
|
}
|
||||||
|
if (value != null) {
|
||||||
|
value = value.replace(reg, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TextUtils.isEmpty(key) && !TextUtils.isEmpty(value) && key!.equals("filename", true)) {
|
||||||
|
if (value != null) {
|
||||||
|
fileName = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TextUtils.isEmpty(fileName)) {
|
||||||
|
let path = response.request().url().encodedPath()
|
||||||
|
let pos = path.lastIndexOf('/')
|
||||||
|
if (pos >= 0) {
|
||||||
|
path = path.substring(pos + 1)
|
||||||
|
if (path.indexOf('.') >= 0 || path.length > 0) { //存在类型后缀或者没有文件格式后缀的情况,取最后LastPathComponent的名称当做文件名。
|
||||||
|
if (path.contains("?")) {
|
||||||
|
path = path.substring(0, path.indexOf("?"))
|
||||||
|
}
|
||||||
|
fileName = path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TextUtils.isEmpty(fileName)) {
|
||||||
|
fileName = System.currentTimeMillis().toString()
|
||||||
|
const contentType = response.header("content-type")
|
||||||
|
let type = MimeTypeMap.getSingleton().getExtensionFromMimeType(contentType);
|
||||||
|
if (type != null) {
|
||||||
|
fileName += "." + type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileName = URLDecoder.decode(fileName, "UTF-8")
|
||||||
|
fileName = fileName.replace(File.separator.toRegex(), "")
|
||||||
|
if (fileName.contains("?")) {
|
||||||
|
fileName = fileName.replace("\\?".toRegex(), "0")
|
||||||
|
}
|
||||||
|
if (fileName.length > 80) {
|
||||||
|
const subFileName : String = fileName.substring(0, 80)
|
||||||
|
fileName = subFileName + System.currentTimeMillis()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (new File(targetPath + fileName).exists()) {
|
||||||
|
const index = fileName.lastIndexOf(".");
|
||||||
|
let tFileName = fileName;
|
||||||
|
let tFileType = "";
|
||||||
|
if (index >= 0) {
|
||||||
|
tFileName = fileName.substring(0, index)
|
||||||
|
tFileType = fileName.substring(index)
|
||||||
|
|
||||||
|
//fileName是 .xxx的情况
|
||||||
|
if(tFileName == ""){
|
||||||
|
tFileName = tFileType
|
||||||
|
tFileType = ""
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tFileName = fileName
|
||||||
|
}
|
||||||
|
var number = 1
|
||||||
|
while (new File(targetPath + fileName).exists()) {
|
||||||
|
fileName = tFileName + "(" + number + ")" + tFileType;
|
||||||
|
number++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
targetPath += fileName
|
||||||
|
|
||||||
|
const file = new File(targetPath)
|
||||||
|
const parentFile = file.getParentFile()
|
||||||
|
if (parentFile != null) {
|
||||||
|
if (!parentFile.exists()) {
|
||||||
|
parentFile.mkdirs()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!file.exists()) {
|
||||||
|
try {
|
||||||
|
file.createNewFile()
|
||||||
|
} catch (exception : Exception) {
|
||||||
|
let option: UTSJSONObject = {};
|
||||||
|
option['statusCode'] = '-1';
|
||||||
|
option['errorCode'] = '602001';
|
||||||
|
option['errorMsg'] = exception.message;
|
||||||
|
let cause = exception.cause.toString();
|
||||||
|
option['cause'] = new SourceError(cause);
|
||||||
|
this.listener?.onComplete(option);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return file
|
||||||
|
}
|
||||||
|
|
||||||
|
isAbsolute(path : string) : boolean {
|
||||||
|
const context = UTSAndroid.getAppContext()!! as Context;
|
||||||
|
if (path.startsWith(context.getFilesDir().getParent())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const exPath = context.getExternalFilesDir(null)?.getParent();
|
||||||
|
if (exPath != null && path.startsWith(exPath)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断两个文件的上下级关系
|
||||||
|
*/
|
||||||
|
isDescendant(parent : File, child : File) : boolean {
|
||||||
|
//有可能开发者传入的是/sdcard 或者/storage/emulated/ 这样的文件路径, 所以要用软连接的实际文件路径进行对比.
|
||||||
|
if (child.getCanonicalPath() == parent.getCanonicalPath()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
let parentFile = child.getParentFile();
|
||||||
|
|
||||||
|
if (parentFile == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return this.isDescendant(parent, parentFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
stringSplit(str : String | null, delim : String | null) : KotlinArray<String | null> | null {
|
||||||
|
if (!TextUtils.isEmpty(str) && !TextUtils.isEmpty(delim)) {
|
||||||
|
const stringTokenizer = new StringTokenizer(str, delim, false);
|
||||||
|
const result = arrayOfNulls<String>(stringTokenizer.countTokens())
|
||||||
|
var index : Int = 0
|
||||||
|
while (stringTokenizer.hasMoreElements()) {
|
||||||
|
result[index] = stringTokenizer.nextToken().trim()
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
+82
@@ -0,0 +1,82 @@
|
|||||||
|
import Interceptor from 'okhttp3.Interceptor';
|
||||||
|
import Response from 'okhttp3.Response';
|
||||||
|
import CookieHandler from 'java.net.CookieHandler';
|
||||||
|
import TreeMap from 'java.util.TreeMap';
|
||||||
|
import Headers from 'okhttp3.Headers';
|
||||||
|
import Request from 'okhttp3.Request';
|
||||||
|
class CookieInterceptor implements Interceptor {
|
||||||
|
|
||||||
|
override intercept(chain : Interceptor.Chain) : Response {
|
||||||
|
let request = chain.request()
|
||||||
|
let headerCookie = request.header("cookie")
|
||||||
|
let uri = request.url().uri()
|
||||||
|
let cookieHandler = CookieHandler.getDefault()
|
||||||
|
if (headerCookie == null) {
|
||||||
|
let requestBuilder = request.newBuilder()
|
||||||
|
try {
|
||||||
|
let currentHeaders = this.toMap(request.headers())
|
||||||
|
let localCookie = cookieHandler.get(uri, currentHeaders)
|
||||||
|
this.addCookies(requestBuilder, localCookie)
|
||||||
|
} catch (e : Exception) {
|
||||||
|
}
|
||||||
|
request = requestBuilder.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
let response = chain.proceed(request)
|
||||||
|
|
||||||
|
try {
|
||||||
|
cookieHandler.put(uri, this.toMap(response.headers()))
|
||||||
|
} catch (e : Exception) {
|
||||||
|
}
|
||||||
|
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
private toMap(headers : Headers) : MutableMap<String, MutableList<String>> {
|
||||||
|
let result : MutableMap<String, MutableList<String>> = new TreeMap(String.CASE_INSENSITIVE_ORDER)
|
||||||
|
let size = headers.size()
|
||||||
|
for (let i:Int = 0; i < size; i++) {
|
||||||
|
let name = headers.name(i)
|
||||||
|
let values = result[name]
|
||||||
|
if (values == null) {
|
||||||
|
values = arrayListOf()
|
||||||
|
result[name] = values
|
||||||
|
}
|
||||||
|
values.add(headers.value(i))
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private addCookies(builder : Request.Builder, localCookie : MutableMap<String, MutableList<String>>) : void {
|
||||||
|
|
||||||
|
let totalList = mutableListOf<String>()
|
||||||
|
let flagList = mutableListOf<String>()
|
||||||
|
for (key in localCookie.keys) {
|
||||||
|
if (flagList.size == 2) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if ("cookie".equals(key, true) || "cookie2".equals(key, true)) {
|
||||||
|
flagList.add(key)
|
||||||
|
let cookieList = localCookie[key]
|
||||||
|
if (!cookieList.isNullOrEmpty()) {
|
||||||
|
totalList.addAll(cookieList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let headerStr = new StringBuilder()
|
||||||
|
for (let str in totalList) {
|
||||||
|
headerStr.append(str)
|
||||||
|
headerStr.append("; ")
|
||||||
|
}
|
||||||
|
if (headerStr.toString().endsWith("; ")) {
|
||||||
|
headerStr.deleteRange(headerStr.length - 2, headerStr.length - 1)
|
||||||
|
}
|
||||||
|
if (!headerStr.toString().isEmpty()){
|
||||||
|
builder.addHeader("Cookie", headerStr.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
CookieInterceptor
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import Arrays from 'java.util.Arrays';
|
||||||
|
import KotlinArray from 'kotlin.Array'
|
||||||
|
|
||||||
|
class SSLConfig {
|
||||||
|
|
||||||
|
private keystore ?: string = null;
|
||||||
|
private storePass ?: string = null;
|
||||||
|
private ca ?: KotlinArray<String> = null;
|
||||||
|
|
||||||
|
public getKeystore() : string | null {
|
||||||
|
return this.keystore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setKeystore(ks : string) {
|
||||||
|
if (ks == null) {
|
||||||
|
ks = "";
|
||||||
|
}
|
||||||
|
this.keystore = ks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getStorePass() : string | null {
|
||||||
|
return this.storePass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setStorePass(sp : string) {
|
||||||
|
if (sp == null) {
|
||||||
|
sp = "";
|
||||||
|
}
|
||||||
|
this.storePass = sp;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public getCa() : KotlinArray<String> | null {
|
||||||
|
return this.ca;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setCa(ca : KotlinArray<String>) {
|
||||||
|
if (ca == null) {
|
||||||
|
ca = emptyArray();
|
||||||
|
}
|
||||||
|
this.ca = ca;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export {
|
||||||
|
SSLConfig
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import SSLSocketFactory from 'javax.net.ssl.SSLSocketFactory';
|
||||||
|
import { SSLConfig } from './SSLConfig.uts'
|
||||||
|
import SSLContext from 'javax.net.ssl.SSLContext';
|
||||||
|
import KeyStore from 'java.security.KeyStore';
|
||||||
|
import KeyManagerFactory from 'javax.net.ssl.KeyManagerFactory';
|
||||||
|
import CertificateFactory from 'java.security.cert.CertificateFactory';
|
||||||
|
import TextUtils from 'android.text.TextUtils';
|
||||||
|
|
||||||
|
|
||||||
|
class SSLFactoryManager {
|
||||||
|
private static instance?: SSLFactoryManager = null;
|
||||||
|
|
||||||
|
private cacheSSLFactory: Map<SSLConfig, SSLSocketFactory> = new Map<SSLConfig, SSLSocketFactory>();
|
||||||
|
|
||||||
|
public static getInstance(): SSLFactoryManager {
|
||||||
|
if (this.instance == null) {
|
||||||
|
this.instance = SSLFactoryManager();
|
||||||
|
}
|
||||||
|
return this.instance!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getSSLSocketFactory(sslConfig: SSLConfig): SSLSocketFactory | null {
|
||||||
|
if (sslConfig == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (this.cacheSSLFactory.has(sslConfig)){
|
||||||
|
let sslFactory = this.cacheSSLFactory.get(sslConfig);
|
||||||
|
if (sslConfig != null){
|
||||||
|
return sslFactory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
try{
|
||||||
|
let sslContext = SSLContext.getInstance('TLS');
|
||||||
|
let keyStore = KeyStore.getInstance('PKCS12');
|
||||||
|
let keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
||||||
|
|
||||||
|
if (!TextUtils.isEmpty(sslConfig.getKeystore()) && !TextUtils.isEmpty(sslConfig.getStorePass())){
|
||||||
|
//todo 1. 这里需要解析keystore
|
||||||
|
// 2. 如果是文件需要转换一下路径,然后读出来。
|
||||||
|
// resolve : 原生层会提供bundleurl和运行模式的接口。
|
||||||
|
|
||||||
|
}else{
|
||||||
|
keyManagerFactory = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let certificateFactory = CertificateFactory.getInstance('X.509');
|
||||||
|
let caKeyStore = KeyStore.getInstance('PKCS12');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}catch(e : Exception){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
SSLFactoryManager
|
||||||
|
}
|
||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
import RequestBody from 'okhttp3.RequestBody';
|
||||||
|
import MediaType from 'okhttp3.MediaType';
|
||||||
|
import InputStream from 'java.io.InputStream';
|
||||||
|
import BufferedSink from 'okio.BufferedSink';
|
||||||
|
import Source from 'okio.Source';
|
||||||
|
import Okio from 'okio.Okio';
|
||||||
|
import Util from 'okhttp3.internal.Util';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export class InputStreamRequestBody extends RequestBody {
|
||||||
|
private mediaType : MediaType | null = null;
|
||||||
|
private length : Long = -1;
|
||||||
|
private inputStream : InputStream | null = null;
|
||||||
|
|
||||||
|
constructor(mediaType : MediaType, length : Long, inputStream : InputStream) {
|
||||||
|
super()
|
||||||
|
this.mediaType = mediaType;
|
||||||
|
this.length = length;
|
||||||
|
this.inputStream = inputStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override contentLength() : Long {
|
||||||
|
return this.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
override contentType() : MediaType {
|
||||||
|
const type = this.mediaType;
|
||||||
|
if (type == null) {
|
||||||
|
return MediaType.parse("application/octet-stream")!;
|
||||||
|
} else {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override writeTo(sink : BufferedSink) {
|
||||||
|
let source : Source | null = null;
|
||||||
|
try {
|
||||||
|
source = Okio.source(this.inputStream);
|
||||||
|
sink.writeAll(source);
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
Util.closeQuietly(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import RequestBody from 'okhttp3.RequestBody';
|
||||||
|
import MediaType from 'okhttp3.MediaType';
|
||||||
|
import BufferedSink from 'okio.BufferedSink';
|
||||||
|
import ForwardingSink from 'okio.ForwardingSink';
|
||||||
|
import Sink from 'okio.Sink';
|
||||||
|
import Buffer from 'okio.Buffer';
|
||||||
|
import Okio from 'okio.Okio';
|
||||||
|
|
||||||
|
export interface UploadProgressListener {
|
||||||
|
onProgress(bytesWritten : number, contentLength : number) : void;
|
||||||
|
}
|
||||||
|
|
||||||
|
class CountingSink extends ForwardingSink {
|
||||||
|
private listener : UploadProgressListener | null = null;
|
||||||
|
private bytesWritten : number = 0;
|
||||||
|
private total : number = 0;
|
||||||
|
constructor(sink : Sink, total : number, listener : UploadProgressListener) {
|
||||||
|
super(sink)
|
||||||
|
this.listener = listener;
|
||||||
|
this.total = total;
|
||||||
|
}
|
||||||
|
|
||||||
|
override write(source : Buffer, byteCount : Long) {
|
||||||
|
super.write(source, byteCount);
|
||||||
|
this.bytesWritten += byteCount;
|
||||||
|
this.listener?.onProgress(this.bytesWritten, this.total);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export class ProgressRequestBody extends RequestBody {
|
||||||
|
private requestBody : RequestBody | null = null;
|
||||||
|
private listener : UploadProgressListener | null = null;
|
||||||
|
constructor(requestBody : RequestBody, listener : UploadProgressListener) {
|
||||||
|
super();
|
||||||
|
this.requestBody = requestBody;
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override contentLength() : Long {
|
||||||
|
return this.requestBody?.contentLength() ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
override contentType() : MediaType {
|
||||||
|
const body = this.requestBody;
|
||||||
|
if (body == null) {
|
||||||
|
return MediaType.parse("application/octet-stream")!;
|
||||||
|
} else {
|
||||||
|
return body.contentType()!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override writeTo(sink : BufferedSink) {
|
||||||
|
const countingSink = new CountingSink(sink, this.contentLength(), this.listener!);
|
||||||
|
const bufferedSink = Okio.buffer(countingSink);
|
||||||
|
this.requestBody?.writeTo(bufferedSink);
|
||||||
|
bufferedSink.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,430 @@
|
|||||||
|
import { UploadFileOptions, UploadTask, UploadFileProgressUpdateCallback, UploadFileOptionFiles, OnProgressUpdateResult } from '../../../interface.uts';
|
||||||
|
import { NetworkUploadFileListener } from '../NetworkManager.uts'
|
||||||
|
import OkHttpClient from 'okhttp3.OkHttpClient';
|
||||||
|
import TimeUnit from 'java.util.concurrent.TimeUnit';
|
||||||
|
import ExecutorService from 'java.util.concurrent.ExecutorService';
|
||||||
|
import Executors from 'java.util.concurrent.Executors';
|
||||||
|
import RequestBody from 'okhttp3.RequestBody';
|
||||||
|
import MediaType from 'okhttp3.MediaType';
|
||||||
|
import MultipartBody from 'okhttp3.MultipartBody';
|
||||||
|
import Call from 'okhttp3.Call';
|
||||||
|
import Dispatcher from 'okhttp3.Dispatcher';
|
||||||
|
import Request from 'okhttp3.Request';
|
||||||
|
import MimeTypeMap from 'android.webkit.MimeTypeMap';
|
||||||
|
import TextUtils from 'android.text.TextUtils';
|
||||||
|
import File from 'java.io.File';
|
||||||
|
import Uri from 'android.net.Uri';
|
||||||
|
import InputStream from 'java.io.InputStream';
|
||||||
|
import MediaStore from 'android.provider.MediaStore';
|
||||||
|
import FileInputStream from 'java.io.FileInputStream';
|
||||||
|
import { InputStreamRequestBody } from './InputStreamRequestBody.uts';
|
||||||
|
import { UploadProgressListener, ProgressRequestBody } from './ProgressRequestBody.uts'
|
||||||
|
import Callback from 'okhttp3.Callback';
|
||||||
|
import Response from 'okhttp3.Response';
|
||||||
|
import IOException from 'java.io.IOException';
|
||||||
|
import Retention from 'java.lang.annotation.Retention';
|
||||||
|
import URI from 'java.net.URI';
|
||||||
|
import Build from 'android.os.Build';
|
||||||
|
import Environment from 'android.os.Environment';
|
||||||
|
import UUID from 'java.util.UUID';
|
||||||
|
import Context from 'android.content.Context';
|
||||||
|
import FileOutputStream from 'java.io.FileOutputStream';
|
||||||
|
import { CookieInterceptor } from '../interceptor/CookieInterceptor.uts'
|
||||||
|
|
||||||
|
|
||||||
|
class FileInformation {
|
||||||
|
public inputStream : InputStream | null = null;
|
||||||
|
public size : Long = -1;
|
||||||
|
public mime : string | null = null;
|
||||||
|
public name : string | null = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
class NetworkUploadTaskImpl implements UploadTask {
|
||||||
|
private call : Call | null = null;
|
||||||
|
private listener : NetworkUploadFileListener | null = null;
|
||||||
|
constructor(call : Call | null, listener : NetworkUploadFileListener) {
|
||||||
|
this.call = call;
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abort() {
|
||||||
|
if (this.call != null) {
|
||||||
|
this.call?.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public onProgressUpdate(option : UploadFileProgressUpdateCallback) {
|
||||||
|
const kListener = this.listener;
|
||||||
|
if (kListener != null) {
|
||||||
|
kListener.progressListeners.add(option);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NetworkUploadProgressListener implements UploadProgressListener {
|
||||||
|
private listener : NetworkUploadFileListener | null = null;
|
||||||
|
constructor(listener : NetworkUploadFileListener) {
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
onProgress(bytesWritten : number, contentLength : number) {
|
||||||
|
const progress = (bytesWritten.toFloat() / contentLength) * 100
|
||||||
|
const progressUpdate : OnProgressUpdateResult = {
|
||||||
|
progress: progress.toInt(),
|
||||||
|
totalBytesSent: bytesWritten,
|
||||||
|
totalBytesExpectedToSend: contentLength
|
||||||
|
}
|
||||||
|
|
||||||
|
this.listener?.onProgress(progressUpdate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UploadController {
|
||||||
|
private static instance : UploadController | null = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传的线程池
|
||||||
|
*/
|
||||||
|
private uploadExecutorService : ExecutorService | null = null;
|
||||||
|
|
||||||
|
public static getInstance() : UploadController {
|
||||||
|
if (this.instance == null) {
|
||||||
|
this.instance = new UploadController();
|
||||||
|
}
|
||||||
|
return this.instance!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public uploadFile(options : UploadFileOptions, listener : NetworkUploadFileListener) : UploadTask {
|
||||||
|
const client = this.createUploadClient(options);
|
||||||
|
|
||||||
|
let request = this.createUploadRequest(options, listener);
|
||||||
|
if (request == null) {
|
||||||
|
return new NetworkUploadTaskImpl(null, listener);;
|
||||||
|
}
|
||||||
|
let call : Call = client.newCall(request);
|
||||||
|
call.enqueue(new SimpleUploadCallback(listener));
|
||||||
|
|
||||||
|
let task = new NetworkUploadTaskImpl(call, listener);
|
||||||
|
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
private createUploadClient(option : UploadFileOptions) : OkHttpClient {
|
||||||
|
|
||||||
|
let clientBuilder = OkHttpClient.Builder();
|
||||||
|
const timeout : Long = option.timeout != null ? option.timeout!.toLong() : 120000;
|
||||||
|
clientBuilder.connectTimeout(timeout, TimeUnit.MILLISECONDS);
|
||||||
|
clientBuilder.readTimeout(timeout, TimeUnit.MILLISECONDS);
|
||||||
|
clientBuilder.writeTimeout(timeout, TimeUnit.MILLISECONDS);
|
||||||
|
clientBuilder.addInterceptor(new CookieInterceptor());
|
||||||
|
|
||||||
|
if (this.uploadExecutorService == null) {
|
||||||
|
this.uploadExecutorService = Executors.newFixedThreadPool(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
clientBuilder.dispatcher(new Dispatcher(this.uploadExecutorService));
|
||||||
|
|
||||||
|
return clientBuilder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private createUploadRequest(options : UploadFileOptions, listener : NetworkUploadFileListener) : Request | null {
|
||||||
|
let requestBilder = new Request.Builder();
|
||||||
|
try {
|
||||||
|
requestBilder.url(options.url);
|
||||||
|
} catch (exception : Exception) {
|
||||||
|
let option = {};
|
||||||
|
option['statusCode'] = '-1';
|
||||||
|
option['errorCode'] = '600009';
|
||||||
|
option['errorMsg'] = "invalid URL";
|
||||||
|
let cause = exception.cause.toString();
|
||||||
|
option['cause'] = new SourceError(cause);
|
||||||
|
if (listener != null) {
|
||||||
|
listener.onComplete(option);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let multiPartBody = (new MultipartBody.Builder("----" + UUID.randomUUID().toString())).setType(MultipartBody.FORM);
|
||||||
|
|
||||||
|
const formData = options.formData?.toMap();
|
||||||
|
if (formData != null) {
|
||||||
|
for (entry in formData) {
|
||||||
|
const key = entry.key;
|
||||||
|
const value = entry.value;
|
||||||
|
if (value != null) {
|
||||||
|
multiPartBody.addFormDataPart(key, "" + value);
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const tempFiles = options.files;
|
||||||
|
if (tempFiles != null && tempFiles!.length > 0) {
|
||||||
|
const files : UploadFileOptionFiles[] = tempFiles;
|
||||||
|
for (let i = 0; i < files.length; i++) {
|
||||||
|
const file = files[i];
|
||||||
|
const path = file.uri;
|
||||||
|
const fileInformation = this.getFileInformation(path)
|
||||||
|
const name = file.name ?? "file";
|
||||||
|
|
||||||
|
const inputStream = fileInformation?.inputStream;
|
||||||
|
if (fileInformation != null && inputStream != null) {
|
||||||
|
let requestBody = new InputStreamRequestBody(MediaType.parse(fileInformation.mime ?? "*/*")!, fileInformation.size, inputStream);
|
||||||
|
multiPartBody.addFormDataPart(name, fileInformation.name, requestBody);
|
||||||
|
} else {
|
||||||
|
let option = {};
|
||||||
|
option['statusCode'] = '-1';
|
||||||
|
option['errorCode'] = '-1';
|
||||||
|
option['errorMsg'] = "Illegal file";
|
||||||
|
option['cause'] = null;
|
||||||
|
if (listener != null) {
|
||||||
|
listener.onComplete(option);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const filePath = options.filePath;
|
||||||
|
if (filePath == null) {
|
||||||
|
let option = {};
|
||||||
|
option['statusCode'] = '-1';
|
||||||
|
option['errorCode'] = '-1';
|
||||||
|
option['errorMsg'] = "filePath is null";
|
||||||
|
option['cause'] = null;
|
||||||
|
if (listener != null) {
|
||||||
|
listener.onComplete(option);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileInformation = this.getFileInformation(filePath);
|
||||||
|
const name = options.name ?? "file";
|
||||||
|
|
||||||
|
const inputStream = fileInformation?.inputStream;
|
||||||
|
if (fileInformation != null && inputStream != null) {
|
||||||
|
let requestBody = new InputStreamRequestBody(MediaType.parse(fileInformation.mime ?? "*/*")!, fileInformation.size, inputStream);
|
||||||
|
multiPartBody.addFormDataPart(name, fileInformation.name, requestBody);
|
||||||
|
} else {
|
||||||
|
let option = {};
|
||||||
|
option['statusCode'] = '-1';
|
||||||
|
option['errorCode'] = '-1';
|
||||||
|
option['errorMsg'] = "Illegal file";
|
||||||
|
option['cause'] = null;
|
||||||
|
if (listener != null) {
|
||||||
|
listener.onComplete(option);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let ua = UTSAndroid.getWebViewInfo(UTSAndroid.getAppContext()!)["ua"].toString();
|
||||||
|
requestBilder.header("User-Agent", ua);
|
||||||
|
|
||||||
|
const headers = options.header?.toMap();
|
||||||
|
if (headers != null) {
|
||||||
|
for (entry in headers) {
|
||||||
|
const key = entry.key;
|
||||||
|
const value = entry.value;
|
||||||
|
|
||||||
|
if (value != null) {
|
||||||
|
requestBilder.addHeader(key, "" + value);
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
requestBilder.post(new ProgressRequestBody(multiPartBody.build(), new NetworkUploadProgressListener(listener)));
|
||||||
|
|
||||||
|
return requestBilder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取文件信息对象
|
||||||
|
*/
|
||||||
|
private getFileInformation(uri : string) : FileInformation | null {
|
||||||
|
let result : FileInformation | null = null;
|
||||||
|
if (uri.startsWith("content://")) {
|
||||||
|
const contentUri = Uri.parse(uri);
|
||||||
|
const context = UTSAndroid.getAppContext();
|
||||||
|
let cursor = context!.getContentResolver().query(contentUri, null, null, null, null);
|
||||||
|
if (cursor != null) {
|
||||||
|
cursor.moveToFirst();
|
||||||
|
let fileInformation = new FileInformation();
|
||||||
|
fileInformation.inputStream = context.getContentResolver().openInputStream(contentUri);
|
||||||
|
fileInformation.size = cursor.getInt(cursor.getColumnIndex(MediaStore.Images.Media.SIZE)).toLong();
|
||||||
|
fileInformation.name = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME));
|
||||||
|
fileInformation.mime = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.MIME_TYPE))
|
||||||
|
result = fileInformation;
|
||||||
|
cursor.close()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (uri.startsWith("file://")) {
|
||||||
|
uri = uri.substring("file://".length)
|
||||||
|
} else if(uri.startsWith("unifile://")){
|
||||||
|
uri = UTSAndroid.convert2AbsFullPath(uri)
|
||||||
|
}else {
|
||||||
|
// 如果不是file://开头的,就说明是相对路径。
|
||||||
|
uri = UTSAndroid.convert2AbsFullPath(uri)
|
||||||
|
if(uri.startsWith("/android_asset/")){
|
||||||
|
const filePath = uri.replace("/android_asset/", "")
|
||||||
|
const context = UTSAndroid.getAppContext();
|
||||||
|
const apkFile = this.copyAssetFileToPrivateDir(context!!, filePath)
|
||||||
|
if(apkFile != null){
|
||||||
|
uri = apkFile.getPath()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let file = new File(uri);
|
||||||
|
let fileInputStream = new FileInputStream(file);
|
||||||
|
let size = file.length();
|
||||||
|
let name = file.getName();
|
||||||
|
let mime = this.getMimeType(name);
|
||||||
|
|
||||||
|
let fileInformation = new FileInformation();
|
||||||
|
fileInformation.inputStream = fileInputStream;
|
||||||
|
fileInformation.size = size;
|
||||||
|
fileInformation.name = name;
|
||||||
|
fileInformation.mime = mime;
|
||||||
|
result = fileInformation;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private copyAssetFileToPrivateDir(context: Context, fileName: string): File| null {
|
||||||
|
try {
|
||||||
|
const destPath = context.getCacheDir().getPath() + "/uploadFiles/" + fileName
|
||||||
|
const outFile = new File(destPath)
|
||||||
|
const parentFile = outFile.getParentFile()
|
||||||
|
if (parentFile != null) {
|
||||||
|
if (!parentFile.exists()) {
|
||||||
|
parentFile.mkdirs()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!outFile.exists()){
|
||||||
|
outFile.createNewFile()
|
||||||
|
}
|
||||||
|
const inputStream = context.getAssets().open(fileName)
|
||||||
|
const outputStream = new FileOutputStream(outFile)
|
||||||
|
let buffer = new ByteArray(1024);
|
||||||
|
do {
|
||||||
|
let len = inputStream.read(buffer);
|
||||||
|
if (len == -1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
outputStream.write(buffer, 0, len)
|
||||||
|
} while (true)
|
||||||
|
|
||||||
|
inputStream.close()
|
||||||
|
outputStream.close()
|
||||||
|
|
||||||
|
return outFile
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private checkPrivatePath(path : string) : boolean {
|
||||||
|
if (Build.VERSION.SDK_INT > 29 && Environment.isExternalStorageManager()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path.startsWith("file://")) {
|
||||||
|
path = path.replace("file://", "");
|
||||||
|
}
|
||||||
|
const context = UTSAndroid.getAppContext()!;
|
||||||
|
let cache = context.getExternalCacheDir();
|
||||||
|
let sPrivateExternalDir = ""
|
||||||
|
if (cache == null) {
|
||||||
|
sPrivateExternalDir = Environment.getExternalStorageDirectory().getPath() + "/Android/data/" + context.getPackageName();
|
||||||
|
} else {
|
||||||
|
sPrivateExternalDir = cache.getParent();
|
||||||
|
}
|
||||||
|
const sPrivateDir = context.getFilesDir().getParent();
|
||||||
|
|
||||||
|
if (sPrivateExternalDir.startsWith("/") && !path.startsWith("/")) {
|
||||||
|
path = "/" + path;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((path.contains(sPrivateDir) || path.contains(sPrivateExternalDir))//表示应用私有路径
|
||||||
|
|| this.isAssetFile(path) //表示apk的assets路径文件
|
||||||
|
|| Build.VERSION.SDK_INT < Build.VERSION_CODES.Q//表示当前手机属于可正常访问路径系统
|
||||||
|
) {
|
||||||
|
//文件路径在私有路径下或手机系统版符合非分区存储逻辑
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private isAssetFile(filePath : string) : boolean {
|
||||||
|
let isAsset = false;
|
||||||
|
if (filePath.startsWith("apps/")) {
|
||||||
|
isAsset = true;
|
||||||
|
} else if (filePath.startsWith("/android_asset/") || filePath.startsWith("android_asset/")) {
|
||||||
|
isAsset = true;
|
||||||
|
}
|
||||||
|
return isAsset;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取文件mime
|
||||||
|
*/
|
||||||
|
private getMimeType(filename : string) : string {
|
||||||
|
let map = MimeTypeMap.getSingleton()
|
||||||
|
var extType = MimeTypeMap.getFileExtensionFromUrl(filename)
|
||||||
|
if (extType == null && filename.lastIndexOf(".") >= 0) {
|
||||||
|
extType = filename.substring(filename.lastIndexOf(".") + 1)
|
||||||
|
}
|
||||||
|
let ret = map.getMimeTypeFromExtension(extType);
|
||||||
|
if (TextUtils.isEmpty(ret)) {
|
||||||
|
if (TextUtils.isEmpty(extType)) {
|
||||||
|
ret = "*/*"
|
||||||
|
} else {
|
||||||
|
ret = "application/" + extType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SimpleUploadCallback implements Callback {
|
||||||
|
private listener : NetworkUploadFileListener | null = null;
|
||||||
|
constructor(listener : NetworkUploadFileListener) {
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override onFailure(call : Call, exception : IOException) {
|
||||||
|
let option = {};
|
||||||
|
option['statusCode'] = '-1';
|
||||||
|
option['errorCode'] = '-1';
|
||||||
|
option['errorMsg'] = exception.message;
|
||||||
|
let cause = exception.cause.toString();
|
||||||
|
option['cause'] = new SourceError(cause);
|
||||||
|
this.listener?.onComplete(option);
|
||||||
|
}
|
||||||
|
override onResponse(call : Call, response : Response) {
|
||||||
|
const result = {};
|
||||||
|
result["statusCode"] = response.code() + "";
|
||||||
|
result["data"] = response.body()?.string();
|
||||||
|
this.listener?.onComplete(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
UploadController
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
|
||||||
|
|
||||||
|
export class NetworkUtil {
|
||||||
|
public static convertHeaders(headers: MutableMap<String, MutableList<String>>): UTSJSONObject {
|
||||||
|
let simpleHeaders = {};
|
||||||
|
if (headers != null) {
|
||||||
|
let it = headers.iterator();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
let entry = it.next();
|
||||||
|
let key = entry.key;
|
||||||
|
let value = entry.value;
|
||||||
|
|
||||||
|
let tmpKey = '_';
|
||||||
|
if (key == null) {
|
||||||
|
key = tmpKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.size == 0) {
|
||||||
|
continue;
|
||||||
|
} else if (value.size == 1) {
|
||||||
|
simpleHeaders[key] = value.get(0);
|
||||||
|
} else {
|
||||||
|
simpleHeaders[key] = value.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return simpleHeaders;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static parseCookie(header: UTSJSONObject | null): string[] {
|
||||||
|
if (header == null) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
let cookiesStr = header.getString('Set-Cookie')
|
||||||
|
if (cookiesStr == null) {
|
||||||
|
cookiesStr = header.getString('set-cookie')
|
||||||
|
}
|
||||||
|
if (cookiesStr == null) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
let cookiesArr = new Array<string>()
|
||||||
|
if (cookiesStr.charAt(0) == "[" && cookiesStr.charAt(cookiesStr.length - 1) == "]") {
|
||||||
|
cookiesStr = cookiesStr.slice(1, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCookiesArr = cookiesStr.split(';')
|
||||||
|
for (let i = 0; i < handleCookiesArr.length; i++) {
|
||||||
|
if (handleCookiesArr[i].indexOf('Expires=') != -1 || handleCookiesArr[i].indexOf('expires=') != -1) {
|
||||||
|
cookiesArr.push(handleCookiesArr[i].replace(',', ''))
|
||||||
|
} else {
|
||||||
|
cookiesArr.push(handleCookiesArr[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cookiesArr = cookiesArr.join(';').split(',')
|
||||||
|
|
||||||
|
return cookiesArr
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import {
|
||||||
|
request
|
||||||
|
} from './network/request.uts'
|
||||||
|
import {
|
||||||
|
uploadFile
|
||||||
|
} from './network/uploadFile.uts'
|
||||||
|
import {
|
||||||
|
downloadFile
|
||||||
|
} from './network/downloadFile.uts'
|
||||||
|
import {
|
||||||
|
configMTLS
|
||||||
|
} from './network/configMTLS.uts'
|
||||||
|
|
||||||
|
export {
|
||||||
|
RequestTask,
|
||||||
|
RequestOptions,
|
||||||
|
RequestSuccess,
|
||||||
|
RequestFail,
|
||||||
|
Request,
|
||||||
|
UploadTask,
|
||||||
|
UploadFileOptions,
|
||||||
|
UploadFileSuccess,
|
||||||
|
OnProgressUpdateResult,
|
||||||
|
UploadFile,
|
||||||
|
DownloadTask,
|
||||||
|
DownloadFileOptions,
|
||||||
|
DownloadFileSuccess,
|
||||||
|
OnProgressDownloadResult,
|
||||||
|
DownloadFile,
|
||||||
|
ConfigMTLSOptions,
|
||||||
|
ConfigMTLSSuccess,
|
||||||
|
ConfigMTLS,
|
||||||
|
UploadFileCompleteCallback,
|
||||||
|
UploadFileFail,
|
||||||
|
UploadFileFailCallback,
|
||||||
|
UploadFileOptionFiles,
|
||||||
|
UploadFileProgressUpdateCallback,
|
||||||
|
UploadFileSuccessCallback,
|
||||||
|
DownloadFileProgressUpdateCallback,
|
||||||
|
DownloadFileComplete,
|
||||||
|
DownloadFileCompleteCallback,
|
||||||
|
DownloadFileFail,
|
||||||
|
DownloadFileFailCallback,
|
||||||
|
DownloadFileSuccessCallback,
|
||||||
|
RequestCompleteCallback,
|
||||||
|
RequestErrorCode,
|
||||||
|
RequestFailCallback,
|
||||||
|
RequestMethod,
|
||||||
|
RequestSuccessCallback,
|
||||||
|
ConfigMTLSComplete,
|
||||||
|
ConfigMTLSCompleteCallback,
|
||||||
|
ConfigMTLSFail,
|
||||||
|
ConfigMTLSFailCallback,
|
||||||
|
ConfigMTLSSuccessCallback,
|
||||||
|
Certificate,
|
||||||
|
RequestTaskOnChunkReceivedCallback,
|
||||||
|
RequestTaskOnChunkReceivedListenerResult,
|
||||||
|
RequestTaskOnHeadersReceivedCallback,
|
||||||
|
RequestTaskOnHeadersReceivedListenerResult
|
||||||
|
} from '../interface.uts'
|
||||||
|
|
||||||
|
export {
|
||||||
|
request,
|
||||||
|
uploadFile,
|
||||||
|
downloadFile,
|
||||||
|
configMTLS
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { BusinessError } from '@ohos.base';
|
||||||
|
import { ConfigMTLS, ConfigMTLSOptions, ConfigMTLSSuccess, Certificate } from '../../interface.uts';
|
||||||
|
import { API_CONFIG_MTLS, ConfigMTLSApiOptions, ConfigMTLSApiProtocol } from '../../protocol.uts';
|
||||||
|
import { certificates } from './utils.uts';
|
||||||
|
|
||||||
|
export const configMTLS: ConfigMTLS = defineAsyncApi<ConfigMTLSOptions, ConfigMTLSSuccess>(
|
||||||
|
API_CONFIG_MTLS,
|
||||||
|
(args: ConfigMTLSOptions, executor: ApiExecutor<ConfigMTLSSuccess>) => {
|
||||||
|
try {
|
||||||
|
args.certificates.forEach((certificate) => {
|
||||||
|
const certHosts = certificates.map(cert => cert.host)
|
||||||
|
const certHostIndex = certHosts.indexOf(certificate.host)
|
||||||
|
if (certHostIndex > -1) {
|
||||||
|
certificates.splice(certHostIndex, 1)
|
||||||
|
}
|
||||||
|
certificates.push(certificate)
|
||||||
|
})
|
||||||
|
executor.resolve()
|
||||||
|
} catch (error) {
|
||||||
|
executor.reject((error as BusinessError).message)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ConfigMTLSApiProtocol,
|
||||||
|
ConfigMTLSApiOptions
|
||||||
|
) as ConfigMTLS
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import webview from '@ohos.web.webview';
|
||||||
|
|
||||||
|
// 鸿蒙非secure cookie无法保存
|
||||||
|
// function replaceHttpWithHttps(url: string): string {
|
||||||
|
// return url.replace(/^http:/, 'https:');
|
||||||
|
// }
|
||||||
|
|
||||||
|
export function getCookie(url: string): Promise<string> {
|
||||||
|
return webview.WebCookieManager.fetchCookie(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCookieSync(url: string): string {
|
||||||
|
return webview.WebCookieManager.fetchCookieSync(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setCookie(url: string, cookies: string[]): Promise<void> {
|
||||||
|
return Promise.all(cookies.map(cookie => webview.WebCookieManager.configCookie(url, cookie))).then(() => {
|
||||||
|
return webview.WebCookieManager.saveCookieAsync();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setCookieSync(url: string, cookies: string[]): void {
|
||||||
|
cookies.forEach(cookie => {
|
||||||
|
// 不知道什么版本鸿蒙修复了非secure cookie无法保存的问题
|
||||||
|
try {
|
||||||
|
webview.WebCookieManager.configCookieSync(url, cookie);
|
||||||
|
} catch (error) { }
|
||||||
|
// let hasSecure = false;
|
||||||
|
// let hasSameSite = false;
|
||||||
|
// let savedCookie = cookie.split(';').map(cookieItem => {
|
||||||
|
// const pair = cookieItem.split('=').map(item => item.trim())
|
||||||
|
// const keyLower = pair[0].toLowerCase();
|
||||||
|
// if (keyLower === 'secure') {
|
||||||
|
// hasSecure = true;
|
||||||
|
// return cookieItem;
|
||||||
|
// }
|
||||||
|
// if (keyLower === 'samesite') {
|
||||||
|
// hasSameSite = true;
|
||||||
|
// return 'samesite=none';
|
||||||
|
// }
|
||||||
|
// return cookieItem
|
||||||
|
// }).join(';');
|
||||||
|
// if (!hasSecure) {
|
||||||
|
// savedCookie += '; secure';
|
||||||
|
// }
|
||||||
|
// if (!hasSameSite) {
|
||||||
|
// savedCookie += '; samesite=none';
|
||||||
|
// }
|
||||||
|
// try {
|
||||||
|
// // https://baidu.com/ 会返回一条 Set-Cookie: __bsi=; max-age=3600; domain=m.baidu.com; path=/(无重定向) m.baidu.com与baidu.com不一致configCookieSync会抛出错误导致崩溃
|
||||||
|
// webview.WebCookieManager.configCookieSync(replaceHttpWithHttps(url), savedCookie);
|
||||||
|
// } catch (error) { }
|
||||||
|
});
|
||||||
|
webview.WebCookieManager.saveCookieAsync();
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,303 @@
|
|||||||
|
import http from '@ohos.net.http'
|
||||||
|
import fs from '@ohos.file.fs'
|
||||||
|
import harmonyUrl from '@ohos.url'
|
||||||
|
import {
|
||||||
|
getEnv,
|
||||||
|
Emitter,
|
||||||
|
getCurrentMP,
|
||||||
|
getRealPath,
|
||||||
|
} from '@dcloudio/uni-runtime'
|
||||||
|
import {
|
||||||
|
DownloadTask as UniDownloadTask,
|
||||||
|
DownloadFileOptions as UniDownloadFileOptions,
|
||||||
|
DownloadFileSuccess as UniDownloadFileSuccess,
|
||||||
|
OnProgressDownloadResult,
|
||||||
|
DownloadFile
|
||||||
|
} from '../../interface.uts'
|
||||||
|
import {
|
||||||
|
API_DOWNLOAD_FILE,
|
||||||
|
DownloadFileApiOptions,
|
||||||
|
DownloadFileApiProtocol,
|
||||||
|
} from '../../protocol.uts'
|
||||||
|
import {
|
||||||
|
lookupExt
|
||||||
|
} from './mime.uts'
|
||||||
|
import {
|
||||||
|
getClientCertificate,
|
||||||
|
parseUrl,
|
||||||
|
} from './utils.uts'
|
||||||
|
import {
|
||||||
|
getCookieSync,
|
||||||
|
setCookieSync
|
||||||
|
} from './cookie.uts'
|
||||||
|
import { BusinessError } from '@ohos.base'
|
||||||
|
|
||||||
|
interface IUniDownloadFileEmitter {
|
||||||
|
on: (eventName: string, callback: Function) => void
|
||||||
|
once: (eventName: string, callback: Function) => void
|
||||||
|
off: (eventName: string, callback?: Function | null) => void
|
||||||
|
emit: (eventName: string, ...args: (Object | undefined | null)[]) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IDownloadTask {
|
||||||
|
abort: Function
|
||||||
|
onHeadersReceived: Function
|
||||||
|
offHeadersReceived: Function
|
||||||
|
onProgressUpdate: Function
|
||||||
|
offProgressUpdate: Function
|
||||||
|
}
|
||||||
|
|
||||||
|
function getContentFileName(contentDisposition: string): string {
|
||||||
|
const contentDispositionFileNameMatches = contentDisposition.match(/filename=['"]?(.*?)(['"]|$)/);
|
||||||
|
const contentDispositionFileName = contentDispositionFileNameMatches ? contentDispositionFileNameMatches[1] : '';
|
||||||
|
return contentDispositionFileName || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GetDownloadFilePathOptions {
|
||||||
|
dirName: string
|
||||||
|
fileName: string
|
||||||
|
contentType: string
|
||||||
|
contentDisposition: string
|
||||||
|
url: string
|
||||||
|
downloadPath: string
|
||||||
|
}
|
||||||
|
|
||||||
|
function fileExists(filePath: string): boolean {
|
||||||
|
try {
|
||||||
|
fs.statSync(filePath)
|
||||||
|
return true
|
||||||
|
} catch (error) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDownloadFilePath(options: GetDownloadFilePathOptions): string {
|
||||||
|
const { dirName, fileName, contentType, contentDisposition, url, downloadPath } = options
|
||||||
|
if (dirName && fileName) {
|
||||||
|
const realFilePath = dirName + '/' + fileName
|
||||||
|
if (fileExists(realFilePath)) {
|
||||||
|
fs.unlinkSync(realFilePath)
|
||||||
|
}
|
||||||
|
if (!fileExists(dirName)) {
|
||||||
|
fs.mkdirSync(dirName, true)
|
||||||
|
}
|
||||||
|
return realFilePath
|
||||||
|
}
|
||||||
|
let realDirName = dirName || downloadPath
|
||||||
|
const contentDispositionFileName = getContentFileName(contentDisposition);
|
||||||
|
const urlFileName = harmonyUrl.URL.parseURL(url).pathname.split('/').pop()
|
||||||
|
let realFileName: string = contentDispositionFileName || urlFileName || ''
|
||||||
|
if (!realFileName) {
|
||||||
|
const ext = lookupExt(contentType) || ''
|
||||||
|
const now = Date.now()
|
||||||
|
realFileName = ext ? '' + now + '.' + ext : '' + now
|
||||||
|
}
|
||||||
|
const lastIndexOfDot = realFileName.lastIndexOf('.')
|
||||||
|
const realFileNameExt = realFileName.substring(lastIndexOfDot + 1)
|
||||||
|
const realFileNameWithoutExt = realFileName.substring(0, lastIndexOfDot)
|
||||||
|
|
||||||
|
let realFilePath: string = realDirName + '/' + realFileName
|
||||||
|
let number = 1
|
||||||
|
while (fileExists(realFilePath)) {
|
||||||
|
realFilePath = realDirName + '/' + realFileNameWithoutExt + '(' + number + ').' + realFileNameExt
|
||||||
|
number++
|
||||||
|
}
|
||||||
|
if (!fileExists(realDirName)) {
|
||||||
|
fs.mkdirSync(realDirName, true)
|
||||||
|
}
|
||||||
|
return decodeURIComponent(realFilePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO 鸿蒙的downloadFile接口也需要传filePath,仍然会遇到content-type -> extension的问题
|
||||||
|
*/
|
||||||
|
|
||||||
|
class DownloadTask implements UniDownloadTask {
|
||||||
|
__v_skip: boolean = true
|
||||||
|
private _downloadTask: IDownloadTask
|
||||||
|
constructor(downloadTask: IDownloadTask) {
|
||||||
|
this._downloadTask = downloadTask
|
||||||
|
}
|
||||||
|
|
||||||
|
abort() {
|
||||||
|
this._downloadTask.abort()
|
||||||
|
}
|
||||||
|
|
||||||
|
onProgressUpdate(callback: Function) {
|
||||||
|
this._downloadTask.onProgressUpdate(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
offProgressUpdate(callback?: Function) {
|
||||||
|
this._downloadTask.offProgressUpdate(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
onHeadersReceived(callback: Function) {
|
||||||
|
this._downloadTask.onHeadersReceived(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
offHeadersReceived(callback?: Function) {
|
||||||
|
this._downloadTask.offHeadersReceived(callback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const downloadFile = defineTaskApi<UniDownloadFileOptions, UniDownloadFileSuccess, UniDownloadTask>(
|
||||||
|
API_DOWNLOAD_FILE,
|
||||||
|
(args: UniDownloadFileOptions, exec: ApiExecutor<UniDownloadFileSuccess>) => {
|
||||||
|
let { url, timeout, header, filePath } = args
|
||||||
|
let dirName = '';
|
||||||
|
let fileName = ''
|
||||||
|
if (filePath) {
|
||||||
|
const realPath = getRealPath(filePath) as string
|
||||||
|
if (filePath.endsWith('/')) {
|
||||||
|
dirName = realPath.endsWith('/') ? realPath.slice(0, -1) : realPath;
|
||||||
|
} else {
|
||||||
|
dirName = realPath.substring(0, realPath.lastIndexOf('/'));
|
||||||
|
fileName = realPath.substring(realPath.lastIndexOf('/') + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
header = header || {} as ESObject
|
||||||
|
if (!header!['Cookie'] && !header!['cookie']) {
|
||||||
|
header!['Cookie'] = getCookieSync(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
const httpRequest = http.createHttp()
|
||||||
|
const mp = getCurrentMP()
|
||||||
|
const userAgent = mp.userAgent.fullUserAgent
|
||||||
|
if (userAgent && !header!['User-Agent'] && !header!['user-agent']) {
|
||||||
|
header!['User-Agent'] = userAgent
|
||||||
|
}
|
||||||
|
const emitter = new Emitter() as IUniDownloadFileEmitter
|
||||||
|
const downloadTask: IDownloadTask = {
|
||||||
|
abort() {
|
||||||
|
emitter.off('headersReceive')
|
||||||
|
emitter.off('progress')
|
||||||
|
httpRequest.destroy()
|
||||||
|
},
|
||||||
|
onHeadersReceived(callback: Function) {
|
||||||
|
emitter.on('headersReceive', callback)
|
||||||
|
},
|
||||||
|
offHeadersReceived(callback?: Function) {
|
||||||
|
emitter.off('headersReceive', callback)
|
||||||
|
},
|
||||||
|
onProgressUpdate(callback: Function) {
|
||||||
|
emitter.on('progress', callback)
|
||||||
|
},
|
||||||
|
offProgressUpdate(callback?: Function) {
|
||||||
|
emitter.off('progress', callback)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
function destroy() {
|
||||||
|
downloadTask.abort()
|
||||||
|
}
|
||||||
|
mp.on('beforeClose', destroy)
|
||||||
|
|
||||||
|
let responseContentType = ''
|
||||||
|
let responseContentDisposition = ''
|
||||||
|
let lastUrl = url
|
||||||
|
httpRequest.on('headersReceive', (headers: Object) => {
|
||||||
|
const realHeaders = headers as Record<string, string | string[]>
|
||||||
|
responseContentType =
|
||||||
|
realHeaders['content-type'] as string ||
|
||||||
|
realHeaders['Content-Type'] as string ||
|
||||||
|
''
|
||||||
|
responseContentDisposition =
|
||||||
|
realHeaders['content-disposition'] as string ||
|
||||||
|
realHeaders['Content-Disposition'] as string ||
|
||||||
|
''
|
||||||
|
const setCookieHeader = realHeaders['set-cookie'] || realHeaders['Set-Cookie']
|
||||||
|
if (setCookieHeader) {
|
||||||
|
setCookieSync(lastUrl, setCookieHeader as string[])
|
||||||
|
}
|
||||||
|
const location = realHeaders['location'] || realHeaders['Location']
|
||||||
|
if (location) {
|
||||||
|
lastUrl = location as string
|
||||||
|
}
|
||||||
|
// TODO headersReceive存在bug,暂不支持回调给用户。注意重定向时会多次触发,但是只需要给用户回调最后一次
|
||||||
|
// emitter.emit('headersReceive', header);
|
||||||
|
})
|
||||||
|
httpRequest.on('dataReceiveProgress', ({ receiveSize, totalSize }) => {
|
||||||
|
emitter.emit('progress', {
|
||||||
|
progress: Math.floor((receiveSize / totalSize) * 100),
|
||||||
|
totalBytesWritten: receiveSize,
|
||||||
|
totalBytesExpectedToWrite: totalSize,
|
||||||
|
} as OnProgressDownloadResult)
|
||||||
|
})
|
||||||
|
const CACHE_PATH = getEnv().CACHE_PATH as string
|
||||||
|
const downloadPath = CACHE_PATH + '/uni-download'
|
||||||
|
if (!fs.accessSync(downloadPath)) {
|
||||||
|
fs.mkdirSync(downloadPath, true)
|
||||||
|
}
|
||||||
|
let stream: fs.Stream
|
||||||
|
let tempFilePath = ''
|
||||||
|
|
||||||
|
let writePromise = Promise.resolve(0)
|
||||||
|
async function queueWrite(data: ArrayBuffer): Promise<number> {
|
||||||
|
writePromise = writePromise.then(async (total) => {
|
||||||
|
const length = await stream.write(data)
|
||||||
|
return total + length
|
||||||
|
})
|
||||||
|
return writePromise
|
||||||
|
}
|
||||||
|
|
||||||
|
httpRequest.on('dataReceive', (data) => {
|
||||||
|
if (!stream) {
|
||||||
|
tempFilePath = getDownloadFilePath({
|
||||||
|
dirName,
|
||||||
|
fileName,
|
||||||
|
contentType: responseContentType,
|
||||||
|
contentDisposition: responseContentDisposition,
|
||||||
|
url,
|
||||||
|
downloadPath
|
||||||
|
} as GetDownloadFilePathOptions)
|
||||||
|
try {
|
||||||
|
stream = fs.createStreamSync(tempFilePath, 'w+');
|
||||||
|
} catch (error) {
|
||||||
|
exec.reject((error as BusinessError).message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
queueWrite(data)
|
||||||
|
})
|
||||||
|
httpRequest.requestInStream(
|
||||||
|
parseUrl(url),
|
||||||
|
{
|
||||||
|
header: header ? header : {} as ESObject,
|
||||||
|
method: http.RequestMethod.GET,
|
||||||
|
connectTimeout: timeout ? timeout : undefined, // 不支持仅设置一个timeout
|
||||||
|
readTimeout: timeout ? timeout : undefined,
|
||||||
|
clientCert: getClientCertificate(url)
|
||||||
|
} as http.HttpRequestOptions,
|
||||||
|
(err, statusCode) => {
|
||||||
|
// 此回调先于dataEnd回调执行
|
||||||
|
let finishPromise: Promise<void> = Promise.resolve()
|
||||||
|
if (err) {
|
||||||
|
/**
|
||||||
|
* TODO abort后此处收到如下错误,待确认是否直接将此错误码转为abort错误
|
||||||
|
* {"code":2300023,"message":"Failed writing received data to disk/application"}
|
||||||
|
*/
|
||||||
|
exec.reject(err.message)
|
||||||
|
} else {
|
||||||
|
finishPromise = writePromise.then(async () => {
|
||||||
|
await stream.flush()
|
||||||
|
await stream.close()
|
||||||
|
exec.resolve({
|
||||||
|
tempFilePath,
|
||||||
|
statusCode,
|
||||||
|
} as UniDownloadFileSuccess)
|
||||||
|
}).catch((err: Error) => {
|
||||||
|
exec.reject(err.message)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
finishPromise.then(() => {
|
||||||
|
downloadTask.offHeadersReceived()
|
||||||
|
downloadTask.offProgressUpdate()
|
||||||
|
httpRequest.destroy() // 调用完毕后必须调用destroy方法
|
||||||
|
mp.off('beforeClose', destroy)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return new DownloadTask(downloadTask)
|
||||||
|
},
|
||||||
|
DownloadFileApiProtocol,
|
||||||
|
DownloadFileApiOptions
|
||||||
|
) as DownloadFile
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
export function lookupExt(contentType: string): string | undefined {
|
||||||
|
const rawContentType = contentType.split(';')[0].trim().toLowerCase()
|
||||||
|
return (UTSHarmony.getExtensionFromMimeType(rawContentType) as string | null) || undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export function lookupContentTypeWithUri(uri: string): string | undefined {
|
||||||
|
const uriArr = uri.split('.')
|
||||||
|
if (uriArr.length <= 1) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
const ext = uriArr.pop() as string
|
||||||
|
return (UTSHarmony.getMimeTypeFromExtension(ext) as string | null) || undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export function lookupContentType(ext: string): string | undefined {
|
||||||
|
return (UTSHarmony.getMimeTypeFromExtension(ext) as string | null) || undefined
|
||||||
|
}
|
||||||
@@ -0,0 +1,360 @@
|
|||||||
|
import http from '@ohos.net.http'
|
||||||
|
import buffer from '@ohos.buffer';
|
||||||
|
import {
|
||||||
|
Emitter,
|
||||||
|
getCurrentMP,
|
||||||
|
} from '@dcloudio/uni-runtime'
|
||||||
|
import {
|
||||||
|
RequestTask as UniRequestTask,
|
||||||
|
RequestOptions as UniRequestOptions,
|
||||||
|
RequestSuccess as UniRequestSuccess,
|
||||||
|
Request,
|
||||||
|
RequestTaskOnChunkReceivedCallback,
|
||||||
|
RequestTaskOnChunkReceivedListenerResult,
|
||||||
|
RequestTaskOnHeadersReceivedCallback,
|
||||||
|
RequestTaskOnHeadersReceivedListenerResult,
|
||||||
|
} from '../../interface.uts'
|
||||||
|
import {
|
||||||
|
API_REQUEST,
|
||||||
|
RequestApiOptions,
|
||||||
|
RequestApiProtocol,
|
||||||
|
} from '../../protocol.uts'
|
||||||
|
import {
|
||||||
|
getClientCertificate,
|
||||||
|
parseUrl,
|
||||||
|
} from './utils.uts'
|
||||||
|
import {
|
||||||
|
getCookieSync,
|
||||||
|
setCookieSync,
|
||||||
|
} from './cookie.uts'
|
||||||
|
|
||||||
|
interface IUniRequestEmitter {
|
||||||
|
on: (eventName: string, callback: Function) => void
|
||||||
|
once: (eventName: string, callback: Function) => void
|
||||||
|
off: (eventName: string, callback?: Function | null) => void
|
||||||
|
emit: (eventName: string, ...args: (Object | undefined | null)[]) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IRequestTask {
|
||||||
|
abort: Function
|
||||||
|
onChunkReceived: (listener: RequestTaskOnChunkReceivedCallback) => void
|
||||||
|
offChunkReceived: (listener?: RequestTaskOnChunkReceivedCallback | null) => void
|
||||||
|
onHeadersReceived: (listener: RequestTaskOnHeadersReceivedCallback) => void
|
||||||
|
offHeadersReceived: (listener?: RequestTaskOnHeadersReceivedCallback | null) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
class RequestTask implements UniRequestTask {
|
||||||
|
__v_skip: boolean = true
|
||||||
|
private _requestTask: IRequestTask
|
||||||
|
private _requestOnChunkReceiveCallbackId: number = 0
|
||||||
|
private _requestOnChunkReceiveCallbacks: Map<number, RequestTaskOnChunkReceivedCallback> = new Map()
|
||||||
|
private _requestOnHeadersReceiveCallbackId: number = 0
|
||||||
|
private _requestOnHeadersReceiveCallbacks: Map<number, RequestTaskOnHeadersReceivedCallback> = new Map()
|
||||||
|
|
||||||
|
constructor(requestTask: IRequestTask) {
|
||||||
|
this._requestTask = requestTask
|
||||||
|
}
|
||||||
|
abort() {
|
||||||
|
this._requestTask.abort()
|
||||||
|
}
|
||||||
|
onChunkReceived(callback: RequestTaskOnChunkReceivedCallback) {
|
||||||
|
this._requestTask.onChunkReceived(callback)
|
||||||
|
this._requestOnChunkReceiveCallbackId++
|
||||||
|
this._requestOnChunkReceiveCallbacks.set(this._requestOnChunkReceiveCallbackId, callback)
|
||||||
|
return this._requestOnChunkReceiveCallbackId
|
||||||
|
}
|
||||||
|
offChunkReceived(callbackId?: RequestTaskOnChunkReceivedCallback | number | null) {
|
||||||
|
if (callbackId === undefined || callbackId === null) {
|
||||||
|
this._requestTask.offChunkReceived()
|
||||||
|
this._requestOnChunkReceiveCallbacks.clear()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (typeof callbackId === 'function') {
|
||||||
|
this._requestOnChunkReceiveCallbacks.forEach((callback, id) => {
|
||||||
|
if (callback === callbackId) {
|
||||||
|
this._requestOnChunkReceiveCallbacks.delete(id)
|
||||||
|
this._requestTask.offChunkReceived(callback)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const callback = this._requestOnChunkReceiveCallbacks.get(callbackId)
|
||||||
|
if (!callback) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this._requestOnChunkReceiveCallbacks.delete(callbackId)
|
||||||
|
this._requestTask.offChunkReceived(callback)
|
||||||
|
}
|
||||||
|
onHeadersReceived(callback: RequestTaskOnHeadersReceivedCallback) {
|
||||||
|
this._requestTask.onHeadersReceived(callback)
|
||||||
|
this._requestOnHeadersReceiveCallbackId++
|
||||||
|
this._requestOnHeadersReceiveCallbacks.set(this._requestOnHeadersReceiveCallbackId, callback)
|
||||||
|
return this._requestOnHeadersReceiveCallbackId
|
||||||
|
}
|
||||||
|
offHeadersReceived(callbackId?: RequestTaskOnHeadersReceivedCallback | number | null) {
|
||||||
|
if (callbackId === undefined || callbackId === null) {
|
||||||
|
this._requestTask.offHeadersReceived()
|
||||||
|
this._requestOnHeadersReceiveCallbacks.clear()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (typeof callbackId === 'function') {
|
||||||
|
this._requestOnHeadersReceiveCallbacks.forEach((callback, id) => {
|
||||||
|
if (callback === callbackId) {
|
||||||
|
this._requestOnHeadersReceiveCallbacks.delete(id)
|
||||||
|
this._requestTask.offHeadersReceived(callback)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const callback = this._requestOnHeadersReceiveCallbacks.get(callbackId)
|
||||||
|
if (!callback) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this._requestOnHeadersReceiveCallbacks.delete(callbackId)
|
||||||
|
this._requestTask.offHeadersReceived(callback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* http.request有5MB响应体大小限制
|
||||||
|
* rcp.Session.fetch无响应体大小限制,但是headers、cookies处理不够完善,另外rcp需要从@hms引用,不是openharmony标准库
|
||||||
|
* 为突破http.request的限制,现在使用http.requestInStream
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const request = defineTaskApi<UniRequestOptions<Object>, UniRequestSuccess<Object>, UniRequestTask>(
|
||||||
|
API_REQUEST,
|
||||||
|
(args: UniRequestOptions<Object>, exec: ApiExecutor<UniRequestSuccess<Object>>) => {
|
||||||
|
let { header, method, dataType, timeout, url, responseType } = args
|
||||||
|
let data: ESObject = args.data
|
||||||
|
header = header || {} as ESObject
|
||||||
|
if (!header!['Cookie'] && !header!['cookie']) {
|
||||||
|
header!['Cookie'] = getCookieSync(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
let contentType = ''
|
||||||
|
|
||||||
|
// header
|
||||||
|
const headers = {} as Record<string, Object>
|
||||||
|
const headerRecord = header as Object as Record<string, string>
|
||||||
|
const headerKeys = Object.keys(headerRecord)
|
||||||
|
for (let i = 0; i < headerKeys.length; i++) {
|
||||||
|
const name = headerKeys[i];
|
||||||
|
if (name.toLowerCase() === 'content-type') {
|
||||||
|
contentType = headerRecord[name] as string
|
||||||
|
}
|
||||||
|
headers[name.toLowerCase()] = headerRecord[name]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!contentType && method === 'POST') {
|
||||||
|
headers['Content-Type'] = 'application/json'
|
||||||
|
contentType = 'application/json'
|
||||||
|
}
|
||||||
|
|
||||||
|
// url data
|
||||||
|
if (method === 'GET' && data && typeof data === 'object') {
|
||||||
|
const dataRecord = data as Record<string, Object>
|
||||||
|
const query = Object.keys(dataRecord)
|
||||||
|
.map((key) => {
|
||||||
|
return (
|
||||||
|
encodeURIComponent(key) +
|
||||||
|
'=' +
|
||||||
|
encodeURIComponent(dataRecord[key] as string | number | boolean)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.join('&')
|
||||||
|
url += query ?
|
||||||
|
(url.indexOf('?') > -1 ? '&' : '?') + query :
|
||||||
|
''
|
||||||
|
data = null
|
||||||
|
} else if (
|
||||||
|
method !== 'GET' &&
|
||||||
|
contentType &&
|
||||||
|
contentType.indexOf('application/json') === 0 &&
|
||||||
|
data &&
|
||||||
|
typeof data !== 'string'
|
||||||
|
) {
|
||||||
|
data = JSON.stringify(data)
|
||||||
|
} else if (
|
||||||
|
method !== 'GET' &&
|
||||||
|
contentType &&
|
||||||
|
contentType.indexOf('application/x-www-form-urlencoded') === 0 &&
|
||||||
|
data &&
|
||||||
|
typeof data === 'object'
|
||||||
|
) {
|
||||||
|
const dataRecord = data as Record<string, Object>
|
||||||
|
data = Object.keys(dataRecord)
|
||||||
|
.map((key) => {
|
||||||
|
return (
|
||||||
|
encodeURIComponent(key) +
|
||||||
|
'=' +
|
||||||
|
encodeURIComponent(dataRecord[key] as number | string | boolean)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.join('&')
|
||||||
|
}
|
||||||
|
|
||||||
|
const httpRequest = http.createHttp()
|
||||||
|
const mp = getCurrentMP()
|
||||||
|
const userAgent = mp.userAgent.fullUserAgent
|
||||||
|
if (userAgent && headers && !headers!['User-Agent'] && !headers!['user-agent']) {
|
||||||
|
headers!['User-Agent'] = userAgent
|
||||||
|
}
|
||||||
|
const emitter = new Emitter() as IUniRequestEmitter
|
||||||
|
const requestTask: IRequestTask = {
|
||||||
|
abort() {
|
||||||
|
emitter.off('headersReceive')
|
||||||
|
httpRequest.destroy()
|
||||||
|
},
|
||||||
|
onHeadersReceived(callback: RequestTaskOnHeadersReceivedCallback) {
|
||||||
|
emitter.on('headersReceive', callback)
|
||||||
|
},
|
||||||
|
offHeadersReceived(callback?: RequestTaskOnHeadersReceivedCallback | null) {
|
||||||
|
emitter.off('headersReceive', callback || undefined)
|
||||||
|
},
|
||||||
|
onChunkReceived(callback: RequestTaskOnChunkReceivedCallback) {
|
||||||
|
emitter.on('dataReceive', callback)
|
||||||
|
},
|
||||||
|
offChunkReceived(callback?: RequestTaskOnChunkReceivedCallback | null) {
|
||||||
|
emitter.off('dataReceive', callback || undefined)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const realRequestTask = new RequestTask(requestTask)
|
||||||
|
|
||||||
|
function destroy() {
|
||||||
|
emitter.off('headersReceive')
|
||||||
|
httpRequest.destroy()
|
||||||
|
}
|
||||||
|
mp.on('beforeClose', destroy)
|
||||||
|
|
||||||
|
let latestHeaders: Object | null = null
|
||||||
|
let lastUrl = url
|
||||||
|
httpRequest.on('headersReceive', (headers: Object) => {
|
||||||
|
const realHeaders = headers as Record<string, string | string[]>
|
||||||
|
const setCookieHeader = realHeaders['set-cookie'] || realHeaders['Set-Cookie']
|
||||||
|
if (setCookieHeader) {
|
||||||
|
setCookieSync(lastUrl, setCookieHeader as string[])
|
||||||
|
}
|
||||||
|
latestHeaders = headers
|
||||||
|
const location = realHeaders['location'] || realHeaders['Location']
|
||||||
|
if (location) {
|
||||||
|
lastUrl = location as string
|
||||||
|
}
|
||||||
|
// TODO headersReceive存在bug,暂不支持回调给用户。
|
||||||
|
// emitter.emit('headersReceive', headers);
|
||||||
|
})
|
||||||
|
|
||||||
|
let headersReceiveTriggered = false
|
||||||
|
const bufs = [] as buffer.Buffer[]
|
||||||
|
httpRequest.on('dataReceive', (data) => {
|
||||||
|
if (!headersReceiveTriggered) {
|
||||||
|
headersReceiveTriggered = true
|
||||||
|
// 注意重定向时会多次触发,但是只需要给用户回调最后一次headers
|
||||||
|
const headers = {} as UTSJSONObject
|
||||||
|
const realHeaders = latestHeaders as Record<string, string | string[]>
|
||||||
|
let cookies = [] as string[]
|
||||||
|
// 有用户反馈部分情况下headers会为null,无法复现,增加判断逻辑容错
|
||||||
|
if (realHeaders) {
|
||||||
|
const keys = Object.keys(realHeaders)
|
||||||
|
for (let i = 0; i < keys.length; i++) {
|
||||||
|
const key = keys[i]
|
||||||
|
if (key === 'set-cookie' || key === 'Set-Cookie') {
|
||||||
|
const setCookieHeader = realHeaders[key] as string[];
|
||||||
|
// TODO 待确认为什么鸿蒙会返回重复的Set-Cookie,不合常理。使用 https://httpbin.org/cookies/set/nnn/123 这个接口测试
|
||||||
|
for (let i = setCookieHeader.length - 1; i >= 0; i--) {
|
||||||
|
const cookie = setCookieHeader[i]
|
||||||
|
if (setCookieHeader.indexOf(cookie) !== i) {
|
||||||
|
setCookieHeader.splice(i, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cookies = setCookieHeader as string[];
|
||||||
|
headers[key] = Array.isArray(setCookieHeader) ? setCookieHeader.join(',') : setCookieHeader;
|
||||||
|
} else {
|
||||||
|
headers[key] = realHeaders[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
emitter.emit('headersReceive', {
|
||||||
|
header: headers,
|
||||||
|
// 鸿蒙不支持在headersReceive时机获取到状态码
|
||||||
|
statusCode: NaN,
|
||||||
|
cookies: cookies,
|
||||||
|
} as RequestTaskOnHeadersReceivedListenerResult);
|
||||||
|
}
|
||||||
|
bufs.push(buffer.from(data))
|
||||||
|
if (args.enableChunked) {
|
||||||
|
emitter.emit('dataReceive', {
|
||||||
|
data: data
|
||||||
|
} as RequestTaskOnChunkReceivedListenerResult)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
httpRequest.requestInStream(
|
||||||
|
parseUrl(url),
|
||||||
|
{
|
||||||
|
header: headers,
|
||||||
|
method: (method || 'GET').toUpperCase() as http.RequestMethod, // 仅OPTIONS不支持
|
||||||
|
extraData: data || undefined, // 传空字符串会报错
|
||||||
|
connectTimeout: timeout ? timeout : undefined, // 不支持仅设置一个timeout
|
||||||
|
readTimeout: timeout ? timeout : undefined,
|
||||||
|
clientCert: getClientCertificate(url)
|
||||||
|
} as http.HttpRequestOptions,
|
||||||
|
(err, statusCode) => {
|
||||||
|
if (err) {
|
||||||
|
/**
|
||||||
|
* TODO abort后此处收到如下错误,待确认是否直接将此错误码转为abort错误
|
||||||
|
* {"code":2300023,"message":"Failed writing received data to disk/application"}
|
||||||
|
*
|
||||||
|
* reject方法第二个参数可以传errCode,reject(err.message, { errCode: -1 })
|
||||||
|
*/
|
||||||
|
exec.reject(err.message)
|
||||||
|
} else {
|
||||||
|
const responseData = buffer.concat(bufs)
|
||||||
|
let data: ArrayBuffer | string | object = ''
|
||||||
|
if (responseType === 'arraybuffer') {
|
||||||
|
data = responseData.buffer
|
||||||
|
} else {
|
||||||
|
data = responseData.toString('utf8')
|
||||||
|
if (dataType === 'json') {
|
||||||
|
try {
|
||||||
|
// #ifdef UNI-APP-X
|
||||||
|
data = globalThis.UTS.JSON.parse(data) || data;
|
||||||
|
// #endif
|
||||||
|
// #ifndef UNI-APP-X
|
||||||
|
data = JSON.parse(data as string);
|
||||||
|
// #endif
|
||||||
|
} catch (e) {
|
||||||
|
// 与微信保持一致,不抛出异常
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const headers = latestHeaders as Record<string, string | string[]>
|
||||||
|
// 有用户反馈部分情况下headers会为null,无法复现,增加判断逻辑容错
|
||||||
|
const oldCookies = headers ? (headers['Set-Cookie'] || headers['set-cookie'] || []) as string[] : [] as string[]
|
||||||
|
let newCookies = oldCookies.join(',')
|
||||||
|
if (newCookies) {
|
||||||
|
if (headers['Set-Cookie']) {
|
||||||
|
headers['Set-Cookie'] = newCookies
|
||||||
|
} else {
|
||||||
|
headers['set-cookie'] = newCookies
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exec.resolve({
|
||||||
|
data,
|
||||||
|
statusCode,
|
||||||
|
header: latestHeaders!,
|
||||||
|
cookies: oldCookies,
|
||||||
|
} as UniRequestSuccess<Object>)
|
||||||
|
}
|
||||||
|
realRequestTask.offChunkReceived()
|
||||||
|
realRequestTask.offHeadersReceived()
|
||||||
|
httpRequest.destroy() // 调用完毕后必须调用destroy方法
|
||||||
|
mp.off('beforeClose', destroy)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return realRequestTask
|
||||||
|
},
|
||||||
|
RequestApiProtocol,
|
||||||
|
RequestApiOptions
|
||||||
|
) as Request<Object>
|
||||||
@@ -0,0 +1,253 @@
|
|||||||
|
import http from '@ohos.net.http'
|
||||||
|
import buffer from '@ohos.buffer';
|
||||||
|
import fs from '@ohos.file.fs'
|
||||||
|
import {
|
||||||
|
getRealPath,
|
||||||
|
Emitter,
|
||||||
|
getCurrentMP,
|
||||||
|
} from '@dcloudio/uni-runtime'
|
||||||
|
import {
|
||||||
|
UploadTask as UniUploadTask,
|
||||||
|
UploadFileOptions as UniUploadFileOptions,
|
||||||
|
UploadFileSuccess as UniUploadFileSuccess,
|
||||||
|
OnProgressUpdateResult,
|
||||||
|
UploadFile
|
||||||
|
} from '../../interface.uts'
|
||||||
|
import {
|
||||||
|
API_UPLOAD_FILE,
|
||||||
|
UploadFileApiOptions,
|
||||||
|
UploadFileApiProtocol,
|
||||||
|
} from '../../protocol.uts'
|
||||||
|
import {
|
||||||
|
lookupContentTypeWithUri
|
||||||
|
} from './mime.uts'
|
||||||
|
import {
|
||||||
|
getClientCertificate,
|
||||||
|
parseUrl,
|
||||||
|
} from './utils.uts'
|
||||||
|
import {
|
||||||
|
getCookieSync,
|
||||||
|
setCookieSync
|
||||||
|
} from './cookie.uts'
|
||||||
|
|
||||||
|
interface IUniUploadFileEmitter {
|
||||||
|
on: (eventName: string, callback: Function) => void
|
||||||
|
once: (eventName: string, callback: Function) => void
|
||||||
|
off: (eventName: string, callback?: Function | null) => void
|
||||||
|
emit: (eventName: string, ...args: (Object | undefined | null)[]) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IUploadTask {
|
||||||
|
abort: Function
|
||||||
|
onHeadersReceived: Function
|
||||||
|
offHeadersReceived: Function
|
||||||
|
onProgressUpdate: Function
|
||||||
|
offProgressUpdate: Function
|
||||||
|
}
|
||||||
|
|
||||||
|
class UploadTask implements UniUploadTask {
|
||||||
|
__v_skip: boolean = true
|
||||||
|
private _uploadTask: IUploadTask
|
||||||
|
constructor(uploadTask: IUploadTask) {
|
||||||
|
this._uploadTask = uploadTask
|
||||||
|
}
|
||||||
|
|
||||||
|
abort() {
|
||||||
|
this._uploadTask.abort()
|
||||||
|
}
|
||||||
|
|
||||||
|
onProgressUpdate(callback: Function) {
|
||||||
|
this._uploadTask.onProgressUpdate(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
offProgressUpdate(callback?: Function) {
|
||||||
|
this._uploadTask.offProgressUpdate(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
onHeadersReceived(callback: Function) {
|
||||||
|
this._uploadTask.onHeadersReceived(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
offHeadersReceived(callback?: Function) {
|
||||||
|
this._uploadTask.offHeadersReceived(callback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function readFile(filePath: string): ArrayBuffer {
|
||||||
|
const readFilePath = getRealPath(filePath) as string
|
||||||
|
const file = fs.openSync(readFilePath, fs.OpenMode.READ_ONLY)
|
||||||
|
const stat = fs.statSync(file.fd)
|
||||||
|
const data = new ArrayBuffer(stat.size)
|
||||||
|
fs.readSync(file.fd, data)
|
||||||
|
fs.closeSync(file.fd)
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
export const uploadFile = defineTaskApi<UniUploadFileOptions, UniUploadFileSuccess, UniUploadTask>(
|
||||||
|
API_UPLOAD_FILE,
|
||||||
|
(args: UniUploadFileOptions, exec: ApiExecutor<UniUploadFileSuccess>) => {
|
||||||
|
let { url, timeout, header, formData, files, filePath, name } = args
|
||||||
|
header = header || {} as ESObject
|
||||||
|
if (!header!['Cookie'] && !header!['cookie']) {
|
||||||
|
header!['Cookie'] = getCookieSync(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
// header
|
||||||
|
const headers = {} as Record<string, Object>
|
||||||
|
if (header) {
|
||||||
|
const headerRecord = header as Object as Record<string, string>
|
||||||
|
const headerKeys = Object.keys(headerRecord)
|
||||||
|
for (let i = 0; i < headerKeys.length; i++) {
|
||||||
|
const name = headerKeys[i]
|
||||||
|
headers[name.toLowerCase()] = headerRecord[name]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
headers['Content-Type'] = 'multipart/form-data'
|
||||||
|
|
||||||
|
const multiFormDataList = [] as Array<http.MultiFormData>
|
||||||
|
if (formData) {
|
||||||
|
const formDataRecord = formData as Object as Record<string, Object>
|
||||||
|
const formDataKeys = Object.keys(formDataRecord)
|
||||||
|
for (let i = 0; i < formDataKeys.length; i++) {
|
||||||
|
const name = formDataKeys[i]
|
||||||
|
multiFormDataList.push({
|
||||||
|
name,
|
||||||
|
contentType: 'text/plain',
|
||||||
|
data: String(formDataRecord[name]),
|
||||||
|
} as http.MultiFormData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (files && files.length) {
|
||||||
|
for (let i = 0; i < files.length; i++) {
|
||||||
|
const { name, uri } = files[i]
|
||||||
|
multiFormDataList.push({
|
||||||
|
name: name || 'file', // 鸿蒙的request.uploadFile接口不能为每个文件设置name
|
||||||
|
contentType: lookupContentTypeWithUri(uri) || 'application/octet-stream',
|
||||||
|
remoteFileName: uri.split('/').pop() || 'no-name',
|
||||||
|
/**
|
||||||
|
* TODO 未联系鸿蒙确认
|
||||||
|
* chooseImage返回"file://media/Photo/1/xxxx",此uri可以被fs相关接口读取,但是上传时使用会报错
|
||||||
|
* 使用"file://media/Photo/1/xxxx"、"file:///media/Photo/1/xxxx"、"/media/Photo/1/xxxx"、"media/Photo/1/xxxx"、格式都会报错
|
||||||
|
* 错误信息:Failed to open/read local data from file/application
|
||||||
|
*/
|
||||||
|
// filePath: getRealPath(uri!),
|
||||||
|
// TODO 调整为异步读取
|
||||||
|
data: readFile(uri!)
|
||||||
|
} as http.MultiFormData)
|
||||||
|
}
|
||||||
|
} else if (filePath) {
|
||||||
|
multiFormDataList.push({
|
||||||
|
name: name || 'file',
|
||||||
|
contentType: lookupContentTypeWithUri(filePath!) || 'application/octet-stream',
|
||||||
|
remoteFileName: filePath.split('/').pop() || 'no-name',
|
||||||
|
data: readFile(filePath!)
|
||||||
|
} as http.MultiFormData)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
exec.reject((error as Error).message)
|
||||||
|
return new UploadTask({
|
||||||
|
abort: () => { },
|
||||||
|
onHeadersReceived: (callback: Function) => { },
|
||||||
|
offHeadersReceived: (callback: Function) => { },
|
||||||
|
onProgressUpdate: (callback: Function) => { },
|
||||||
|
offProgressUpdate: (callback: Function) => { },
|
||||||
|
} as IUploadTask)
|
||||||
|
}
|
||||||
|
|
||||||
|
const httpRequest = http.createHttp()
|
||||||
|
const mp = getCurrentMP()
|
||||||
|
const userAgent = mp.userAgent.fullUserAgent
|
||||||
|
if (userAgent && !headers['User-Agent'] && !headers['user-agent']) {
|
||||||
|
headers['User-Agent'] = userAgent
|
||||||
|
}
|
||||||
|
const emitter = new Emitter() as IUniUploadFileEmitter
|
||||||
|
const uploadTask: IUploadTask = {
|
||||||
|
abort() {
|
||||||
|
emitter.off('headersReceive')
|
||||||
|
emitter.off('progress')
|
||||||
|
httpRequest.destroy()
|
||||||
|
},
|
||||||
|
onHeadersReceived(callback: Function) {
|
||||||
|
emitter.on('headersReceive', callback)
|
||||||
|
},
|
||||||
|
offHeadersReceived(callback?: Function) {
|
||||||
|
emitter.off('headersReceive', callback)
|
||||||
|
},
|
||||||
|
onProgressUpdate(callback: Function) {
|
||||||
|
emitter.on('progress', callback)
|
||||||
|
},
|
||||||
|
offProgressUpdate(callback?: Function) {
|
||||||
|
emitter.off('progress', callback)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
function destroy() {
|
||||||
|
emitter.off('headersReceive')
|
||||||
|
emitter.off('progress')
|
||||||
|
httpRequest.destroy()
|
||||||
|
}
|
||||||
|
mp.on('beforeClose', destroy)
|
||||||
|
|
||||||
|
let lastUrl = url
|
||||||
|
httpRequest.on('headersReceive', (headers: Object) => {
|
||||||
|
const realHeaders = headers as Record<string, string | string[]>
|
||||||
|
const setCookieHeader = realHeaders['set-cookie'] || realHeaders['Set-Cookie']
|
||||||
|
if (setCookieHeader) {
|
||||||
|
setCookieSync(lastUrl, setCookieHeader as string[])
|
||||||
|
}
|
||||||
|
const location = realHeaders['location'] || realHeaders['Location']
|
||||||
|
if (location) {
|
||||||
|
lastUrl = location as string
|
||||||
|
}
|
||||||
|
// TODO headersReceive存在bug,暂不支持回调给用户。注意重定向时会多次触发,但是只需要给用户回调最后一次
|
||||||
|
// emitter.emit('headersReceive', header);
|
||||||
|
})
|
||||||
|
httpRequest.on('dataSendProgress', ({ sendSize, totalSize }) => {
|
||||||
|
emitter.emit('progress', {
|
||||||
|
progress: Math.floor((sendSize / totalSize) * 100),
|
||||||
|
totalBytesSent: sendSize,
|
||||||
|
totalBytesExpectedToSend: totalSize,
|
||||||
|
} as OnProgressUpdateResult)
|
||||||
|
})
|
||||||
|
|
||||||
|
const bufs = [] as buffer.Buffer[]
|
||||||
|
httpRequest.on('dataReceive', (data) => {
|
||||||
|
bufs.push(buffer.from(data))
|
||||||
|
})
|
||||||
|
httpRequest.requestInStream(
|
||||||
|
parseUrl(url),
|
||||||
|
{
|
||||||
|
header: headers,
|
||||||
|
method: http.RequestMethod.POST,
|
||||||
|
connectTimeout: timeout ? timeout : undefined, // 不支持仅设置一个timeout
|
||||||
|
readTimeout: timeout ? timeout : undefined,
|
||||||
|
multiFormDataList,
|
||||||
|
expectDataType: http.HttpDataType.STRING,
|
||||||
|
clientCert: getClientCertificate(url)
|
||||||
|
} as http.HttpRequestOptions,
|
||||||
|
(err, statusCode) => {
|
||||||
|
if (err) {
|
||||||
|
/**
|
||||||
|
* TODO abort后此处收到如下错误,待确认是否直接将此错误码转为abort错误
|
||||||
|
* {"code":2300023,"message":"Failed writing received data to disk/application"}
|
||||||
|
*/
|
||||||
|
exec.reject(err.message)
|
||||||
|
} else {
|
||||||
|
const responseData = buffer.concat(bufs)
|
||||||
|
exec.resolve({
|
||||||
|
data: responseData.toString('utf8'),
|
||||||
|
statusCode,
|
||||||
|
} as UniUploadFileSuccess)
|
||||||
|
}
|
||||||
|
uploadTask.offHeadersReceived()
|
||||||
|
uploadTask.offProgressUpdate()
|
||||||
|
httpRequest.destroy() // 调用完毕后必须调用destroy方法
|
||||||
|
mp.off('beforeClose', destroy)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return new UploadTask(uploadTask)
|
||||||
|
},
|
||||||
|
UploadFileApiProtocol,
|
||||||
|
UploadFileApiOptions
|
||||||
|
) as UploadFile
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import harmonyUrl from '@ohos.url'
|
||||||
|
import { http } from '@kit.NetworkKit'
|
||||||
|
import { Certificate } from '../../interface.uts'
|
||||||
|
import { getRealPath } from '@dcloudio/uni-runtime'
|
||||||
|
/**
|
||||||
|
* 鸿蒙url内包含中文时处理有问题
|
||||||
|
* 例如 /code/version/11?subject=中文测试 在部分服务器上会收到 /code/version/11?subject=é德䏿³
|
||||||
|
* 如下看起来很怪异的代码仅仅是为了绕过此Bug,待鸿蒙修复后可删除
|
||||||
|
*/
|
||||||
|
|
||||||
|
function needsEncoding(str: string) {
|
||||||
|
const decoded = decodeURIComponent(str);
|
||||||
|
if (decoded !== str) {
|
||||||
|
if (encodeURIComponent(decoded) === str) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return encodeURIComponent(decoded) !== decoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseUrl(url: string) {
|
||||||
|
try {
|
||||||
|
const urlObj = harmonyUrl.URL.parseURL(url);
|
||||||
|
urlObj.params.forEach((value, key) => {
|
||||||
|
if (needsEncoding(value)) {
|
||||||
|
urlObj.params.set(key, value);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return urlObj.toString()
|
||||||
|
} catch (error) {
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const certificates: Certificate[] = []
|
||||||
|
|
||||||
|
function getCertType(certPath: string): http.CertType {
|
||||||
|
const certExt = certPath.split('.').pop()
|
||||||
|
switch (certExt) {
|
||||||
|
case 'p12':
|
||||||
|
return http.CertType.P12
|
||||||
|
case 'pem':
|
||||||
|
return http.CertType.PEM
|
||||||
|
default:
|
||||||
|
return http.CertType.PEM
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getClientCertificate(url: string): http.ClientCert | undefined {
|
||||||
|
if (certificates.length === 0) return undefined
|
||||||
|
const urlObj = harmonyUrl.URL.parseURL(url);
|
||||||
|
const cert = certificates.find((certificate) => certificate.host === urlObj.host)
|
||||||
|
if (cert) {
|
||||||
|
return {
|
||||||
|
certType: getCertType(cert.client!),
|
||||||
|
certPath: getRealPath(cert.client!),
|
||||||
|
keyPath: cert.keyPath ?? '',
|
||||||
|
keyPassword: cert.clientPassword
|
||||||
|
} as http.ClientCert
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
@@ -0,0 +1,417 @@
|
|||||||
|
import { Request, RequestOptions, RequestSuccess, RequestFail, RequestTask, UploadFileOptions, UploadFile, UploadTask, OnProgressUpdateResult, UploadFileSuccess, UploadFileProgressUpdateCallback, DownloadFileProgressUpdateCallback, DownloadFileOptions, OnProgressDownloadResult, DownloadFile, DownloadFileSuccess } from '../interface';
|
||||||
|
import { NetworkManager, NetworkRequestListener, NetworkUploadFileListener, NetworkDownloadFileListener } from './network/NetworkManager.uts'
|
||||||
|
import { Data, HTTPURLResponse, NSError, NSNumber, ComparisonResult, RunLoop, Thread, JSONSerialization } from 'Foundation';
|
||||||
|
import { StatusCode } from './network/StatusCode.uts';
|
||||||
|
import { RequestFailImpl, UploadFileFailImpl, DownloadFileFailImpl, getErrcode } from '../unierror';
|
||||||
|
import { NetworkUtil } from './network/utils/NetworkUtil.uts';
|
||||||
|
import { CFString } from 'CoreFoundation';
|
||||||
|
class SimpleNetworkListener<T> extends NetworkRequestListener {
|
||||||
|
private param : RequestOptions<T> | null = null;
|
||||||
|
private headers : Map<string, any> | null = null;
|
||||||
|
private received : number = 0;
|
||||||
|
private data : Data = new Data();
|
||||||
|
@UTSiOS.keyword("weak")
|
||||||
|
public override task : AnyObject | null = null;
|
||||||
|
constructor(param : RequestOptions<T>) {
|
||||||
|
this.param = param;
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override onStart() : void {
|
||||||
|
}
|
||||||
|
|
||||||
|
public override onHeadersReceived(statusCode : number, headers : Map<string, any>) : void {
|
||||||
|
this.headers = headers;
|
||||||
|
|
||||||
|
}
|
||||||
|
public override onDataReceived(data : Data) : void {
|
||||||
|
this.received += Number.from(data.count);
|
||||||
|
this.data.append(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override onFinished(response : HTTPURLResponse) : void {
|
||||||
|
try {
|
||||||
|
let headers = response.allHeaderFields as Map<string, any>;
|
||||||
|
let kParam = this.param;
|
||||||
|
let result = {};
|
||||||
|
result['statusCode'] = response.statusCode;
|
||||||
|
result['statusText'] = StatusCode.getStatus(new String(response.statusCode));
|
||||||
|
if (headers != null) {
|
||||||
|
result['header'] = headers;
|
||||||
|
}
|
||||||
|
let strData = this.readStringFromData(this.data, response.textEncodingName);
|
||||||
|
|
||||||
|
|
||||||
|
let type = kParam?.responseType != null ? kParam?.responseType : kParam?.dataType;
|
||||||
|
|
||||||
|
if (type == null && headers != null) {
|
||||||
|
|
||||||
|
for (entry in headers) {
|
||||||
|
let key = entry.key;
|
||||||
|
if (key.caseInsensitiveCompare("Content-Type") == ComparisonResult.orderedSame) {
|
||||||
|
type = headers[key] as string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
result['data'] = this.parseData(this.data, strData, type);
|
||||||
|
|
||||||
|
if (result['data'] == null) {
|
||||||
|
let failResult = new RequestFailImpl(getErrcode(100001));
|
||||||
|
let fail = kParam?.fail;
|
||||||
|
let complete = kParam?.complete;
|
||||||
|
fail?.(failResult);
|
||||||
|
complete?.(failResult);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let tmp = new RequestSuccess<T>({
|
||||||
|
data: result['data']! as T,
|
||||||
|
statusCode: (new NSNumber(value = response.statusCode)),
|
||||||
|
header: result['header'] ?? "",
|
||||||
|
cookies: this.parseCookie(this.headers)
|
||||||
|
})
|
||||||
|
let success = kParam?.success;
|
||||||
|
let complete = kParam?.complete;
|
||||||
|
success?.(tmp);
|
||||||
|
complete?.(tmp);
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
if (this.task != null) {
|
||||||
|
UTSiOS.destroyInstance(this.task!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override onFail(error : NSError) : void {
|
||||||
|
let kParam = this.param;
|
||||||
|
let code = (error as NSError).code;
|
||||||
|
let errCode = code;
|
||||||
|
let cause = error.localizedDescription;
|
||||||
|
if (code == -1001) {
|
||||||
|
errCode = 5;
|
||||||
|
} else if (code == -1004) {
|
||||||
|
errCode = 1000;
|
||||||
|
} else if (code == -1009) {
|
||||||
|
errCode = 600003;
|
||||||
|
} else {
|
||||||
|
errCode = 602001;
|
||||||
|
}
|
||||||
|
|
||||||
|
let failResult = new RequestFailImpl(getErrcode(Number.from(errCode)));
|
||||||
|
failResult.cause = new SourceError(cause);
|
||||||
|
|
||||||
|
let fail = kParam?.fail;
|
||||||
|
let complete = kParam?.complete;
|
||||||
|
fail?.(failResult);
|
||||||
|
complete?.(failResult);
|
||||||
|
if (this.task != null) {
|
||||||
|
UTSiOS.destroyInstance(this.task!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private readStringFromData(data : Data, type : string | null) : string | null {
|
||||||
|
let result : string | null = null;
|
||||||
|
let finalType = type;
|
||||||
|
if (finalType == null || finalType!.length == 0) {
|
||||||
|
finalType = "utf-8";
|
||||||
|
}
|
||||||
|
|
||||||
|
let cfEncoding = CFStringConvertIANACharSetNameToEncoding(finalType as CFString);
|
||||||
|
if (cfEncoding != kCFStringEncodingInvalidId) {
|
||||||
|
let stringEncoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
|
||||||
|
let encode = new String.Encoding(rawValue = stringEncoding);
|
||||||
|
result = new String(data = data, encoding = encode);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseData(data : Data | null, dataStr : string | null, parseType : string | null) : any | null {
|
||||||
|
if (`${type(of = T.self)}` == "Any.Protocol" || `${type(of = T.self)}` == "Optional<Any>.Type") {
|
||||||
|
if (parseType != null && parseType!.contains("json")) {
|
||||||
|
if (dataStr == null || dataStr!.length == 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.tryParseJson(data!, dataStr!);
|
||||||
|
} else if (parseType == 'jsonp') {
|
||||||
|
if (dataStr == null || dataStr!.length == 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
let start = dataStr!.indexOf('(');
|
||||||
|
let end = dataStr!.indexOf(')');
|
||||||
|
if (start == 0 || start >= end) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
start += 1;
|
||||||
|
let tmp = dataStr!.slice(start, end);
|
||||||
|
return this.tryParseJson(data!, tmp);
|
||||||
|
} else if (parseType == 'arraybuffer') {
|
||||||
|
if (data == null) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
return ArrayBuffer.fromData(data!)
|
||||||
|
} else {
|
||||||
|
//dataStr如果解码失败是空的时候,还需要继续尝试解码。极端情况,服务器不是utf8的,所以字符解码会出现乱码,所以特殊处理一下非utf8的字符。
|
||||||
|
if (data == null) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentStr : string | null = dataStr;
|
||||||
|
//todo 等uts支持swift文件混编的时候,再进行处理。
|
||||||
|
// if (currentStr == null) {
|
||||||
|
// let data = cleanUTF8(data);
|
||||||
|
// if (data != null) {
|
||||||
|
// currentStr = new String(data = data, encoding = String.Encoding.utf8);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (currentStr == null) {
|
||||||
|
currentStr = new String(data = data!, encoding = String.Encoding.utf8);
|
||||||
|
}
|
||||||
|
// utf8 如果失败了,就用ascii,虽然几率很小。
|
||||||
|
if (currentStr == null) {
|
||||||
|
currentStr = new String(data = data!, encoding = String.Encoding.ascii);
|
||||||
|
}
|
||||||
|
return this.tryParseJson(data!, currentStr!) ?? currentStr;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (dataStr == null || dataStr!.length == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return JSON.parse<T>(dataStr!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 尝试进行json解析,如果失败了就说明不是json数据
|
||||||
|
*/
|
||||||
|
private tryParseJson(data : Data, jsonStr : string) : any | null {
|
||||||
|
let jsonData : any | null = null;
|
||||||
|
try {
|
||||||
|
const res = UTSiOS.try(JSONSerialization.jsonObject(with = data, options = JSONSerialization.ReadingOptions.allowFragments), "?")
|
||||||
|
if (res != null) {
|
||||||
|
jsonData = JSON.parse(jsonStr)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
return jsonData;
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseCookie(header : Map<string, any> | null) : string[] {
|
||||||
|
if (header == null) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
let cookiesStr = header!.get('Set-Cookie') as string | null
|
||||||
|
if (cookiesStr == null) {
|
||||||
|
cookiesStr = header!.get('set-cookie') as string | null
|
||||||
|
}
|
||||||
|
if (cookiesStr == null) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
let cookiesArr = new Array<string>()
|
||||||
|
if (cookiesStr!.charAt(0) == "[" && cookiesStr!.charAt(cookiesStr!.length - 1) == "]") {
|
||||||
|
cookiesStr = cookiesStr!.slice(1, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCookiesArr = cookiesStr!.split(';')
|
||||||
|
for (let i = 0; i < handleCookiesArr.length; i++) {
|
||||||
|
if (handleCookiesArr[i].indexOf('Expires=') != -1 || handleCookiesArr[i].indexOf('expires=') != -1) {
|
||||||
|
cookiesArr.push(handleCookiesArr[i].replace(',', ''))
|
||||||
|
} else {
|
||||||
|
cookiesArr.push(handleCookiesArr[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cookiesArr = cookiesArr.join(';').split(',')
|
||||||
|
|
||||||
|
return cookiesArr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UploadNetworkListener extends NetworkUploadFileListener {
|
||||||
|
private param : UploadFileOptions | null = null;
|
||||||
|
public override progressListeners : Array<UploadFileProgressUpdateCallback> = [];
|
||||||
|
private data : Data = new Data();
|
||||||
|
@UTSiOS.keyword("weak")
|
||||||
|
public override task : AnyObject | null = null;
|
||||||
|
|
||||||
|
constructor(param : UploadFileOptions) {
|
||||||
|
this.param = param;
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override onProgress(progressUpdate : OnProgressUpdateResult) {
|
||||||
|
if (this.progressListeners.length != 0) {
|
||||||
|
for (let i = 0; i < this.progressListeners.length; i++) {
|
||||||
|
let listener = this.progressListeners[i];
|
||||||
|
listener(progressUpdate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override onDataReceived(data : Data) : void {
|
||||||
|
this.data.append(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override onFinished(response : HTTPURLResponse) : void {
|
||||||
|
try {
|
||||||
|
let kParam = this.param;
|
||||||
|
let strData = this.readStringFromData(this.data, response.textEncodingName);
|
||||||
|
if (strData == null) {
|
||||||
|
strData = new String(data = this.data, encoding = String.Encoding.utf8);
|
||||||
|
// utf8 如果失败了,就用ascii,几率很小。
|
||||||
|
if (strData == null) {
|
||||||
|
strData = new String(data = this.data, encoding = String.Encoding.ascii);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let successResult : UploadFileSuccess = {
|
||||||
|
data: strData ?? "",
|
||||||
|
statusCode: response.statusCode
|
||||||
|
}
|
||||||
|
let success = kParam?.success;
|
||||||
|
let complete = kParam?.complete;
|
||||||
|
success?.(successResult);
|
||||||
|
complete?.(successResult);
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
this.progressListeners.splice(0, this.progressListeners.length)
|
||||||
|
if (this.task != null) {
|
||||||
|
UTSiOS.destroyInstance(this.task!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override onFail(error : NSError) : void {
|
||||||
|
let kParam = this.param;
|
||||||
|
let code = (error as NSError).code;
|
||||||
|
let errCode = code;
|
||||||
|
let cause = error.localizedDescription;
|
||||||
|
if (code == -1001) {
|
||||||
|
errCode = 5;
|
||||||
|
} else if (code == -1004) {
|
||||||
|
errCode = 1000;
|
||||||
|
} else if (code == -1009) {
|
||||||
|
errCode = 600003;
|
||||||
|
} else {
|
||||||
|
errCode = 602001;
|
||||||
|
}
|
||||||
|
|
||||||
|
let failResult = new UploadFileFailImpl(getErrcode(Number.from(errCode)));
|
||||||
|
failResult.cause = new SourceError(cause);
|
||||||
|
let fail = kParam?.fail;
|
||||||
|
let complete = kParam?.complete;
|
||||||
|
fail?.(failResult);
|
||||||
|
complete?.(failResult);
|
||||||
|
this.progressListeners.splice(0, this.progressListeners.length)
|
||||||
|
if (this.task != null) {
|
||||||
|
UTSiOS.destroyInstance(this.task!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private readStringFromData(data : Data, type : string | null) : string | null {
|
||||||
|
let result : string | null = null;
|
||||||
|
let finalType = type;
|
||||||
|
if (finalType == null || finalType!.length == 0) {
|
||||||
|
finalType = "utf-8";
|
||||||
|
}
|
||||||
|
|
||||||
|
let cfEncoding = CFStringConvertIANACharSetNameToEncoding(finalType as CFString);
|
||||||
|
if (cfEncoding != kCFStringEncodingInvalidId) {
|
||||||
|
let stringEncoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
|
||||||
|
let encode = new String.Encoding(rawValue = stringEncoding);
|
||||||
|
result = new String(data = data, encoding = encode);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DownloadNetworkListener extends NetworkDownloadFileListener {
|
||||||
|
public override options : DownloadFileOptions | null = null;
|
||||||
|
public override progressListeners : Array<DownloadFileProgressUpdateCallback> = [];
|
||||||
|
private data : Data = new Data();
|
||||||
|
@UTSiOS.keyword("weak")
|
||||||
|
public override task : AnyObject | null = null;
|
||||||
|
|
||||||
|
constructor(options : DownloadFileOptions) {
|
||||||
|
super();
|
||||||
|
this.options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override onProgress(progressUpdate : OnProgressDownloadResult) {
|
||||||
|
if (this.progressListeners.length != 0) {
|
||||||
|
for (let i = 0; i < this.progressListeners.length; i++) {
|
||||||
|
let listener = this.progressListeners[i];
|
||||||
|
listener(progressUpdate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override onFinished(response : HTTPURLResponse, filePath : string) : void {
|
||||||
|
try {
|
||||||
|
let kParam = this.options;
|
||||||
|
let tmp : DownloadFileSuccess = {
|
||||||
|
tempFilePath: filePath,
|
||||||
|
statusCode: response.statusCode
|
||||||
|
};
|
||||||
|
let success = kParam?.success;
|
||||||
|
let complete = kParam?.complete;
|
||||||
|
success?.(tmp);
|
||||||
|
complete?.(tmp);
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
this.progressListeners.splice(0, this.progressListeners.length)
|
||||||
|
if (this.task != null) {
|
||||||
|
UTSiOS.destroyInstance(this.task!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override onFail(error : NSError) : void {
|
||||||
|
let kParam = this.options;
|
||||||
|
let code = (error as NSError).code;
|
||||||
|
let errCode = code;
|
||||||
|
let cause = error.localizedDescription;
|
||||||
|
if (code == -1001) {
|
||||||
|
errCode = 5;
|
||||||
|
} else if (code == -1004) {
|
||||||
|
errCode = 1000;
|
||||||
|
} else if (code == -1009) {
|
||||||
|
errCode = 600003;
|
||||||
|
} else {
|
||||||
|
errCode = 602001;
|
||||||
|
}
|
||||||
|
|
||||||
|
let failResult = new DownloadFileFailImpl(getErrcode(Number.from(errCode)));
|
||||||
|
let codeCause = StatusCode.getStatus(new String(code))
|
||||||
|
const sourceError = new SourceError(codeCause == "unknown status" ? cause : codeCause);
|
||||||
|
sourceError.code = Number.from(code);
|
||||||
|
failResult.cause = sourceError;
|
||||||
|
let fail = kParam?.fail;
|
||||||
|
let complete = kParam?.complete;
|
||||||
|
fail?.(failResult);
|
||||||
|
complete?.(failResult);
|
||||||
|
this.progressListeners.splice(0, this.progressListeners.length)
|
||||||
|
if (this.task != null) {
|
||||||
|
UTSiOS.destroyInstance(this.task!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// export const request : Request = (options : RequestOptions) : RequestTask => {
|
||||||
|
// return NetworkManager.getInstance().request(options, new SimpleNetworkListener(options));
|
||||||
|
// }
|
||||||
|
|
||||||
|
export function request<T>(options : RequestOptions<T>, _t : T.Type) : RequestTask {
|
||||||
|
return NetworkManager.getInstance().request(options, new SimpleNetworkListener<T>(options));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const uploadFile : UploadFile = (options : UploadFileOptions) : UploadTask => {
|
||||||
|
return NetworkManager.getInstance().uploadFile(options, new UploadNetworkListener(options));
|
||||||
|
}
|
||||||
|
|
||||||
|
export const downloadFile : DownloadFile = (options : DownloadFileOptions) : DownloadTask => {
|
||||||
|
return NetworkManager.getInstance().downloadFile(options, new DownloadNetworkListener(options));
|
||||||
|
}
|
||||||
@@ -0,0 +1,412 @@
|
|||||||
|
import {
|
||||||
|
RequestOptions,
|
||||||
|
RequestTask,
|
||||||
|
UploadTask,
|
||||||
|
UploadFileOptions,
|
||||||
|
OnProgressUpdateResult,
|
||||||
|
UploadFileProgressUpdateCallback,
|
||||||
|
OnProgressDownloadResult,
|
||||||
|
DownloadTask,
|
||||||
|
DownloadFileOptions,
|
||||||
|
DownloadFileProgressUpdateCallback,
|
||||||
|
RequestTaskOnHeadersReceivedCallback,
|
||||||
|
RequestTaskOnHeadersReceivedListenerResult,
|
||||||
|
RequestTaskOnChunkReceivedCallback,
|
||||||
|
RequestTaskOnChunkReceivedListenerResult
|
||||||
|
}
|
||||||
|
from '../../interface.uts';
|
||||||
|
import { UTSiOS } from "DCloudUTSFoundation";
|
||||||
|
import { NSNumber, URLSessionDataDelegate, URL, CharacterSet, URLSession, URLSessionConfiguration, OperationQueue, URLSessionTask, URLResponse, URLSessionDataTask, URLAuthenticationChallengeSender, URLAuthenticationChallenge, URLCredential, URLSessionTaskMetrics, Data, HTTPURLResponse, NSError, URLRequest, ComparisonResult } from 'Foundation';
|
||||||
|
import { UploadController } from './upload/UploadController.uts';
|
||||||
|
import { DownloadController } from './download/DownloadController.uts';
|
||||||
|
import { Int } from 'Swift';
|
||||||
|
import { NetworkUtil } from './utils/NetworkUtil.uts';
|
||||||
|
|
||||||
|
class NetworkRequestListener {
|
||||||
|
@UTSiOS.keyword("weak")
|
||||||
|
public task: AnyObject | null = null;
|
||||||
|
|
||||||
|
public onStart() : void { }
|
||||||
|
|
||||||
|
public onHeadersReceived(statusCode : number, headers : Map<string, any>) : void { }
|
||||||
|
|
||||||
|
public onDataReceived(data : Data) : void { }
|
||||||
|
|
||||||
|
public onFinished(response : HTTPURLResponse) : void { }
|
||||||
|
|
||||||
|
public onFail(error : NSError) : void { }
|
||||||
|
}
|
||||||
|
|
||||||
|
class NetworkUploadFileListener {
|
||||||
|
@UTSiOS.keyword("weak")
|
||||||
|
public task: AnyObject | null = null;
|
||||||
|
public progressListeners : Array<UploadFileProgressUpdateCallback> = new Array<UploadFileProgressUpdateCallback>();
|
||||||
|
public onProgress(progressUpdate : OnProgressUpdateResult) : void { };
|
||||||
|
public onDataReceived(data : Data) : void { };
|
||||||
|
public onFinished(response : HTTPURLResponse) : void { };
|
||||||
|
public onFail(error : NSError) : void { };
|
||||||
|
}
|
||||||
|
|
||||||
|
class NetworkDownloadFileListener {
|
||||||
|
@UTSiOS.keyword("weak")
|
||||||
|
public task: AnyObject | null = null;
|
||||||
|
public options : DownloadFileOptions | null = null;
|
||||||
|
public progressListeners : Array<DownloadFileProgressUpdateCallback> = new Array<DownloadFileProgressUpdateCallback>();
|
||||||
|
public onProgress(progressUpdate : OnProgressDownloadResult) : void { };
|
||||||
|
public onFinished(response : HTTPURLResponse, filePath : string) : void { };
|
||||||
|
public onFail(error : NSError) : void { };
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class NetworkRequestTaskImpl implements RequestTask {
|
||||||
|
public headersReceivedListeners = new Map<number, RequestTaskOnHeadersReceivedCallback>()
|
||||||
|
public chunkReceivedListeners = new Map<number, RequestTaskOnChunkReceivedCallback>()
|
||||||
|
|
||||||
|
private requestTaskOnHeadersReceivedCallbackCount: number = 0
|
||||||
|
private requestTaskOnChunkReceivedCallbackCount: number = 0
|
||||||
|
private semaphore = DispatchSemaphore(value = 1)
|
||||||
|
|
||||||
|
private task : URLSessionDataTask | null = null;
|
||||||
|
constructor(task : URLSessionDataTask | null) {
|
||||||
|
this.task = task;
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public abort() {
|
||||||
|
this.task?.cancel()
|
||||||
|
UTSiOS.destroyInstance(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
public onHeadersReceived(listener: RequestTaskOnHeadersReceivedCallback): number {
|
||||||
|
this.semaphore.wait()
|
||||||
|
this.requestTaskOnHeadersReceivedCallbackCount += 1
|
||||||
|
this.semaphore.signal()
|
||||||
|
this.headersReceivedListeners.set(this.requestTaskOnHeadersReceivedCallbackCount, listener)
|
||||||
|
return this.requestTaskOnHeadersReceivedCallbackCount
|
||||||
|
}
|
||||||
|
|
||||||
|
public offHeadersReceived(listener ?: number | RequestTaskOnHeadersReceivedCallback | null): void {
|
||||||
|
if (listener != null && typeof listener! == "number") {
|
||||||
|
const id = listener as number
|
||||||
|
this.headersReceivedListeners.delete(id)
|
||||||
|
} else {
|
||||||
|
this.headersReceivedListeners.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public onChunkReceived(listener: RequestTaskOnChunkReceivedCallback): number {
|
||||||
|
this.semaphore.wait()
|
||||||
|
this.requestTaskOnChunkReceivedCallbackCount += 1
|
||||||
|
this.semaphore.signal()
|
||||||
|
this.chunkReceivedListeners.set(this.requestTaskOnChunkReceivedCallbackCount, listener)
|
||||||
|
return this.requestTaskOnChunkReceivedCallbackCount
|
||||||
|
}
|
||||||
|
|
||||||
|
public offChunkReceived(listener ?: number | RequestTaskOnChunkReceivedCallback | null): void {
|
||||||
|
if (listener != null && typeof listener! == "number") {
|
||||||
|
const id = listener as number
|
||||||
|
this.chunkReceivedListeners.delete(id)
|
||||||
|
} else {
|
||||||
|
this.chunkReceivedListeners.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NetworkManager implements URLSessionDataDelegate {
|
||||||
|
private static instance : NetworkManager | null = null;
|
||||||
|
private enableChunked: boolean = false;
|
||||||
|
private session : URLSession | null = null;
|
||||||
|
|
||||||
|
private taskMap : Map<URLSessionTask, NetworkRequestListener> = new Map<URLSessionDataTask, NetworkRequestListener>();
|
||||||
|
|
||||||
|
public static getInstance() : NetworkManager {
|
||||||
|
if (this.instance == null) {
|
||||||
|
this.instance = new NetworkManager();
|
||||||
|
}
|
||||||
|
return this.instance!;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public request<T>(param : RequestOptions<T>, listener : NetworkRequestListener) : RequestTask {
|
||||||
|
let request = this.createRequest(param);
|
||||||
|
this.enableChunked = param.enableChunked ?? false
|
||||||
|
if (request == null) {
|
||||||
|
let error = new NSError(domain = "invalid URL", code = 600009);
|
||||||
|
listener.onFail(error);
|
||||||
|
let task = new NetworkRequestTaskImpl(null);
|
||||||
|
listener.task = task;
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.session == null) {
|
||||||
|
let urlSessionConfig = URLSessionConfiguration.default;
|
||||||
|
|
||||||
|
this.session = new URLSession(configuration = urlSessionConfig, delegate = this, delegateQueue = OperationQueue.current);
|
||||||
|
}
|
||||||
|
let task = this.session?.dataTask(with = request!);
|
||||||
|
task?.resume();
|
||||||
|
if (task != null) {
|
||||||
|
this.taskMap.set(task!, listener);
|
||||||
|
}
|
||||||
|
let requestTask = new NetworkRequestTaskImpl(task);
|
||||||
|
listener.task = requestTask;
|
||||||
|
return requestTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public uploadFile(options : UploadFileOptions, listener : NetworkUploadFileListener) : UploadTask {
|
||||||
|
return UploadController.getInstance().uploadFile(options, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public downloadFile(options : DownloadFileOptions, listener : NetworkDownloadFileListener) : DownloadTask {
|
||||||
|
return DownloadController.getInstance().downloadFile(options, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public createRequest<T>(param : RequestOptions<T>) : URLRequest | null {
|
||||||
|
const encodeUrl = this.percentEscapedString(param.url)
|
||||||
|
let url = new URL(string = encodeUrl);
|
||||||
|
if (url == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let timeout = param.timeout == null ? 120000 : param.timeout;
|
||||||
|
let timeoutInterval = new Double(timeout!) / 1000;
|
||||||
|
let request = new URLRequest(url = url!, cachePolicy = URLRequest.CachePolicy.useProtocolCachePolicy, timeoutInterval = timeoutInterval);
|
||||||
|
request.httpShouldHandleCookies = true;
|
||||||
|
let method = param.method;
|
||||||
|
if (method == null || method!.trimmingCharacters(in = CharacterSet.whitespacesAndNewlines).count == 0) {
|
||||||
|
method = "GET";
|
||||||
|
}
|
||||||
|
request.httpMethod = method!;
|
||||||
|
|
||||||
|
let ua = UTSiOS.getUserAgent();
|
||||||
|
request.setValue(ua, forHTTPHeaderField = "User-Agent");
|
||||||
|
|
||||||
|
if (param.header == null) {
|
||||||
|
param.header = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let headers = param.header?.toMap();
|
||||||
|
let hasContentType = false;
|
||||||
|
if (headers != null) {
|
||||||
|
for (entry in headers!) {
|
||||||
|
let key = entry.key;
|
||||||
|
let value = entry.value;
|
||||||
|
if (key.caseInsensitiveCompare("Content-Type") == ComparisonResult.orderedSame) {
|
||||||
|
hasContentType = true;
|
||||||
|
}
|
||||||
|
let valueStr = "";
|
||||||
|
if (value instanceof UTSJSONObject) {
|
||||||
|
valueStr = JSON.stringify(value) ?? ""
|
||||||
|
} else if (value instanceof Map<string, any>) {
|
||||||
|
valueStr = JSON.stringify(new UTSJSONObject(value)) ?? ""
|
||||||
|
} else {
|
||||||
|
valueStr = `${value}`
|
||||||
|
}
|
||||||
|
request.setValue(valueStr, forHTTPHeaderField = key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasContentType) {
|
||||||
|
if ("GET" != method) {
|
||||||
|
request.setValue("application/json", forHTTPHeaderField = "Content-Type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ("GET" == method) {
|
||||||
|
const data = param.data;
|
||||||
|
if (data != null) {
|
||||||
|
let json : UTSJSONObject | null = null;
|
||||||
|
if (typeof (data) == 'string') {
|
||||||
|
json = JSON.parseObject(data as string);
|
||||||
|
} else if (data instanceof UTSJSONObject) {
|
||||||
|
json = data as UTSJSONObject;
|
||||||
|
} else if (data instanceof Map<string, any>) {
|
||||||
|
json = new UTSJSONObject(data!)
|
||||||
|
}
|
||||||
|
if (json != null) {
|
||||||
|
let urlWithQuery = this.stringifyQuery(encodeUrl, json!)
|
||||||
|
let url = new URL(string = urlWithQuery);
|
||||||
|
request.url = url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (param.data != null) {
|
||||||
|
let bodyData : Data | null = null;
|
||||||
|
if (typeof (param.data) == 'string') {
|
||||||
|
bodyData = (param.data as string).data(using = String.Encoding.utf8);
|
||||||
|
} else if (param.data instanceof Map<string, any>) {
|
||||||
|
let body : string | null = "";
|
||||||
|
const contentType = request.value(forHTTPHeaderField = "Content-Type")
|
||||||
|
const data = new UTSJSONObject(param.data!);
|
||||||
|
if (contentType != null) {
|
||||||
|
if (contentType!.indexOf("application/x-www-form-urlencoded") == 0) {
|
||||||
|
const map : Map<string, any | null> = data.toMap();
|
||||||
|
const bodyArray = new Array<string>();
|
||||||
|
map.forEach((value, key) => {
|
||||||
|
bodyArray.push(key + "=" + `${value ?? "null"}`);
|
||||||
|
})
|
||||||
|
body = bodyArray.join("&");
|
||||||
|
} else {
|
||||||
|
body = JSON.stringify(data);
|
||||||
|
}
|
||||||
|
bodyData = body?.data(using = String.Encoding.utf8);
|
||||||
|
}
|
||||||
|
} else if (param.data instanceof ArrayBuffer) {
|
||||||
|
const arrayBuffer = param.data as ArrayBuffer
|
||||||
|
bodyData = arrayBuffer.toData()
|
||||||
|
} else if (param.data instanceof UTSJSONObject) {
|
||||||
|
let body : string | null = "";
|
||||||
|
const contentType = request.value(forHTTPHeaderField = "Content-Type")
|
||||||
|
if (contentType != null) {
|
||||||
|
if (contentType!.indexOf("application/x-www-form-urlencoded") == 0) {
|
||||||
|
const data = param.data as UTSJSONObject;
|
||||||
|
const map : Map<string, any | null> = data.toMap();
|
||||||
|
const bodyArray = new Array<string>();
|
||||||
|
map.forEach((value, key) => {
|
||||||
|
bodyArray.push(key + "=" + `${value ?? "null"}`);
|
||||||
|
})
|
||||||
|
body = bodyArray.join("&");
|
||||||
|
} else {
|
||||||
|
body = JSON.stringify(param.data);
|
||||||
|
}
|
||||||
|
bodyData = body?.data(using = String.Encoding.utf8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (bodyData == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
request.httpBody = bodyData;
|
||||||
|
}
|
||||||
|
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* data拼接到url上
|
||||||
|
*/
|
||||||
|
private stringifyQuery(url : string, data : UTSJSONObject) : string {
|
||||||
|
let newUrl = url;
|
||||||
|
//http:xxx/xxx?a=b&c=d#123
|
||||||
|
let str = url.split('#')
|
||||||
|
let hash = ''
|
||||||
|
if (str.length > 1) {
|
||||||
|
hash = str[1] //123
|
||||||
|
}
|
||||||
|
str = str[0].split('?')
|
||||||
|
let query = ''
|
||||||
|
if (str.length > 1) {
|
||||||
|
query = str[1] //a=b&c=d
|
||||||
|
}
|
||||||
|
newUrl = str[0] // http:xxx/xxx
|
||||||
|
const pairs = query.split('&')
|
||||||
|
const queryMap = new Map<string, string>();
|
||||||
|
pairs.forEach((item : string, index : number) => {
|
||||||
|
const temp = item.split('=')
|
||||||
|
if (temp.length > 1) {
|
||||||
|
queryMap[temp[0]] = temp[1]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const dataMap : Map<string, any | null> = data.toMap();
|
||||||
|
dataMap.forEach((value, key) => {
|
||||||
|
if (value == null) {
|
||||||
|
value = "";
|
||||||
|
}
|
||||||
|
let encodeKey = encodeURIComponent(key)!
|
||||||
|
if (value instanceof UTSJSONObject || value instanceof Array<any | null>) {
|
||||||
|
queryMap[encodeKey] = encodeURIComponent(JSON.stringify(value)!)!
|
||||||
|
} else if (value instanceof Map<string, any>) {
|
||||||
|
queryMap[encodeKey] = encodeURIComponent(JSON.stringify(new UTSJSONObject(value))!)!
|
||||||
|
} else {
|
||||||
|
queryMap[encodeKey] = encodeURIComponent(`${value!}`)!;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
let queryStr = "";
|
||||||
|
queryMap.forEach((value, key) => {
|
||||||
|
queryStr += key + "=" + value + "&"
|
||||||
|
});
|
||||||
|
|
||||||
|
queryStr = queryStr.slice(0, -1);
|
||||||
|
if (queryStr.length > 0) {
|
||||||
|
newUrl += "?" + queryStr;
|
||||||
|
}
|
||||||
|
if (hash.length > 0) {
|
||||||
|
newUrl += "#" + hash;
|
||||||
|
}
|
||||||
|
return newUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
private percentEscapedString(str : string) : string {
|
||||||
|
//如果url已经有部分经过encode,那么需要先decode再encode。
|
||||||
|
return str.removingPercentEncoding?.addingPercentEncoding(withAllowedCharacters = CharacterSet.urlQueryAllowed) ?? str
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//mark --- URLSessionDataDelegate
|
||||||
|
|
||||||
|
urlSession(session : URLSession, @argumentLabel("") task : URLSessionTask, @argumentLabel("didSendBodyData") bytesSent : Int64, @argumentLabel("") totalBytesSent : Int64, @argumentLabel("") totalBytesExpectedToSend : Int64) {
|
||||||
|
//todo 原生的onDataSent貌似没实现 ,考虑删掉这个回调。
|
||||||
|
}
|
||||||
|
urlSession(session : URLSession, @argumentLabel("") dataTask : URLSessionDataTask, @argumentLabel("didReceive") response : URLResponse, @argumentLabel("") @escaping completionHandler : (dis : URLSession.ResponseDisposition) => void) {
|
||||||
|
// response开始的时候的header回调
|
||||||
|
let listener = this.taskMap.get(dataTask);
|
||||||
|
if (listener != null) {
|
||||||
|
let httpResponse : HTTPURLResponse = response as HTTPURLResponse;
|
||||||
|
let statusCode = new NSNumber(value = httpResponse.statusCode);
|
||||||
|
listener?.onHeadersReceived(statusCode, httpResponse.allHeaderFields as Map<string, any>);
|
||||||
|
|
||||||
|
const headers = NetworkUtil.convertHeaders(httpResponse.allHeaderFields as Map<string, any>))
|
||||||
|
if (listener!.task != null && listener!.task instanceof NetworkRequestTaskImpl) {
|
||||||
|
const task = listener!.task! as NetworkRequestTaskImpl
|
||||||
|
task.headersReceivedListeners.forEach((value : RequestTaskOnHeadersReceivedCallback, key : number) => {
|
||||||
|
const result: RequestTaskOnHeadersReceivedListenerResult = {
|
||||||
|
cookies: NetworkUtil.parseCookie(headers),
|
||||||
|
header: headers,
|
||||||
|
statusCode: statusCode
|
||||||
|
}
|
||||||
|
value(result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
completionHandler(URLSession.ResponseDisposition.allow);
|
||||||
|
}
|
||||||
|
|
||||||
|
urlSession(session : URLSession, @argumentLabel("") dataTask : URLSessionDataTask, @argumentLabel("didReceive") data : Data) {
|
||||||
|
let listener = this.taskMap.get(dataTask);
|
||||||
|
listener?.onDataReceived(data);
|
||||||
|
if (this.enableChunked && listener != null) {
|
||||||
|
const task = listener!.task! as NetworkRequestTaskImpl
|
||||||
|
task.chunkReceivedListeners.forEach((value : RequestTaskOnChunkReceivedCallback, key : number) => {
|
||||||
|
const result : RequestTaskOnChunkReceivedListenerResult = {
|
||||||
|
data: ArrayBuffer.fromData(data)
|
||||||
|
}
|
||||||
|
value(result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
urlSession(session : URLSession, @argumentLabel("") task : URLSessionTask, @argumentLabel("didCompleteWithError") error : NSError | null) {
|
||||||
|
let listener = this.taskMap.get(task);
|
||||||
|
|
||||||
|
if (error != null) {
|
||||||
|
listener?.onFail(error as NSError);
|
||||||
|
} else {
|
||||||
|
listener?.onFinished(task.response as HTTPURLResponse);
|
||||||
|
}
|
||||||
|
this.taskMap.delete(task);
|
||||||
|
}
|
||||||
|
//todo 暂时证书验证先不实现。
|
||||||
|
// urlSession( session: URLSession, @argumentLabel("didReceive") challenge: URLAuthenticationChallenge, @escaping completionHandler:(dis:URLSession.AuthChallengeDisposition, credentiual:URLCredential)=>void) {
|
||||||
|
// console.log("didReceivechallenge");
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export {
|
||||||
|
NetworkManager,
|
||||||
|
NetworkRequestListener,
|
||||||
|
NetworkUploadFileListener,
|
||||||
|
NetworkDownloadFileListener
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class StatusCode {
|
||||||
|
private static statusCodeMap : Map<string, string> | null = null;
|
||||||
|
|
||||||
|
private static initStatusCodeMap() {
|
||||||
|
let map = new Map<string, string>();
|
||||||
|
map.set('100', "Continue");
|
||||||
|
map.set('101', "Switching Protocol");
|
||||||
|
map.set('200', "OK");
|
||||||
|
map.set('201', "Created");
|
||||||
|
map.set('202', "Accepted");
|
||||||
|
map.set('203', "Non-Authoritative Information");
|
||||||
|
map.set('204', "No Content");
|
||||||
|
map.set('205', "Reset Content");
|
||||||
|
map.set('206', "Partial Content");
|
||||||
|
map.set('300', "Multiple Choice");
|
||||||
|
map.set('301', "Moved Permanently");
|
||||||
|
map.set('302', "Found");
|
||||||
|
map.set('303', "See Other");
|
||||||
|
map.set('304', "Not Modified");
|
||||||
|
map.set('305', "Use Proxy");
|
||||||
|
map.set('306', "unused");
|
||||||
|
map.set('307', "Temporary Redirect");
|
||||||
|
map.set('308', "Permanent Redirect");
|
||||||
|
map.set('400', "Bad Request");
|
||||||
|
map.set('401', "Unauthorized");
|
||||||
|
map.set('402', "Payment Required");
|
||||||
|
map.set('403', "Forbidden");
|
||||||
|
map.set('404', "Not Found");
|
||||||
|
map.set('405', "Method Not Allowed");
|
||||||
|
map.set('406', "Not Acceptable");
|
||||||
|
map.set('407', "Proxy Authentication Required");
|
||||||
|
map.set('408', "Request Timeout");
|
||||||
|
map.set('409', "Conflict");
|
||||||
|
map.set('410', "Gone");
|
||||||
|
map.set('411', "Length Required");
|
||||||
|
map.set('412', "Precondition Failed");
|
||||||
|
map.set('413', "Payload Too Large");
|
||||||
|
map.set('414', "URI Too Long");
|
||||||
|
map.set('415', "Unsupported Media Type");
|
||||||
|
map.set('416', "Requested Range Not Satisfiable");
|
||||||
|
map.set('417', "Expectation Failed");
|
||||||
|
map.set('418', "I'm a teapot");
|
||||||
|
map.set('421', "Misdirected Request");
|
||||||
|
map.set('426', "Upgrade Required");
|
||||||
|
map.set('428', "Precondition Required");
|
||||||
|
map.set('429', "Too Many Requests");
|
||||||
|
map.set('431', "Request Header Fields Too Large");
|
||||||
|
map.set('500', "Internal Server Error");
|
||||||
|
map.set('501', "Not Implemented");
|
||||||
|
map.set('502', "Bad Gateway");
|
||||||
|
map.set('503', "Service Unavailable");
|
||||||
|
map.set('504', "Gateway Timeout");
|
||||||
|
map.set('505', "HTTP Version Not Supported");
|
||||||
|
map.set('506', "Variant Also Negotiates");
|
||||||
|
map.set('507', "Variant Also Negotiates");
|
||||||
|
map.set('511', "Network Authentication Required");
|
||||||
|
this.statusCodeMap = map;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getStatus(code : string) : string {
|
||||||
|
let map = this.statusCodeMap;
|
||||||
|
if (map == null) {
|
||||||
|
this.initStatusCodeMap();
|
||||||
|
}
|
||||||
|
let tmp = this.statusCodeMap!;
|
||||||
|
|
||||||
|
if (!(tmp.has(code))) {
|
||||||
|
return 'unknown status';
|
||||||
|
} else {
|
||||||
|
return tmp.get(code)! as string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export {
|
||||||
|
StatusCode
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 弱引用的包装类
|
||||||
|
*/
|
||||||
|
export class WeakRef<T extends Any>{
|
||||||
|
|
||||||
|
@UTSiOS.keyword("weak")
|
||||||
|
private value: T | null = null
|
||||||
|
|
||||||
|
constructor(value: T | null){
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public get(): T | null{
|
||||||
|
return this.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,287 @@
|
|||||||
|
import { DownloadFileOptions, DownloadTask, DownloadFileProgressUpdateCallback, OnProgressDownloadResult } from '../../../interface.uts';
|
||||||
|
import { NetworkDownloadFileListener } from '../NetworkManager.uts';
|
||||||
|
import { UUID, Data, URL, URLResourceKey, URLSessionDataTask, URLSessionTask, URLSession, URLSessionConfiguration, OperationQueue, URLSessionDataDelegate, URLSessionDownloadTask, NSError, URLSessionDownloadDelegate, URLRequest, FileManager, NSString, NSTemporaryDirectory, NSHomeDirectory , CharacterSet , HTTPURLResponse } from 'Foundation';
|
||||||
|
import { } from 'CommonCrypto';
|
||||||
|
import { Int, UnsafeBufferPointer, UnsafeRawBufferPointer } from 'Swift';
|
||||||
|
import { ObjCBool } from "ObjectiveC";
|
||||||
|
|
||||||
|
class NetworkDownloadTaskImpl implements DownloadTask {
|
||||||
|
private task : URLSessionDownloadTask | null = null;
|
||||||
|
private listener : NetworkDownloadFileListener | null = null;
|
||||||
|
constructor(task : URLSessionDownloadTask | null, listener : NetworkDownloadFileListener) {
|
||||||
|
this.task = task;
|
||||||
|
this.listener = listener;
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public abort() {
|
||||||
|
this.task?.cancel()
|
||||||
|
UTSiOS.destroyInstance(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
public onProgressUpdate(option : DownloadFileProgressUpdateCallback) {
|
||||||
|
const kListener = this.listener;
|
||||||
|
if (kListener != null) {
|
||||||
|
kListener!.progressListeners.add(option);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export class DownloadController implements URLSessionDownloadDelegate {
|
||||||
|
private static instance : DownloadController | null = null
|
||||||
|
|
||||||
|
private session : URLSession | null = null;
|
||||||
|
|
||||||
|
private taskMap : Map<URLSessionTask, NetworkDownloadFileListener> = new Map<URLSessionTask, NetworkDownloadFileListener>();
|
||||||
|
|
||||||
|
public static getInstance() : DownloadController {
|
||||||
|
if (this.instance == null) {
|
||||||
|
this.instance = new DownloadController();
|
||||||
|
}
|
||||||
|
return this.instance!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public downloadFile(options : DownloadFileOptions, listener : NetworkDownloadFileListener) : DownloadTask {
|
||||||
|
let request = this.createDownloadRequest(options, listener);
|
||||||
|
if (request == null) {
|
||||||
|
const task = new NetworkDownloadTaskImpl(null, listener)
|
||||||
|
listener.task = task;
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.session == null) {
|
||||||
|
let urlSessionConfig = URLSessionConfiguration.default;
|
||||||
|
|
||||||
|
this.session = new URLSession(configuration = urlSessionConfig, delegate = this, delegateQueue = OperationQueue.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
let task = this.session?.downloadTask(with = request!);
|
||||||
|
task?.resume();
|
||||||
|
|
||||||
|
if (task != null) {
|
||||||
|
this.taskMap.set(task!, listener);
|
||||||
|
}
|
||||||
|
let requestTask = new NetworkDownloadTaskImpl(task, listener);
|
||||||
|
listener.task = requestTask
|
||||||
|
return requestTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private createDownloadRequest(options : DownloadFileOptions, listener : NetworkDownloadFileListener) : URLRequest | null {
|
||||||
|
const encodeUrl = this.percentEscapedString(options.url)
|
||||||
|
let url = new URL(string = encodeUrl);
|
||||||
|
if (url == null) {
|
||||||
|
let error = new NSError(domain = "invalid URL", code = 600009);
|
||||||
|
listener.onFail(error);
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
let timeout = options.timeout == null ? 120000 : options.timeout;
|
||||||
|
let timeoutInterval = new Double(timeout!) / 1000;
|
||||||
|
let request = new URLRequest(url = url!, cachePolicy = URLRequest.CachePolicy.useProtocolCachePolicy, timeoutInterval = timeoutInterval);
|
||||||
|
request.httpShouldHandleCookies = true;
|
||||||
|
request.httpMethod = "GET";
|
||||||
|
|
||||||
|
let ua = UTSiOS.getUserAgent();
|
||||||
|
request.setValue(ua, forHTTPHeaderField = "User-Agent");
|
||||||
|
|
||||||
|
let headers = options.header?.toMap();
|
||||||
|
if (headers != null) {
|
||||||
|
for (entry in headers!) {
|
||||||
|
let key = entry.key;
|
||||||
|
let value = entry.value;
|
||||||
|
let valueStr = "";
|
||||||
|
if (value instanceof UTSJSONObject) {
|
||||||
|
valueStr = JSON.stringify(value) ?? ""
|
||||||
|
}else if(value instanceof Map<string, any>){
|
||||||
|
valueStr = JSON.stringify(new UTSJSONObject(value)) ?? ""
|
||||||
|
}else{
|
||||||
|
valueStr = `${value}`
|
||||||
|
}
|
||||||
|
request.setValue(valueStr, forHTTPHeaderField = key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
private percentEscapedString(str: string): string {
|
||||||
|
//如果url已经有部分经过encode,那么需要先decode再encode。
|
||||||
|
return str.removingPercentEncoding?.addingPercentEncoding(withAllowedCharacters= CharacterSet.urlQueryAllowed) ?? str
|
||||||
|
}
|
||||||
|
|
||||||
|
private convertToMD5(param : string) : string {
|
||||||
|
const strData = param.data(using = String.Encoding.utf8)!
|
||||||
|
let digest = new Array<UInt8>(repeating = 0, count = new Int(CC_MD5_DIGEST_LENGTH))
|
||||||
|
strData.withUnsafeBytes((body : UnsafeRawBufferPointer) => {
|
||||||
|
CC_MD5(body.baseAddress, new UInt32(strData.count), UTSiOS.getPointer(digest))
|
||||||
|
})
|
||||||
|
let md5String = ""
|
||||||
|
digest.forEach((byte : UInt8) => {
|
||||||
|
md5String += new String(format = "%02x", new UInt8(byte))
|
||||||
|
})
|
||||||
|
|
||||||
|
return md5String
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private isSandBoxPath(path : string) : boolean {
|
||||||
|
return path.startsWith(NSHomeDirectory())
|
||||||
|
}
|
||||||
|
|
||||||
|
private getTempPath() : string {
|
||||||
|
let cacheDirectory = FileManager.default.urls(for = FileManager.SearchPathDirectory.cachesDirectory, in = FileManager.SearchPathDomainMask.userDomainMask).first!
|
||||||
|
return cacheDirectory.path
|
||||||
|
}
|
||||||
|
|
||||||
|
private getRealPath() : string {
|
||||||
|
return this.getTempPath() + "/uni-download/"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private getTargetPath(options : DownloadFileOptions | null, fileName : string, listener : NetworkDownloadFileListener | null) : string | null {
|
||||||
|
let targetPath = ""
|
||||||
|
let specifyPath = options?.filePath ?? ""
|
||||||
|
if(specifyPath.startsWith("unifile://")){
|
||||||
|
specifyPath = UTSiOS.getResourceAbsolutePath(specifyPath, null);
|
||||||
|
}
|
||||||
|
let hasFileName = false;
|
||||||
|
if (specifyPath != "") {
|
||||||
|
const pos = specifyPath.lastIndexOf("/")
|
||||||
|
if (pos == specifyPath.length - 1) {
|
||||||
|
//如果filePath是目录
|
||||||
|
if (this.isSandBoxPath(specifyPath)) {
|
||||||
|
targetPath = specifyPath
|
||||||
|
} else {
|
||||||
|
targetPath = this.getTempPath() + "/" + specifyPath
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let path = "";
|
||||||
|
if (this.isSandBoxPath(specifyPath)) {
|
||||||
|
path = specifyPath;
|
||||||
|
} else {
|
||||||
|
path = this.getTempPath() + "/" + specifyPath
|
||||||
|
}
|
||||||
|
let fileManager = FileManager.default
|
||||||
|
var isDirectory : ObjCBool = false
|
||||||
|
if (fileManager.fileExists(atPath = path, isDirectory = UTSiOS.getPointer(isDirectory))) {
|
||||||
|
if (isDirectory.boolValue) {
|
||||||
|
let error = new NSError(domain = "The target file path is already a directory file, and file creation failed.", code = 602001);
|
||||||
|
listener?.onFail(error);
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
targetPath = path
|
||||||
|
hasFileName = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
targetPath = this.getRealPath()
|
||||||
|
}
|
||||||
|
if(!hasFileName){
|
||||||
|
targetPath += fileName
|
||||||
|
}
|
||||||
|
let fileManager = FileManager.default
|
||||||
|
if (fileManager.fileExists(atPath = targetPath)) {
|
||||||
|
const index = targetPath.lastIndexOf(".");
|
||||||
|
let tFileName = targetPath;
|
||||||
|
let tFileType = "";
|
||||||
|
if (index >= 0) {
|
||||||
|
tFileName = targetPath.substring(0, index as Int)
|
||||||
|
tFileType = targetPath.substring(index as Int)
|
||||||
|
}
|
||||||
|
|
||||||
|
var number = 1
|
||||||
|
while (fileManager.fileExists(atPath = targetPath)) {
|
||||||
|
targetPath = tFileName + `(${number})` + tFileType;
|
||||||
|
number++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return targetPath
|
||||||
|
}
|
||||||
|
|
||||||
|
private getFileName(fileName : string, url : URL | null) : string {
|
||||||
|
var suggestedFilename = fileName
|
||||||
|
|
||||||
|
if (suggestedFilename != "") {
|
||||||
|
let cString = suggestedFilename.cString(using = String.Encoding.isoLatin1)
|
||||||
|
if (cString != null) {
|
||||||
|
suggestedFilename = new String(cString = cString!, encoding = String.Encoding.utf8) ?? suggestedFilename
|
||||||
|
}
|
||||||
|
|
||||||
|
let cleanUri = suggestedFilename.removingPercentEncoding
|
||||||
|
if (cleanUri != null && cleanUri!.length > 0) {
|
||||||
|
suggestedFilename = cleanUri!
|
||||||
|
}
|
||||||
|
|
||||||
|
suggestedFilename = suggestedFilename.replacingOccurrences(of = "/", with = "")
|
||||||
|
suggestedFilename = suggestedFilename.replacingOccurrences(of = "\\", with = "")
|
||||||
|
} else {
|
||||||
|
if (url == null) {
|
||||||
|
suggestedFilename = ""
|
||||||
|
} else {
|
||||||
|
suggestedFilename = this.convertToMD5(url!.absoluteString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (suggestedFilename.length > 255) {
|
||||||
|
let extensionType = (suggestedFilename as NSString).pathExtension
|
||||||
|
suggestedFilename = this.convertToMD5((suggestedFilename as NSString).deletingPathExtension)
|
||||||
|
if (extensionType != "") {
|
||||||
|
suggestedFilename = (suggestedFilename as NSString).appendingPathExtension(extensionType) ?? suggestedFilename
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return suggestedFilename
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//mark --- URLSessionDownloadDelegate
|
||||||
|
|
||||||
|
urlSession(session : URLSession, @argumentLabel("") downloadTask : URLSessionDownloadTask, @argumentLabel("didWriteData") bytesWritten : Int64, @argumentLabel("") totalBytesWritten : Int64, @argumentLabel("") totalBytesExpectedToWrite : Int64) {
|
||||||
|
let listener = this.taskMap.get(downloadTask);
|
||||||
|
const progress = (Number.from(totalBytesWritten) / totalBytesExpectedToWrite) * 100
|
||||||
|
const progressUpdate : OnProgressDownloadResult = {
|
||||||
|
progress: progress.toInt(),
|
||||||
|
totalBytesWritten: totalBytesWritten,
|
||||||
|
totalBytesExpectedToWrite: totalBytesExpectedToWrite
|
||||||
|
}
|
||||||
|
listener?.onProgress(progressUpdate)
|
||||||
|
}
|
||||||
|
|
||||||
|
urlSession(session : URLSession, @argumentLabel("") downloadTask : URLSessionDownloadTask, @argumentLabel("didFinishDownloadingTo") location : URL) {
|
||||||
|
let listener = this.taskMap.get(downloadTask);
|
||||||
|
let suggestedFilename = downloadTask.response?.suggestedFilename
|
||||||
|
const statusCode = (downloadTask.response as HTTPURLResponse).statusCode
|
||||||
|
if(statusCode - 200 < 100 && statusCode - 200 >= 0) {
|
||||||
|
const fileName = this.getFileName(suggestedFilename ?? "", downloadTask.response?.url)
|
||||||
|
let destPath = this.getTargetPath(listener?.options, fileName, listener);
|
||||||
|
if (destPath != null) {
|
||||||
|
let fileManager = FileManager.default
|
||||||
|
try {
|
||||||
|
let directoryPath = (destPath as NSString).deletingLastPathComponent
|
||||||
|
if (!fileManager.fileExists(atPath = directoryPath)) {
|
||||||
|
UTSiOS.try(fileManager.createDirectory(atPath = directoryPath, withIntermediateDirectories = true, attributes = null), "?")
|
||||||
|
}
|
||||||
|
UTSiOS.try(fileManager.moveItem(atPath = location.path, toPath = destPath!), "?")
|
||||||
|
listener?.onFinished(downloadTask.response as HTTPURLResponse, destPath!);
|
||||||
|
} catch {
|
||||||
|
let error = new NSError(domain = "file move fail", code = 602001);
|
||||||
|
listener?.onFail(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let error = new NSError(domain = "request fail", code = statusCode);
|
||||||
|
listener?.onFail(error);
|
||||||
|
}
|
||||||
|
this.taskMap.delete(downloadTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
urlSession(session : URLSession, @argumentLabel("") task : URLSessionTask, @argumentLabel("didCompleteWithError") error : NSError | null) {
|
||||||
|
if(error != null){
|
||||||
|
let listener = this.taskMap.get(task);
|
||||||
|
listener?.onFail(error as NSError);
|
||||||
|
this.taskMap.delete(task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,261 @@
|
|||||||
|
import { UploadFileOptions, UploadTask, UploadFileProgressUpdateCallback, UploadFileOptionFiles, OnProgressUpdateResult } from '../../../interface.uts';
|
||||||
|
import { NetworkUploadFileListener } from '../NetworkManager.uts'
|
||||||
|
import { CharacterSet, HTTPURLResponse, UUID, Data, URL, URLResourceKey, URLSessionDataTask, URLSessionTask, URLSession, URLSessionConfiguration, OperationQueue, URLSessionDataDelegate, URLSessionUploadTask, NSError , NSMutableData , NSMutableSet , URLRequest} from 'Foundation';
|
||||||
|
import { UTTypeCreatePreferredIdentifierForTag, UTTypeCopyPreferredTagWithClass, kUTTagClassFilenameExtension, kUTTagClassMIMEType } from 'MobileCoreServices';
|
||||||
|
import { CFString } from 'CoreFoundation';
|
||||||
|
|
||||||
|
class NetworkUploadTaskImpl implements UploadTask {
|
||||||
|
private task : URLSessionDataTask | null = null;
|
||||||
|
private listener : NetworkUploadFileListener | null = null;
|
||||||
|
constructor(task : URLSessionDataTask | null, listener : NetworkUploadFileListener) {
|
||||||
|
this.task = task;
|
||||||
|
this.listener = listener;
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public abort() {
|
||||||
|
this.task?.cancel()
|
||||||
|
UTSiOS.destroyInstance(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
public onProgressUpdate(option : UploadFileProgressUpdateCallback) {
|
||||||
|
const kListener = this.listener;
|
||||||
|
if (kListener != null) {
|
||||||
|
kListener!.progressListeners.add(option);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UploadController implements URLSessionDataDelegate {
|
||||||
|
private static instance : UploadController | null = null
|
||||||
|
|
||||||
|
private session : URLSession | null = null;
|
||||||
|
|
||||||
|
private taskMap : Map<URLSessionTask, NetworkUploadFileListener> = new Map<URLSessionTask, NetworkUploadFileListener>();
|
||||||
|
|
||||||
|
public static getInstance() : UploadController {
|
||||||
|
if (this.instance == null) {
|
||||||
|
this.instance = new UploadController();
|
||||||
|
}
|
||||||
|
return this.instance!;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public uploadFile(options : UploadFileOptions, listener : NetworkUploadFileListener) : UploadTask {
|
||||||
|
let boundary = `----${new UUID().uuidString}`
|
||||||
|
let request = this.createRequest(options, listener, boundary);
|
||||||
|
if (request == null) {
|
||||||
|
const task = new NetworkUploadTaskImpl(null, listener)
|
||||||
|
listener.task = task
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.session == null) {
|
||||||
|
let urlSessionConfig = URLSessionConfiguration.default;
|
||||||
|
|
||||||
|
this.session = new URLSession(configuration = urlSessionConfig, delegate = this, delegateQueue = OperationQueue.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bodyData = this.createBody(boundary, options, listener);
|
||||||
|
if (bodyData == null) {
|
||||||
|
const task = new NetworkUploadTaskImpl(null, listener);
|
||||||
|
listener.task = task
|
||||||
|
return task
|
||||||
|
}
|
||||||
|
let task = this.session?.uploadTask(with = request!, from = bodyData!);
|
||||||
|
task?.resume();
|
||||||
|
if (task != null) {
|
||||||
|
this.taskMap.set(task!, listener);
|
||||||
|
}
|
||||||
|
let requestTask = new NetworkUploadTaskImpl(task, listener);
|
||||||
|
listener.task = requestTask
|
||||||
|
return requestTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private createRequest(param : UploadFileOptions, listener : NetworkUploadFileListener, boundary : string) : URLRequest | null {
|
||||||
|
const encodeUrl = this.percentEscapedString(param.url)
|
||||||
|
let url = new URL(string = encodeUrl);
|
||||||
|
if (url == null) {
|
||||||
|
let error = new NSError(domain = "invalid URL", code = 600009);
|
||||||
|
listener.onFail(error);
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
let timeout = param.timeout == null ? 120000 : param.timeout;
|
||||||
|
let timeoutInterval = new Double(timeout!) / 1000;
|
||||||
|
let request = new URLRequest(url = url!, cachePolicy = URLRequest.CachePolicy.useProtocolCachePolicy, timeoutInterval = timeoutInterval);
|
||||||
|
request.httpShouldHandleCookies = true;
|
||||||
|
|
||||||
|
request.httpMethod = "POST";
|
||||||
|
|
||||||
|
request.setValue(`multipart/form-data; boundary=${boundary}`, forHTTPHeaderField = "Content-Type");
|
||||||
|
|
||||||
|
let ua = UTSiOS.getUserAgent();
|
||||||
|
request.setValue(ua, forHTTPHeaderField = "User-Agent");
|
||||||
|
|
||||||
|
|
||||||
|
let headers = param.header?.toMap();
|
||||||
|
if (headers != null) {
|
||||||
|
for (entry in headers!) {
|
||||||
|
let key = entry.key;
|
||||||
|
let value = entry.value;
|
||||||
|
let valueStr = "";
|
||||||
|
if (value instanceof UTSJSONObject) {
|
||||||
|
valueStr = JSON.stringify(value) ?? ""
|
||||||
|
}else if(value instanceof Map<string, any>){
|
||||||
|
valueStr = JSON.stringify(new UTSJSONObject(value)) ?? ""
|
||||||
|
}else{
|
||||||
|
valueStr = `${value}`
|
||||||
|
}
|
||||||
|
request.setValue(valueStr, forHTTPHeaderField = key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
private createBody(boundary : string, options : UploadFileOptions, listener : NetworkUploadFileListener) : Data | null {
|
||||||
|
let body = new NSMutableData();
|
||||||
|
let formData = options.formData?.toMap();
|
||||||
|
if (formData != null) {
|
||||||
|
for (entry in formData!) {
|
||||||
|
const key = entry.key;
|
||||||
|
const value = entry.value;
|
||||||
|
if (value != null && typeof (key) == 'string') {
|
||||||
|
if (value instanceof UTSJSONObject) {
|
||||||
|
let valueStr = JSON.stringify(value)
|
||||||
|
if (valueStr != null) {
|
||||||
|
this.fillTextPart(body, boundary, key, valueStr as string)
|
||||||
|
}
|
||||||
|
}else if(value instanceof Map<string, any>){
|
||||||
|
let valueStr = JSON.stringify(new UTSJSONObject(value))
|
||||||
|
if (valueStr != null) {
|
||||||
|
this.fillTextPart(body, boundary, key, valueStr as string)
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
this.fillTextPart(body, boundary, key, `${value}`)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const tempFiles = options.files;
|
||||||
|
if (tempFiles != null && tempFiles!.length > 0) {
|
||||||
|
const files : UploadFileOptionFiles[] = tempFiles!;
|
||||||
|
for (let i = 0; i < files.length; i++) {
|
||||||
|
const item = files[i]
|
||||||
|
const filePath = item.uri;
|
||||||
|
const name = item.name ?? "file";
|
||||||
|
if (!this.fillFilePart(body, boundary, name, filePath!, listener)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const filePath = options.filePath;
|
||||||
|
const name = options.name ?? "file";
|
||||||
|
if (filePath == null) {
|
||||||
|
let error = new NSError(domain = "filePath is null", code = -1);
|
||||||
|
listener.onFail(error)
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!this.fillFilePart(body, boundary, name, filePath!, listener)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body.append(`--${boundary}--\r\n`.data(using = String.Encoding.utf8)!)
|
||||||
|
|
||||||
|
return body.copy() as Data;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private percentEscapedString(str: string): string {
|
||||||
|
//如果url已经有部分经过encode,那么需要先decode再encode。
|
||||||
|
return str.removingPercentEncoding?.addingPercentEncoding(withAllowedCharacters= CharacterSet.urlQueryAllowed) ?? str
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fillTextPart(body : NSMutableData, boundary : string, key : string, value : string) {
|
||||||
|
body.append(`--${boundary}\r\n`.data(using = String.Encoding.utf8)!)
|
||||||
|
body.append(`Content-Disposition: form-data; name=\"${key}\"\r\n`.data(using = String.Encoding.utf8)!)
|
||||||
|
body.append("\r\n".data(using = String.Encoding.utf8)!)
|
||||||
|
body.append(value.data(using = String.Encoding.utf8)!)
|
||||||
|
body.append("\r\n".data(using = String.Encoding.utf8)!)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fillFilePart(body : NSMutableData, boundary : string, name : string, filePath : string, listener : NetworkUploadFileListener) : boolean {
|
||||||
|
const absolutePath = new URL(fileURLWithPath = UTSiOS.getResourceAbsolutePath(filePath, null))
|
||||||
|
const fileData = UTSiOS.try(new Data(contentsOf = absolutePath), "?")
|
||||||
|
if (fileData == null) {
|
||||||
|
let error = new NSError(domain = "Illegal file", code = -1);
|
||||||
|
listener.onFail(error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const mimeType = this.getMimeType(absolutePath)
|
||||||
|
const fileName = absolutePath.lastPathComponent
|
||||||
|
|
||||||
|
const keys = new Swift.Set([URLResourceKey.fileSizeKey])
|
||||||
|
const resourceValue = UTSiOS.try(absolutePath.resourceValues(forKeys = keys), "?")
|
||||||
|
const fileSize = resourceValue?.fileSize
|
||||||
|
body.append(`--${boundary}\r\n`.data(using = String.Encoding.utf8)!)
|
||||||
|
body.append(`Content-Disposition: form-data; name=\"${name}\"; filename=\"${fileName}\"\r\n`.data(using = String.Encoding.utf8)!)
|
||||||
|
body.append(`Content-Type: ${mimeType}\r\n`.data(using = String.Encoding.utf8)!)
|
||||||
|
if (fileSize != null) {
|
||||||
|
body.append(`Content-Length: ${fileSize!}\r\n`.data(using = String.Encoding.utf8)!)
|
||||||
|
}
|
||||||
|
body.append("\r\n".data(using = String.Encoding.utf8)!)
|
||||||
|
body.append(fileData!)
|
||||||
|
body.append("\r\n".data(using = String.Encoding.utf8)!)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private getMimeType(url : URL) : string {
|
||||||
|
let pathExtension = url.pathExtension
|
||||||
|
|
||||||
|
const uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, pathExtension as CFString, null)?.takeRetainedValue()
|
||||||
|
if (uti != null) {
|
||||||
|
let mimetype = UTTypeCopyPreferredTagWithClass(uti!, kUTTagClassMIMEType)?.takeRetainedValue()
|
||||||
|
if (mimetype != null) {
|
||||||
|
return mimetype as string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "application/octet-stream"
|
||||||
|
}
|
||||||
|
|
||||||
|
//mark --- URLSessionDataDelegate
|
||||||
|
urlSession(session : URLSession, @argumentLabel("") task : URLSessionTask, @argumentLabel("didSendBodyData") bytesSent : Int64, @argumentLabel("") totalBytesSent : Int64, @argumentLabel("") totalBytesExpectedToSend : Int64) {
|
||||||
|
|
||||||
|
let listener = this.taskMap.get(task);
|
||||||
|
const progress = (Number.from(totalBytesSent) / totalBytesExpectedToSend) * 100
|
||||||
|
const progressUpdate : OnProgressUpdateResult = {
|
||||||
|
progress: progress.toInt(),
|
||||||
|
totalBytesSent: totalBytesSent,
|
||||||
|
totalBytesExpectedToSend: totalBytesExpectedToSend
|
||||||
|
}
|
||||||
|
listener?.onProgress(progressUpdate)
|
||||||
|
}
|
||||||
|
|
||||||
|
urlSession(session : URLSession, @argumentLabel("") dataTask : URLSessionDataTask, @argumentLabel("didReceive") data : Data) {
|
||||||
|
let listener = this.taskMap.get(dataTask);
|
||||||
|
listener?.onDataReceived(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
urlSession(session : URLSession, @argumentLabel("") task : URLSessionTask, @argumentLabel("didCompleteWithError") error : NSError | null) {
|
||||||
|
let listener = this.taskMap.get(task);
|
||||||
|
|
||||||
|
if (error != null) {
|
||||||
|
listener?.onFail(error as NSError);
|
||||||
|
} else {
|
||||||
|
listener?.onFinished(task.response as HTTPURLResponse);
|
||||||
|
}
|
||||||
|
this.taskMap.delete(task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export {
|
||||||
|
UploadController
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
export class NetworkUtil {
|
||||||
|
public static convertHeaders(headers: Map<string, any> | null): UTSJSONObject {
|
||||||
|
|
||||||
|
let simpleHeaders = new UTSJSONObject()
|
||||||
|
if (headers != null) {
|
||||||
|
headers!.forEach((value: any | null, key: string) => {
|
||||||
|
if (value == null) {
|
||||||
|
simpleHeaders.set(key, '')
|
||||||
|
} else {
|
||||||
|
simpleHeaders.set(key, value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return simpleHeaders;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static parseCookie(header : UTSJSONObject | null) : string[] {
|
||||||
|
if (header == null) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
let cookiesStr = header!.getString('Set-Cookie') as string | null
|
||||||
|
if (cookiesStr == null) {
|
||||||
|
cookiesStr = header!.getString('set-cookie') as string | null
|
||||||
|
}
|
||||||
|
if (cookiesStr == null) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
let cookiesArr = new Array<string>()
|
||||||
|
if (cookiesStr!.charAt(0) == "[" && cookiesStr!.charAt(cookiesStr!.length - 1) == "]") {
|
||||||
|
cookiesStr = cookiesStr!.slice(1, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCookiesArr = cookiesStr!.split(';')
|
||||||
|
for (let i = 0; i < handleCookiesArr.length; i++) {
|
||||||
|
if (handleCookiesArr[i].indexOf('Expires=') != -1 || handleCookiesArr[i].indexOf('expires=') != -1) {
|
||||||
|
cookiesArr.push(handleCookiesArr[i].replace(',', ''))
|
||||||
|
} else {
|
||||||
|
cookiesArr.push(handleCookiesArr[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cookiesArr = cookiesArr.join(';').split(',')
|
||||||
|
|
||||||
|
return cookiesArr
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,205 @@
|
|||||||
|
|
||||||
|
export type RequestDataOptions =
|
||||||
|
/**
|
||||||
|
* @description json对象
|
||||||
|
* @uniPlatform {
|
||||||
|
* "app": {
|
||||||
|
* "android": {
|
||||||
|
* "osVer": "4.4",
|
||||||
|
* "uniVer": "√",
|
||||||
|
* "unixVer": "3.9+"
|
||||||
|
* },
|
||||||
|
* "ios": {
|
||||||
|
* "osVer": "9.0",
|
||||||
|
* "uniVer": "√",
|
||||||
|
* "unixVer": "3.9+"
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
Object
|
||||||
|
/**
|
||||||
|
* @description 字符串
|
||||||
|
* @uniPlatform {
|
||||||
|
* "app": {
|
||||||
|
* "android": {
|
||||||
|
* "osVer": "4.4",
|
||||||
|
* "uniVer": "√",
|
||||||
|
* "unixVer": "3.9+"
|
||||||
|
* },
|
||||||
|
* "ios": {
|
||||||
|
* "osVer": "9.0",
|
||||||
|
* "uniVer": "√",
|
||||||
|
* "unixVer": "3.9+"
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
| String
|
||||||
|
/**
|
||||||
|
* @description ArrayBuffer
|
||||||
|
* @uniPlatform {
|
||||||
|
* "app": {
|
||||||
|
* "android": {
|
||||||
|
* "osVer": "x",
|
||||||
|
* "uniVer": "√",
|
||||||
|
* "unixVer": "x"
|
||||||
|
* },
|
||||||
|
* "ios": {
|
||||||
|
* "osVer": "x",
|
||||||
|
* "uniVer": "√",
|
||||||
|
* "unixVer": "x"
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
| ArrayBuffer;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export type RequestMethod =
|
||||||
|
/**
|
||||||
|
* @description GET
|
||||||
|
* @uniPlatform {
|
||||||
|
* "app": {
|
||||||
|
* "android": {
|
||||||
|
* "osVer": "4.4",
|
||||||
|
* "uniVer": "√",
|
||||||
|
* "unixVer": "3.9+"
|
||||||
|
* },
|
||||||
|
* "ios": {
|
||||||
|
* "osVer": "9.0",
|
||||||
|
* "uniVer": "√",
|
||||||
|
* "unixVer": "3.9+"
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
GET
|
||||||
|
/**
|
||||||
|
* @description POST
|
||||||
|
* @uniPlatform {
|
||||||
|
* "app": {
|
||||||
|
* "android": {
|
||||||
|
* "osVer": "4.4",
|
||||||
|
* "uniVer": "√",
|
||||||
|
* "unixVer": "3.9+"
|
||||||
|
* },
|
||||||
|
* "ios": {
|
||||||
|
* "osVer": "9.0",
|
||||||
|
* "uniVer": "√",
|
||||||
|
* "unixVer": "3.9+"
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
| POST
|
||||||
|
/**
|
||||||
|
* @description PUT
|
||||||
|
* @uniPlatform {
|
||||||
|
* "app": {
|
||||||
|
* "android": {
|
||||||
|
* "osVer": "4.4",
|
||||||
|
* "uniVer": "√",
|
||||||
|
* "unixVer": "3.9+"
|
||||||
|
* },
|
||||||
|
* "ios": {
|
||||||
|
* "osVer": "9.0",
|
||||||
|
* "uniVer": "√",
|
||||||
|
* "unixVer": "3.9+"
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
| PUT
|
||||||
|
/**
|
||||||
|
* @description DELETE
|
||||||
|
* @uniPlatform {
|
||||||
|
* "app": {
|
||||||
|
* "android": {
|
||||||
|
* "osVer": "4.4",
|
||||||
|
* "uniVer": "√",
|
||||||
|
* "unixVer": "3.9+"
|
||||||
|
* },
|
||||||
|
* "ios": {
|
||||||
|
* "osVer": "9.0",
|
||||||
|
* "uniVer": "√",
|
||||||
|
* "unixVer": "3.9+"
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
| DELETE
|
||||||
|
/**
|
||||||
|
* @description CONNECT
|
||||||
|
* @uniPlatform {
|
||||||
|
* "app": {
|
||||||
|
* "android": {
|
||||||
|
* "osVer": "4.4",
|
||||||
|
* "uniVer": "√",
|
||||||
|
* "unixVer": "3.9+"
|
||||||
|
* },
|
||||||
|
* "ios": {
|
||||||
|
* "osVer": "9.0",
|
||||||
|
* "uniVer": "√",
|
||||||
|
* "unixVer": "3.9+"
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
| CONNECT
|
||||||
|
/**
|
||||||
|
* @description HEAD
|
||||||
|
* @uniPlatform {
|
||||||
|
* "app": {
|
||||||
|
* "android": {
|
||||||
|
* "osVer": "4.4",
|
||||||
|
* "uniVer": "√",
|
||||||
|
* "unixVer": "3.9+"
|
||||||
|
* },
|
||||||
|
* "ios": {
|
||||||
|
* "osVer": "9.0",
|
||||||
|
* "uniVer": "√",
|
||||||
|
* "unixVer": "3.9+"
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
| HEAD
|
||||||
|
/**
|
||||||
|
* @description OPTIONS
|
||||||
|
* @uniPlatform {
|
||||||
|
* "app": {
|
||||||
|
* "android": {
|
||||||
|
* "osVer": "4.4",
|
||||||
|
* "uniVer": "√",
|
||||||
|
* "unixVer": "3.9+"
|
||||||
|
* },
|
||||||
|
* "ios": {
|
||||||
|
* "osVer": "9.0",
|
||||||
|
* "uniVer": "√",
|
||||||
|
* "unixVer": "3.9+"
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
| OPTIONS
|
||||||
|
/**
|
||||||
|
* @description TRACE
|
||||||
|
* @uniPlatform {
|
||||||
|
* "app": {
|
||||||
|
* "android": {
|
||||||
|
* "osVer": "4.4",
|
||||||
|
* "uniVer": "√",
|
||||||
|
* "unixVer": "3.9+"
|
||||||
|
* },
|
||||||
|
* "ios": {
|
||||||
|
* "osVer": "9.0",
|
||||||
|
* "uniVer": "√",
|
||||||
|
* "unixVer": "3.9+"
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
| TRACE;
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,282 @@
|
|||||||
|
import {
|
||||||
|
RequestMethod,
|
||||||
|
RequestOptions,
|
||||||
|
UploadFileOptions,
|
||||||
|
DownloadFileOptions,
|
||||||
|
Certificate,
|
||||||
|
ConfigMTLSOptions,
|
||||||
|
} from './interface.uts'
|
||||||
|
|
||||||
|
// request
|
||||||
|
export const API_REQUEST = 'request';
|
||||||
|
export const RequestApiProtocol = new Map<string, ProtocolOptions>([
|
||||||
|
[
|
||||||
|
'url',
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'data',
|
||||||
|
{
|
||||||
|
type: 'object',
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'header',
|
||||||
|
{
|
||||||
|
type: 'object',
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'method',
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'dataType',
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'responseType',
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'timeout',
|
||||||
|
{
|
||||||
|
type: 'number',
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'sslVerify',
|
||||||
|
{
|
||||||
|
type: 'boolean',
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'withCredentials',
|
||||||
|
{
|
||||||
|
type: 'boolean',
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'firstIpv4',
|
||||||
|
{
|
||||||
|
type: 'boolean',
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
])
|
||||||
|
export const RequestApiOptions: ApiOptions<RequestOptions<any>> = {
|
||||||
|
formatArgs: new Map<string, Function>([
|
||||||
|
[
|
||||||
|
'url',
|
||||||
|
function (url: string, params: RequestOptions<any>) {
|
||||||
|
if (url == null) {
|
||||||
|
throw new Error('url is required')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'method',
|
||||||
|
function (method: string, params: RequestOptions<any>) {
|
||||||
|
params.method = (method || 'GET').toUpperCase() as RequestMethod
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'dataType',
|
||||||
|
function (dataType: string, params: RequestOptions<any>) {
|
||||||
|
if (dataType == null) {
|
||||||
|
params.dataType = 'json'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'responseType',
|
||||||
|
function (responseType: string, params: RequestOptions<any>) {
|
||||||
|
if (responseType == null) {
|
||||||
|
params.responseType = 'text'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'timeout',
|
||||||
|
function (timeout: number, params: RequestOptions<any>) {
|
||||||
|
if (timeout == null) {
|
||||||
|
params.timeout = 60000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'sslVerify',
|
||||||
|
function (sslVerify: boolean, params: RequestOptions<any>) {
|
||||||
|
if (sslVerify == null) {
|
||||||
|
params.sslVerify = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'withCredentials',
|
||||||
|
function (withCredentials: boolean, params: RequestOptions<any>) {
|
||||||
|
if (withCredentials == null) {
|
||||||
|
params.withCredentials = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'firstIpv4',
|
||||||
|
function (firstIpv4: boolean, params: RequestOptions<any>) {
|
||||||
|
if (firstIpv4 == null) {
|
||||||
|
params.firstIpv4 = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
]),
|
||||||
|
}
|
||||||
|
|
||||||
|
// downloadFile
|
||||||
|
export const API_DOWNLOAD_FILE = 'downloadFile';
|
||||||
|
export const DownloadFileApiProtocol = new Map<string, ProtocolOptions>([
|
||||||
|
[
|
||||||
|
'url',
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'header',
|
||||||
|
{
|
||||||
|
type: 'object',
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'timeout',
|
||||||
|
{
|
||||||
|
type: 'number',
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
])
|
||||||
|
export const DownloadFileApiOptions: ApiOptions<DownloadFileOptions> = {
|
||||||
|
formatArgs: new Map<string, Function>([
|
||||||
|
[
|
||||||
|
'url',
|
||||||
|
function (url: string, params: DownloadFileOptions) {
|
||||||
|
if (url == null) {
|
||||||
|
throw new Error('url is required')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
]),
|
||||||
|
}
|
||||||
|
|
||||||
|
// uploadFile
|
||||||
|
export const API_UPLOAD_FILE = 'uploadFile';
|
||||||
|
export const UploadFileApiProtocol = new Map<string, ProtocolOptions>([
|
||||||
|
[
|
||||||
|
'url',
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'filePath',
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name',
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'header',
|
||||||
|
{
|
||||||
|
type: 'object',
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'formData',
|
||||||
|
{
|
||||||
|
type: 'object',
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'timeout',
|
||||||
|
{
|
||||||
|
type: 'number',
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
])
|
||||||
|
export const UploadFileApiOptions: ApiOptions<UploadFileOptions> = {
|
||||||
|
formatArgs: new Map<string, Function>([
|
||||||
|
[
|
||||||
|
'url',
|
||||||
|
function (url: string, params: UploadFileOptions) {
|
||||||
|
if (url == null) {
|
||||||
|
throw new Error('url is required')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name',
|
||||||
|
function (name: string, params: UploadFileOptions) {
|
||||||
|
if (name == null) {
|
||||||
|
params.name = 'file'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
]),
|
||||||
|
}
|
||||||
|
|
||||||
|
// #region configMTLS
|
||||||
|
export const API_CONFIG_MTLS = 'configMTLS'
|
||||||
|
export const ConfigMTLSApiProtocol = new Map<string, ProtocolOptions>([
|
||||||
|
[
|
||||||
|
'certificates',
|
||||||
|
{
|
||||||
|
type: 'array',
|
||||||
|
required: true,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
])
|
||||||
|
export const ConfigMTLSApiOptions: ApiOptions<ConfigMTLSOptions> = {
|
||||||
|
formatArgs: new Map<string, Function>(
|
||||||
|
[
|
||||||
|
[
|
||||||
|
'certificates',
|
||||||
|
function (certificates?: Certificate[]) {
|
||||||
|
if (!certificates || certificates.some((item) => typeof item.host !== 'string')) {
|
||||||
|
return '参数 certificates 配置错误,请确认后重试'
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// #endregion
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
import { RequestErrorCode, RequestFail, UploadFileFail, DownloadFileFail } from "./interface.uts"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 错误主题
|
||||||
|
*/
|
||||||
|
export const UniNetWorkErrorSubject = 'uni-request';
|
||||||
|
/**
|
||||||
|
* 错误码
|
||||||
|
* @UniError
|
||||||
|
*/
|
||||||
|
const NetWorkUniErrors : Map<RequestErrorCode, string> = new Map([
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 接口超时
|
||||||
|
*/
|
||||||
|
[5, 'time out'],
|
||||||
|
/**
|
||||||
|
* 服务端系统错误
|
||||||
|
*/
|
||||||
|
[1000, 'server system error'],
|
||||||
|
/**
|
||||||
|
* json数据解析错误
|
||||||
|
*/
|
||||||
|
[100001, 'invalid json'],
|
||||||
|
/**
|
||||||
|
* 错误信息json解析失败
|
||||||
|
*/
|
||||||
|
[100002, 'error message invalid json'],
|
||||||
|
/**
|
||||||
|
* 网络中断
|
||||||
|
*/
|
||||||
|
[600003, 'network interrupted error'],
|
||||||
|
/**
|
||||||
|
* data参数类型不合法
|
||||||
|
*/
|
||||||
|
[600008, 'the data parameter type is invalid'],
|
||||||
|
/**
|
||||||
|
* URL 格式不合法
|
||||||
|
*/
|
||||||
|
[600009, 'invalid URL'],
|
||||||
|
/**
|
||||||
|
* request系统错误
|
||||||
|
*/
|
||||||
|
[602001, 'request system error']
|
||||||
|
]);
|
||||||
|
|
||||||
|
// #ifdef APP-IOS
|
||||||
|
@UTSiOS.keyword("fileprivate")
|
||||||
|
// #endif
|
||||||
|
export function getErrcode(errCode : number) : RequestErrorCode {
|
||||||
|
const res = NetWorkUniErrors[errCode];
|
||||||
|
return res == null ? 602001 : errCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export class RequestFailImpl extends UniError implements RequestFail {
|
||||||
|
// #ifdef APP-ANDROID
|
||||||
|
override errCode: RequestErrorCode
|
||||||
|
// #endif
|
||||||
|
constructor(errCode : RequestErrorCode) {
|
||||||
|
super();
|
||||||
|
this.errSubject = UniNetWorkErrorSubject;
|
||||||
|
this.errCode = errCode;
|
||||||
|
this.errMsg = NetWorkUniErrors[errCode] ?? "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export class UploadFileFailImpl extends UniError implements UploadFileFail {
|
||||||
|
// #ifdef APP-ANDROID
|
||||||
|
override errCode: RequestErrorCode
|
||||||
|
// #endif
|
||||||
|
constructor(errCode : RequestErrorCode) {
|
||||||
|
super();
|
||||||
|
this.errSubject = "uni-uploadFile";
|
||||||
|
this.errCode = errCode;
|
||||||
|
this.errMsg = NetWorkUniErrors[errCode] ?? "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export class DownloadFileFailImpl extends UniError implements DownloadFileFail {
|
||||||
|
// #ifdef APP-ANDROID
|
||||||
|
override errCode: RequestErrorCode
|
||||||
|
// #endif
|
||||||
|
constructor(errCode : RequestErrorCode) {
|
||||||
|
super();
|
||||||
|
this.errSubject = "uni-downloadFile";
|
||||||
|
this.errCode = errCode;
|
||||||
|
this.errMsg = NetWorkUniErrors[errCode] ?? "";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -106,7 +106,7 @@
|
|||||||
return this.src.indexOf('/') !== -1
|
return this.src.indexOf('/') !== -1
|
||||||
},
|
},
|
||||||
// 图片加载时失败时触发
|
// 图片加载时失败时触发
|
||||||
errorHandler() {
|
errorHandler(e) {
|
||||||
this.$emit('onError')
|
this.$emit('onError')
|
||||||
this.avatarUrl = this.defaultUrl || base64Avatar
|
this.avatarUrl = this.defaultUrl || base64Avatar
|
||||||
},
|
},
|
||||||
|
|||||||
+2
-1
@@ -34,10 +34,11 @@ export const getEl = (el) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getDbDir = () => {
|
export const getDbDir = () => {
|
||||||
|
const userID = uni.getStorageSync("IMUserID");
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
plus.io.requestFileSystem(plus.io.PRIVATE_DOC, (fs) => {
|
plus.io.requestFileSystem(plus.io.PRIVATE_DOC, (fs) => {
|
||||||
fs.root.getDirectory(
|
fs.root.getDirectory(
|
||||||
"user", {
|
userID ? userID : "user", {
|
||||||
create: true,
|
create: true,
|
||||||
},
|
},
|
||||||
(entry) => {
|
(entry) => {
|
||||||
|
|||||||
+101
-14
@@ -3,6 +3,10 @@
|
|||||||
import base from '@/common/config';
|
import base from '@/common/config';
|
||||||
//import store from "@/store";
|
//import store from "@/store";
|
||||||
import IMSDK from "openim-uniapp-polyfill";
|
import IMSDK from "openim-uniapp-polyfill";
|
||||||
|
|
||||||
|
import md5 from "md5";
|
||||||
|
|
||||||
|
import {downloadFile} from "@/uni_modules/network-manage";
|
||||||
const isString = (v)=> {
|
const isString = (v)=> {
|
||||||
return typeof v === 'string' || v instanceof String;
|
return typeof v === 'string' || v instanceof String;
|
||||||
},
|
},
|
||||||
@@ -160,14 +164,14 @@ const scan = ()=>{
|
|||||||
|
|
||||||
const fileExsit = async(fn)=>{
|
const fileExsit = async(fn)=>{
|
||||||
return await new Promise((resolve) => {
|
return await new Promise((resolve) => {
|
||||||
plus.io.resolveLocalFileSystemURL("_doc/"+fn, function(entry) {
|
plus.io.resolveLocalFileSystemURL(fn, function(entry) {
|
||||||
resolve(true);
|
resolve(true);
|
||||||
}, function() {
|
}, function() {
|
||||||
resolve(false);
|
resolve(false);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const downloadFile = (url, savepath, successCb, errorCb, progressCb) => {
|
const downloadFile1 = (url, savepath, successCb, errorCb, progressCb) => {
|
||||||
const root_dir = "_doc/";
|
const root_dir = "_doc/";
|
||||||
if (!url) {
|
if (!url) {
|
||||||
errorCb && errorCb.call(this,new Error('empty url'));
|
errorCb && errorCb.call(this,new Error('empty url'));
|
||||||
@@ -223,20 +227,100 @@ const downloadFile = (url, savepath, successCb, errorCb, progressCb) => {
|
|||||||
startDownload();
|
startDownload();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const cacheFile = async (url, savepath, successCb, errorCb, progressCb) => {
|
|
||||||
//plus.downloader.clear();
|
const get_absolute_path = (fn)=>{
|
||||||
//return ;
|
return plus.io.convertLocalFileSystemURL(fn);
|
||||||
const coverExists = await fileExsit(savepath);
|
|
||||||
if(coverExists){
|
|
||||||
successCb && successCb.call(this,"_doc/"+savepath);
|
|
||||||
}else{
|
|
||||||
downloadFile(url, savepath, successCb, errorCb, progressCb)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
const pendingDownloads = new Map();
|
||||||
|
const cacheFile = (url, saveDir,progressCallback) => {
|
||||||
|
let cacheDir = plus.io.convertLocalFileSystemURL(`_doc/{{dir}}/{{key}}.{{ext}}`);
|
||||||
|
cacheDir = cacheDir.replace('apps/'+plus.runtime.appid+'/doc','cache');
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
try {
|
||||||
|
if(!url || !url.startsWith('http')){
|
||||||
|
resolve(url);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const key = md5(url);
|
||||||
|
var ext = "png"
|
||||||
|
if(url.toLowerCase().indexOf('.mp4')!==-1){
|
||||||
|
ext = "mp4";
|
||||||
|
}
|
||||||
|
const cacheFilePath = cacheDir.replace('{{dir}}',saveDir)
|
||||||
|
.replace('{{key}}',key)
|
||||||
|
.replace('{{ext}}',ext);
|
||||||
|
|
||||||
|
//console.error('cacheDir:',cacheDir);
|
||||||
|
// 如果缓存存在且文件存在,直接返回缓存路径
|
||||||
|
if (cacheFilePath) {
|
||||||
|
//console.error('cacheFilePath:', cacheFilePath);
|
||||||
|
const coverExists = await fileExsit(cacheFilePath);
|
||||||
|
//console.log("coverExists:" ,coverExists);
|
||||||
|
if (coverExists) {
|
||||||
|
//console.log("已缓存为:" , cacheFilePath,url);
|
||||||
|
resolve(cacheFilePath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 检查是否已经有相同的 URL 正在下载
|
||||||
|
if (pendingDownloads.has(url)) {
|
||||||
|
// 如果已经有相同的下载在进行,则添加到等待列表
|
||||||
|
pendingDownloads.get(url).promises.push({ resolve, reject });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 3. 如果没有正在下载,则创建新的下载任务
|
||||||
|
pendingDownloads.set(url, {
|
||||||
|
promises: [{ resolve, reject }],
|
||||||
|
completed: false,
|
||||||
|
savedPath: saveDir
|
||||||
|
});
|
||||||
|
|
||||||
|
// 4. 开始下载文件
|
||||||
|
const task = downloadFile({
|
||||||
|
url:url,
|
||||||
|
timeout: 30000,
|
||||||
|
filePath:cacheFilePath,
|
||||||
|
success(res) {
|
||||||
|
// 下载成功,解决所有等待的 Promise
|
||||||
|
const info = pendingDownloads.get(url);
|
||||||
|
if (info) {
|
||||||
|
info.completed = true;
|
||||||
|
info.promises.forEach(p => p.resolve(res.tempFilePath));
|
||||||
|
pendingDownloads.delete(url);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail(e){
|
||||||
|
// 下载失败,拒绝所有等待的 Promise
|
||||||
|
const info = pendingDownloads.get(url);
|
||||||
|
if (info) {
|
||||||
|
info.promises.forEach(p => p.reject(error));
|
||||||
|
pendingDownloads.delete(url);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
complate(){
|
||||||
|
task = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
//console.error('task:', task);
|
||||||
|
task.onProgressUpdate(({progress,totalBytesWritten,totalBytesExpectedToWrite})=>{
|
||||||
|
//console.error('pres:', progress,totalBytesWritten,totalBytesExpectedToWrite);
|
||||||
|
progressCallback && progressCallback.call(this,progress,totalBytesWritten,totalBytesExpectedToWrite)
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('cacheFile 发生错误:', error);
|
||||||
|
resolve(url); // 返回原始URL作为fallback
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export default{
|
export default{
|
||||||
fileExsit,
|
|
||||||
downloadFile,
|
|
||||||
cacheFile,
|
cacheFile,
|
||||||
|
fileExsit,
|
||||||
|
get_absolute_path,
|
||||||
|
downloadFile,
|
||||||
isString :isString,
|
isString :isString,
|
||||||
isNumber :isNumber,
|
isNumber :isNumber,
|
||||||
isInteger :isInteger,
|
isInteger :isInteger,
|
||||||
@@ -258,7 +342,10 @@ export default{
|
|||||||
v= v || "";
|
v= v || "";
|
||||||
v = v.replace(/\\/ig,"/").replace('/\/\/ig',"/");
|
v = v.replace(/\\/ig,"/").replace('/\/\/ig',"/");
|
||||||
//console.log(v);
|
//console.log(v);
|
||||||
if(isString(v)){
|
if(v && isString(v)){
|
||||||
|
if(v.startsWith('//')){
|
||||||
|
return 'http:'+v;
|
||||||
|
}
|
||||||
if(v.startsWith('blob:') || v.startsWith('file://') || v.startsWith('http')){
|
if(v.startsWith('blob:') || v.startsWith('file://') || v.startsWith('http')){
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user