3.18 患者端
This commit is contained in:
@@ -170,16 +170,16 @@ function handleNoDevicePermissionError(error) {
|
||||
exports.handleNoDevicePermissionError = handleNoDevicePermissionError;
|
||||
/*
|
||||
* 获取向下取整的 performance.now() 值
|
||||
* 在不支持 performance.now 的浏览器中,使用 Date.now(). 例如 ie 9,ie 10,避免加载 sdk 时报错
|
||||
* @export
|
||||
* @return {Number}
|
||||
*/
|
||||
function performanceNow() {
|
||||
// 在不支持 performance.now 的浏览器中,使用 Date.now()
|
||||
// 例如 ie 9,ie 10,避免加载 sdk 时报错
|
||||
if (!performance || !performance.now) {
|
||||
return Date.now();
|
||||
}
|
||||
return Math.floor(performance.now());
|
||||
// if (!performance || !performance.now) {
|
||||
// return Date.now();
|
||||
// }
|
||||
// return Math.floor(performance.now()); // uni-app 打包小程序没有 performance, 报错
|
||||
return Date.now();
|
||||
}
|
||||
exports.performanceNow = performanceNow;
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,239 @@
|
||||
import { NAME } from '../const/index';
|
||||
import TUIGlobal from '../TUIGlobal/tuiGlobal';
|
||||
|
||||
export const isUndefined = function (input: any) {
|
||||
return typeof input === NAME.UNDEFINED;
|
||||
};
|
||||
|
||||
export const isPlainObject = function (input: any) {
|
||||
// 注意不能使用以下方式判断,因为IE9/IE10下,对象的__proto__是 undefined
|
||||
// return isObject(input) && input.__proto__ === Object.prototype;
|
||||
if (typeof input !== NAME.OBJECT || input === null) {
|
||||
return false;
|
||||
}
|
||||
const proto = Object.getPrototypeOf(input);
|
||||
if (proto === null) { // edge case Object.create(null)
|
||||
return true;
|
||||
}
|
||||
let baseProto = proto;
|
||||
while (Object.getPrototypeOf(baseProto) !== null) {
|
||||
baseProto = Object.getPrototypeOf(baseProto);
|
||||
}
|
||||
// 原型链第一个和最后一个比较
|
||||
return proto === baseProto;
|
||||
};
|
||||
|
||||
export const isArray = function (input: any) {
|
||||
if (typeof Array.isArray === NAME.FUNCTION) {
|
||||
return Array.isArray(input);
|
||||
}
|
||||
return (Object as any).prototype.toString.call(input).match(/^\[object (.*)\]$/)[1].toLowerCase() === NAME.ARRAY;
|
||||
};
|
||||
|
||||
export const isPrivateKey = function (key: string) {
|
||||
return key.startsWith('_');
|
||||
};
|
||||
|
||||
export const isUrl = function (url: string) {
|
||||
return /^(https?:\/\/(([a-zA-Z0-9]+-?)+[a-zA-Z0-9]+\.)+[a-zA-Z]+)(:\d+)?(\/.*)?(\?.*)?(#.*)?$/.test(url);
|
||||
};
|
||||
/**
|
||||
* 检测input类型是否为string
|
||||
* @param {*} input 任意类型的输入
|
||||
* @returns {Boolean} true->string / false->not a string
|
||||
*/
|
||||
export const isString = function (input: any) {
|
||||
return typeof input === NAME.STRING;
|
||||
};
|
||||
export const isBoolean = function (input: any) {
|
||||
return typeof input === NAME.BOOLEAN;
|
||||
};
|
||||
export const isNumber = function (input: any) {
|
||||
return (
|
||||
// eslint-disable-next-line
|
||||
input !== null &&
|
||||
((typeof input === NAME.NUMBER && !isNaN(input - 0)) || (typeof input === NAME.OBJECT && input.constructor === Number))
|
||||
);
|
||||
};
|
||||
|
||||
export function formatTime(secondTime: number): string {
|
||||
const hours: number = Math.floor(secondTime / 3600);
|
||||
const minutes: number = Math.floor((secondTime % 3600) / 60);
|
||||
const seconds: number = Math.floor(secondTime % 60);
|
||||
let callDurationStr: string = hours > 9 ? `${hours}` : `0${hours}`;
|
||||
callDurationStr += minutes > 9 ? `:${minutes}` : `:0${minutes}`;
|
||||
callDurationStr += seconds > 9 ? `:${seconds}` : `:0${seconds}`;
|
||||
return callDurationStr;
|
||||
}
|
||||
export function formatTimeInverse(stringTime: string): number {
|
||||
const list = stringTime.split(':');
|
||||
return parseInt(list[0]) * 3600 + parseInt(list[1]) * 60 + parseInt(list[2]); // eslint-disable-line
|
||||
}
|
||||
|
||||
|
||||
// Determine if it is a JSON string
|
||||
export function isJSON(str: string) {
|
||||
if (typeof str === NAME.STRING) {
|
||||
try {
|
||||
const data = JSON.parse(str);
|
||||
if (data) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (error) {
|
||||
console.debug(error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Determine if it is a JSON string
|
||||
export const JSONToObject = function (str: string) {
|
||||
if (!str || !isJSON(str)) {
|
||||
return str;
|
||||
}
|
||||
return JSON.parse(str);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 重试函数, catch 时,重试
|
||||
* @param {Promise} promise 需重试的函数
|
||||
* @param {number} num 需要重试的次数
|
||||
* @param {number} time 间隔时间(s)
|
||||
* @returns {Promise<any>} im 接口的 response 原样返回
|
||||
*/
|
||||
export const retryPromise = (promise: Promise<any>, num: number = 6, time: number = 0.5) => {
|
||||
let n = num;
|
||||
const func = () => promise;
|
||||
return func()
|
||||
.catch((error: any) => {
|
||||
if (n === 0) {
|
||||
throw error;
|
||||
}
|
||||
const timer = setTimeout(() => {
|
||||
func();
|
||||
clearTimeout(timer);
|
||||
n = n - 1;
|
||||
}, time * 1000);
|
||||
});
|
||||
};
|
||||
|
||||
// /**
|
||||
// * 节流函数(目前 TUICallKit 增加防重调用装饰器,该方法可删除)
|
||||
// * @param {Function} func 传入的函数
|
||||
// * @param {wait} time 间隔时间(ms)
|
||||
// */
|
||||
// export const throttle = (func: Function, wait: number) => {
|
||||
// let previousTime = 0;
|
||||
// return function () {
|
||||
// const now = Date.now();
|
||||
// const args = [...arguments];
|
||||
// if (now - previousTime > wait) {
|
||||
// func.apply(this, args);
|
||||
// previousTime = now;
|
||||
// }
|
||||
// };
|
||||
// }
|
||||
|
||||
/**
|
||||
* web call engine 重复调用时的错误, 这种错误在 TUICallKit 应该忽略
|
||||
* @param {any} error 错误信息
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
export function handleRepeatedCallError(error: any) {
|
||||
if (error?.message.indexOf('is ongoing, please avoid repeated calls') !== -1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* 设备无权限时的错误处理
|
||||
* @param {any} error 错误信息
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
export function handleNoDevicePermissionError(error: any) {
|
||||
const { message } = error;
|
||||
if (message.indexOf('NotAllowedError: Permission denied') !== -1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* 获取向下取整的 performance.now() 值
|
||||
* 在不支持 performance.now 的浏览器中,使用 Date.now(). 例如 ie 9,ie 10,避免加载 sdk 时报错
|
||||
* @export
|
||||
* @return {Number}
|
||||
*/
|
||||
export function performanceNow() {
|
||||
// if (!performance || !performance.now) {
|
||||
// return Date.now();
|
||||
// }
|
||||
// return Math.floor(performance.now()); // uni-app 打包小程序没有 performance, 报错
|
||||
return Date.now();
|
||||
|
||||
}
|
||||
/**
|
||||
* 检测input类型是否为function
|
||||
* @param {*} input 任意类型的输入
|
||||
* @returns {Boolean} true->input is a function
|
||||
*/
|
||||
export const isFunction = function (input: any) {
|
||||
return typeof input === NAME.FUNCTION;
|
||||
};
|
||||
|
||||
/*
|
||||
* 获取浏览器语言
|
||||
* @export
|
||||
* @return {zh-cn | en}
|
||||
*/
|
||||
export const getLanguage = () => {
|
||||
if (TUIGlobal.getInstance().isWeChat) {
|
||||
return 'zh-cn';
|
||||
}
|
||||
// @ts-ignore
|
||||
const lang = (navigator?.language || navigator?.userLanguage || '').substr(0, 2);
|
||||
let language = 'en';
|
||||
switch (lang) {
|
||||
case 'zh':
|
||||
language = 'zh-cn';
|
||||
break;
|
||||
case 'ja':
|
||||
language = 'ja_JP';
|
||||
break;
|
||||
default:
|
||||
language = 'en';
|
||||
}
|
||||
return language;
|
||||
};
|
||||
|
||||
export function noop(e: any) {}
|
||||
|
||||
/**
|
||||
* Get the object type string
|
||||
* @param {*} input 任意类型的输入
|
||||
* @returns {String} the object type string
|
||||
*/
|
||||
export const getType = function(input) {
|
||||
return Object.prototype.toString
|
||||
.call(input)
|
||||
.match(/^\[object (.*)\]$/)[1]
|
||||
.toLowerCase();
|
||||
};
|
||||
// 修改对象键名
|
||||
export function modifyObjectKey(obj, oldKey, newKey) {
|
||||
if (!obj.hasOwnProperty(oldKey)) {
|
||||
return obj;
|
||||
}
|
||||
const newObj = {};
|
||||
Object.keys(obj).forEach(key => {
|
||||
if (key === oldKey) {
|
||||
newObj[newKey] = obj[key];
|
||||
} else {
|
||||
newObj[key] = obj[key];
|
||||
}
|
||||
});
|
||||
return newObj;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 异步函数重试 Interface
|
||||
* @param {number} [retries = 5] 重试次数
|
||||
* @param {number} [timeout = 2000] 重试间隔时间
|
||||
* @param {Function=} onError 重试错误回调
|
||||
* @param {Function=} onRetrying 重试回调
|
||||
* @param {Function=} onRetryFailed 重试失败回调
|
||||
*/
|
||||
interface IPromiseRetryDecoratorSettings {
|
||||
retries?: number;
|
||||
timeout?: number;
|
||||
onError?: Function;
|
||||
onRetrying?: Function;
|
||||
onRetryFailed?: Function;
|
||||
}
|
||||
/**
|
||||
* 装饰器函数:给异步函数增加重试
|
||||
* @param {Object} settings 入参
|
||||
* @returns {Function}
|
||||
* @example
|
||||
* class LocalStream {
|
||||
* @promiseRetryDecorator({
|
||||
* retries: 10,
|
||||
* timeout: 3000,
|
||||
* onRetryFailed: function(error) {
|
||||
* }
|
||||
* })
|
||||
* async recoverCapture(options) {}
|
||||
* }
|
||||
*/
|
||||
export default function promiseRetryDecorator(settings: IPromiseRetryDecoratorSettings): (target: any, name: any, descriptor: any) => any;
|
||||
export {};
|
||||
@@ -0,0 +1,40 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const retry_1 = __importDefault(require("../retry"));
|
||||
;
|
||||
/**
|
||||
* 装饰器函数:给异步函数增加重试
|
||||
* @param {Object} settings 入参
|
||||
* @returns {Function}
|
||||
* @example
|
||||
* class LocalStream {
|
||||
* @promiseRetryDecorator({
|
||||
* retries: 10,
|
||||
* timeout: 3000,
|
||||
* onRetryFailed: function(error) {
|
||||
* }
|
||||
* })
|
||||
* async recoverCapture(options) {}
|
||||
* }
|
||||
*/
|
||||
function promiseRetryDecorator(settings) {
|
||||
return function (target, name, descriptor) {
|
||||
const { retries = 5, timeout = 2000, onError, onRetrying, onRetryFailed } = settings;
|
||||
const oldFn = (0, retry_1.default)({
|
||||
retryFunction: descriptor.value,
|
||||
settings: { retries, timeout },
|
||||
onError,
|
||||
onRetrying,
|
||||
onRetryFailed,
|
||||
context: null,
|
||||
});
|
||||
descriptor.value = function (...args) {
|
||||
return oldFn.apply(this, args);
|
||||
};
|
||||
return descriptor;
|
||||
};
|
||||
}
|
||||
exports.default = promiseRetryDecorator;
|
||||
@@ -0,0 +1,51 @@
|
||||
import promiseRetry from '../retry';
|
||||
|
||||
/**
|
||||
* 异步函数重试 Interface
|
||||
* @param {number} [retries = 5] 重试次数
|
||||
* @param {number} [timeout = 2000] 重试间隔时间
|
||||
* @param {Function=} onError 重试错误回调
|
||||
* @param {Function=} onRetrying 重试回调
|
||||
* @param {Function=} onRetryFailed 重试失败回调
|
||||
*/
|
||||
interface IPromiseRetryDecoratorSettings {
|
||||
retries?: number;
|
||||
timeout?: number;
|
||||
onError?: Function;
|
||||
onRetrying?: Function;
|
||||
onRetryFailed?: Function;
|
||||
};
|
||||
|
||||
/**
|
||||
* 装饰器函数:给异步函数增加重试
|
||||
* @param {Object} settings 入参
|
||||
* @returns {Function}
|
||||
* @example
|
||||
* class LocalStream {
|
||||
* @promiseRetryDecorator({
|
||||
* retries: 10,
|
||||
* timeout: 3000,
|
||||
* onRetryFailed: function(error) {
|
||||
* }
|
||||
* })
|
||||
* async recoverCapture(options) {}
|
||||
* }
|
||||
*/
|
||||
export default function promiseRetryDecorator(settings: IPromiseRetryDecoratorSettings) {
|
||||
return function(target, name, descriptor) {
|
||||
const { retries = 5, timeout = 2000, onError, onRetrying, onRetryFailed } = settings;
|
||||
const oldFn = promiseRetry({
|
||||
retryFunction: descriptor.value,
|
||||
settings: { retries, timeout },
|
||||
onError,
|
||||
onRetrying,
|
||||
onRetryFailed,
|
||||
context: null,
|
||||
});
|
||||
|
||||
descriptor.value = function(...args) {
|
||||
return oldFn.apply(this, args);
|
||||
};
|
||||
return descriptor;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// eslint-disable-next-line
|
||||
declare var wx: any;
|
||||
// eslint-disable-next-line
|
||||
declare var uni: any;
|
||||
// eslint-disable-next-line
|
||||
declare var window: any;
|
||||
|
||||
// 在 uniApp 框架下,打包 H5、ios app、android app 时存在 wx/qq/tt/swan/my 等变量会导致引入 web sdk 环境判断失效
|
||||
// 小程序 getSystemInfoSync 返回的 fontSizeSetting 在 H5 和 app 中为 undefined,所以通过 fontSizeSetting 增强小程序环境判断
|
||||
// wx 小程序
|
||||
export const IN_WX_MINI_APP = (typeof wx !== 'undefined' && typeof wx.getSystemInfoSync === 'function' && Boolean(wx.getSystemInfoSync().fontSizeSetting));
|
||||
|
||||
// 用 uni-app 打包 native app,此时运行于 js core,无 window 等对象,此时调用 api 都得 uni.xxx,由于风格跟小程序类似,就归为 IN_MINI_APP 的一种
|
||||
export const IN_UNI_NATIVE_APP = (typeof uni !== 'undefined' && typeof uni === 'undefined');
|
||||
|
||||
export const IN_MINI_APP = IN_WX_MINI_APP || IN_UNI_NATIVE_APP;
|
||||
|
||||
export const IN_UNI_APP = (typeof uni !== 'undefined');
|
||||
|
||||
// 在 uniApp 框架下,由于客户打包 ios app、android app 时 window 不一定存在,所以通过 !IN_MINI_APP 进行判断
|
||||
// 非 uniApp 框架下,仍然通过 window 结合 IN_MINI_APP 进行判断,可兼容 Taro3.0+ 暴露 window 对象引起的 IN_BROWSER 判断失效问题
|
||||
export const IN_BROWSER = (function () {
|
||||
if (typeof uni !== 'undefined') {
|
||||
return !IN_MINI_APP;
|
||||
}
|
||||
return (typeof window !== 'undefined') && !IN_MINI_APP;
|
||||
}());
|
||||
|
||||
// 命名空间
|
||||
export const APP_NAMESPACE = (function () {
|
||||
if (IN_WX_MINI_APP) {
|
||||
return wx;
|
||||
}
|
||||
if (IN_UNI_APP) {
|
||||
return uni;
|
||||
}
|
||||
return window;
|
||||
}());
|
||||
|
||||
// eslint-disable-next-line no-mixed-operators
|
||||
const USER_AGENT = IN_BROWSER && window && window.navigator && window.navigator.userAgent || '';
|
||||
const IS_ANDROID = /Android/i.test(USER_AGENT);
|
||||
const IS_WIN_PHONE = /(?:Windows Phone)/.test(USER_AGENT);
|
||||
const IS_SYMBIAN = /(?:SymbianOS)/.test(USER_AGENT);
|
||||
const IS_IOS = /iPad/i.test(USER_AGENT) || /iPhone/i.test(USER_AGENT) || /iPod/i.test(USER_AGENT);
|
||||
|
||||
export const IS_H5 = IS_ANDROID || IS_WIN_PHONE || IS_SYMBIAN || IS_IOS;
|
||||
|
||||
export const IS_PC = IN_BROWSER && !IS_H5;
|
||||
export const IS_WIN = IS_PC && USER_AGENT.includes('Windows NT');
|
||||
export const IS_MAC = IS_PC && USER_AGENT.includes('Mac');
|
||||
@@ -0,0 +1,16 @@
|
||||
export async function checkLocalMP3FileExists(src: string) {
|
||||
if (!src) return false;
|
||||
try {
|
||||
const response = await new Promise<XMLHttpRequest>((resolve, reject) => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('HEAD', src, true);
|
||||
xhr.onload = () => resolve(xhr);
|
||||
xhr.onerror = () => reject(xhr);
|
||||
xhr.send();
|
||||
});
|
||||
return response.status === 200 && response.getResponseHeader('Content-Type') === 'audio/mpeg';
|
||||
} catch (error) {
|
||||
console.warn(error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { isPlainObject } from './common-utils';
|
||||
|
||||
const isEmpty = function (input: any) {
|
||||
// Null and Undefined...
|
||||
if (input === null || typeof (input) === 'undefined') return true;
|
||||
// Booleans...
|
||||
if (typeof input === 'boolean') return false;
|
||||
// Numbers...
|
||||
if (typeof input === 'number') return input === 0;
|
||||
// Strings...
|
||||
if (typeof input === 'string') return input.length === 0;
|
||||
// Functions...
|
||||
if (typeof input === 'function') return input.length === 0;
|
||||
// Arrays...
|
||||
if (Array.isArray(input)) return input.length === 0;
|
||||
// Errors...
|
||||
if (input instanceof Error) return input.message === '';
|
||||
// plain object
|
||||
if (isPlainObject(input)) {
|
||||
// eslint-disable-next-line
|
||||
for (const key in input) {
|
||||
if (Object.prototype.hasOwnProperty.call(input, key)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export default isEmpty;
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* 给异步函数封装重试逻辑
|
||||
* @param {Object} options 重试逻辑入参
|
||||
* @param {Object} options.retryFunction 需要封装的异步函数
|
||||
* @param {Object} options.settings 重试属性
|
||||
* @param {Number} [options.settings.retries = 5] 重试次数
|
||||
* @param {Number} [options.settings.timeout = 1000] 重试间隔
|
||||
* @param {onErrorCallback} options.onError 重试错误回调
|
||||
* @param {onRetryingCallback} [options.onRetrying] 重试后的回调
|
||||
* @param {Object} options.context 上下文,可选
|
||||
* @returns {Function} 封装后的函数
|
||||
* @example
|
||||
* const getUserMedia = promiseRetry({
|
||||
* retryFunction: getUserMedia_,
|
||||
* settings: { retries: 5, timeout: 2000 },
|
||||
* onError: (error, retry, reject) => {
|
||||
* if (error.name === 'NotReadableError') {
|
||||
* retry();
|
||||
* } else {
|
||||
* reject(error);
|
||||
* }
|
||||
* },
|
||||
* onRetrying: retryCount => {
|
||||
* console.warn(`getUserMedia NotReadableError observed, retrying [${retryCount}/5]`);
|
||||
* }
|
||||
* });
|
||||
*/
|
||||
declare function promiseRetry({ retryFunction, settings, onError, onRetrying, onRetryFailed, context }: {
|
||||
retryFunction: any;
|
||||
settings: any;
|
||||
onError: any;
|
||||
onRetrying: any;
|
||||
onRetryFailed: any;
|
||||
context: any;
|
||||
}): (...args: any[]) => Promise<unknown>;
|
||||
export default promiseRetry;
|
||||
@@ -0,0 +1,95 @@
|
||||
"use strict";
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const common_utils_1 = require("./common-utils");
|
||||
const RETRY_STATE_NOT_START = 0;
|
||||
const RETRY_STATE_STARTED = 1;
|
||||
const RETRY_STATE_STOPPED = 2;
|
||||
/**
|
||||
* 给异步函数封装重试逻辑
|
||||
* @param {Object} options 重试逻辑入参
|
||||
* @param {Object} options.retryFunction 需要封装的异步函数
|
||||
* @param {Object} options.settings 重试属性
|
||||
* @param {Number} [options.settings.retries = 5] 重试次数
|
||||
* @param {Number} [options.settings.timeout = 1000] 重试间隔
|
||||
* @param {onErrorCallback} options.onError 重试错误回调
|
||||
* @param {onRetryingCallback} [options.onRetrying] 重试后的回调
|
||||
* @param {Object} options.context 上下文,可选
|
||||
* @returns {Function} 封装后的函数
|
||||
* @example
|
||||
* const getUserMedia = promiseRetry({
|
||||
* retryFunction: getUserMedia_,
|
||||
* settings: { retries: 5, timeout: 2000 },
|
||||
* onError: (error, retry, reject) => {
|
||||
* if (error.name === 'NotReadableError') {
|
||||
* retry();
|
||||
* } else {
|
||||
* reject(error);
|
||||
* }
|
||||
* },
|
||||
* onRetrying: retryCount => {
|
||||
* console.warn(`getUserMedia NotReadableError observed, retrying [${retryCount}/5]`);
|
||||
* }
|
||||
* });
|
||||
*/
|
||||
function promiseRetry({ retryFunction, settings, onError, onRetrying, onRetryFailed, context }) {
|
||||
return function (...args) {
|
||||
const retries = settings.retries || 5;
|
||||
let retryCount = 0;
|
||||
let timer = -1;
|
||||
let retryState = RETRY_STATE_NOT_START;
|
||||
const run = (resolve, reject) => __awaiter(this, void 0, void 0, function* () {
|
||||
const ctx = context || this;
|
||||
try {
|
||||
const result = yield retryFunction.apply(ctx, args);
|
||||
// 执行成功,正常返回
|
||||
retryCount = 0;
|
||||
resolve(result);
|
||||
}
|
||||
catch (error) {
|
||||
// 用于停止重试
|
||||
const stopRetry = () => {
|
||||
clearTimeout(timer);
|
||||
retryCount = 0;
|
||||
retryState = RETRY_STATE_STOPPED;
|
||||
reject(error);
|
||||
};
|
||||
const retry = () => {
|
||||
if (retryState !== RETRY_STATE_STOPPED && retryCount < retries) {
|
||||
retryCount++;
|
||||
retryState = RETRY_STATE_STARTED;
|
||||
if ((0, common_utils_1.isFunction)(onRetrying)) {
|
||||
onRetrying.call(ctx, retryCount, stopRetry);
|
||||
}
|
||||
timer = setTimeout(() => {
|
||||
timer = -1;
|
||||
run(resolve, reject);
|
||||
}, (0, common_utils_1.isUndefined)(settings.timeout) ? 1000 : settings.timeout);
|
||||
}
|
||||
else {
|
||||
stopRetry();
|
||||
if ((0, common_utils_1.isFunction)(onRetryFailed)) {
|
||||
onRetryFailed.call(ctx, error);
|
||||
}
|
||||
}
|
||||
};
|
||||
if ((0, common_utils_1.isFunction)(onError)) {
|
||||
onError.call(ctx, error, retry, reject, args);
|
||||
}
|
||||
else {
|
||||
retry();
|
||||
}
|
||||
}
|
||||
});
|
||||
return new Promise(run);
|
||||
};
|
||||
}
|
||||
exports.default = promiseRetry;
|
||||
@@ -0,0 +1,88 @@
|
||||
import { isFunction, isUndefined } from './common-utils';
|
||||
|
||||
const RETRY_STATE_NOT_START = 0;
|
||||
const RETRY_STATE_STARTED = 1;
|
||||
const RETRY_STATE_STOPPED = 2;
|
||||
|
||||
/**
|
||||
* 给异步函数封装重试逻辑
|
||||
* @param {Object} options 重试逻辑入参
|
||||
* @param {Object} options.retryFunction 需要封装的异步函数
|
||||
* @param {Object} options.settings 重试属性
|
||||
* @param {Number} [options.settings.retries = 5] 重试次数
|
||||
* @param {Number} [options.settings.timeout = 1000] 重试间隔
|
||||
* @param {onErrorCallback} options.onError 重试错误回调
|
||||
* @param {onRetryingCallback} [options.onRetrying] 重试后的回调
|
||||
* @param {Object} options.context 上下文,可选
|
||||
* @returns {Function} 封装后的函数
|
||||
* @example
|
||||
* const getUserMedia = promiseRetry({
|
||||
* retryFunction: getUserMedia_,
|
||||
* settings: { retries: 5, timeout: 2000 },
|
||||
* onError: (error, retry, reject) => {
|
||||
* if (error.name === 'NotReadableError') {
|
||||
* retry();
|
||||
* } else {
|
||||
* reject(error);
|
||||
* }
|
||||
* },
|
||||
* onRetrying: retryCount => {
|
||||
* console.warn(`getUserMedia NotReadableError observed, retrying [${retryCount}/5]`);
|
||||
* }
|
||||
* });
|
||||
*/
|
||||
function promiseRetry({ retryFunction, settings, onError, onRetrying, onRetryFailed, context }) {
|
||||
return function(...args) {
|
||||
const retries = settings.retries || 5;
|
||||
let retryCount = 0;
|
||||
let timer: any = -1;
|
||||
let retryState = RETRY_STATE_NOT_START;
|
||||
const run = async (resolve, reject) => {
|
||||
const ctx = context || this;
|
||||
try {
|
||||
const result = await retryFunction.apply(ctx, args);
|
||||
// 执行成功,正常返回
|
||||
retryCount = 0;
|
||||
resolve(result);
|
||||
} catch (error) {
|
||||
// 用于停止重试
|
||||
const stopRetry = () => {
|
||||
clearTimeout(timer);
|
||||
retryCount = 0;
|
||||
retryState = RETRY_STATE_STOPPED;
|
||||
reject(error);
|
||||
};
|
||||
const retry = () => {
|
||||
if (retryState !== RETRY_STATE_STOPPED && retryCount < retries) {
|
||||
retryCount++;
|
||||
retryState = RETRY_STATE_STARTED;
|
||||
if (isFunction(onRetrying)) {
|
||||
onRetrying.call(ctx, retryCount, stopRetry);
|
||||
}
|
||||
timer = setTimeout(
|
||||
() => {
|
||||
timer = -1;
|
||||
run(resolve, reject);
|
||||
},
|
||||
isUndefined(settings.timeout) ? 1000 : settings.timeout
|
||||
);
|
||||
} else {
|
||||
stopRetry();
|
||||
if (isFunction(onRetryFailed)) {
|
||||
onRetryFailed.call(ctx, error);
|
||||
}
|
||||
}
|
||||
};
|
||||
if (isFunction(onError)) {
|
||||
onError.call(ctx, error, retry, reject, args);
|
||||
} else {
|
||||
retry();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return new Promise(run);
|
||||
};
|
||||
}
|
||||
|
||||
export default promiseRetry;
|
||||
+1
-1
@@ -42,7 +42,7 @@ declare class Timer {
|
||||
* @param {*} count
|
||||
* @returns ID
|
||||
*/
|
||||
static interval(taskItem: any): NodeJS.Timer;
|
||||
static interval(taskItem: any): NodeJS.Timeout;
|
||||
/**
|
||||
* 延迟执行回调
|
||||
* count = 0,循环
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
/* eslint-disable */
|
||||
import { isPlainObject, performanceNow, isFunction } from './common-utils';
|
||||
import { NAME } from '../const/index';
|
||||
|
||||
/**
|
||||
* 定时器,功能:
|
||||
* 1. 支持定时执行回调 [1,n] 次,用于常规延迟、定时操作
|
||||
* @example
|
||||
* // 默认嵌套执行,count=0 无限次
|
||||
* timer.run(callback, {delay: 2000});
|
||||
* // count=1 等同于 原始setTimeout
|
||||
* timer.run(callback, {delay: 2000, count:0});
|
||||
* 2. 支持 RAF 执行回调,用于小流渲染,audio音量获取等任务,需要渲染频率稳定,支持页面退后台后,用 setTimeout 接管,最短 1s 执行一次
|
||||
* @example
|
||||
* // 默认60fps,可以根据单位时长换算,默认开启后台执行
|
||||
* timer.run('raf', callback, {fps: 60});
|
||||
* // 设置执行次数
|
||||
* timer.run('raf', callback, {fps: 60, count: 300, backgroundTask: false});
|
||||
* 3. 支持空闲任务执行回调, requestIdleCallback 在帧渲染的空闲时间执行任务,可以用于 storage 写入等低优先级的任务
|
||||
* @example
|
||||
* // 支持原生setInterval 但不推荐使用,定时任务推荐用 timeout
|
||||
* timer.run('interval', callback, {delay:2000, count:10})
|
||||
*/
|
||||
|
||||
class Timer {
|
||||
static taskMap = new Map();
|
||||
static currentTaskID = 1;
|
||||
static generateTaskID() {
|
||||
return this.currentTaskID++;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param {string} taskName 'interval' 'timeout'
|
||||
* @param {function} callback
|
||||
* @param {object} options include:
|
||||
* @param {number} options.delay millisecond
|
||||
* @param {number} options.count 定时器回调执行次数,0 无限次 or n次
|
||||
* @param {boolean} options.backgroundTask 在页面静默后是否继续执行定时器
|
||||
*/
|
||||
static run(taskName = NAME.TIMEOUT, callback: any, options: any) {
|
||||
// default options
|
||||
if (taskName === NAME.INTERVAL) {
|
||||
options = { ...{ delay: 2000, count: 0, backgroundTask: true }, ...options };
|
||||
} else {
|
||||
options = { ...{ delay: 2000, count: 0, backgroundTask: true }, ...options };
|
||||
}
|
||||
// call run(function, {...})
|
||||
if (isPlainObject(callback)) {
|
||||
options = { ...options, ...callback };
|
||||
}
|
||||
if (isFunction(taskName)) {
|
||||
callback = taskName;
|
||||
taskName = NAME.TIMEOUT;
|
||||
}
|
||||
// 1. 创建 taskID,作为 timer task 的唯一标识,在本函数执行完后返回,用于在调用的地方实现互斥逻辑
|
||||
// 2. 根据 taskName 执行相应的函数
|
||||
const taskItem = {
|
||||
taskID: this.generateTaskID(),
|
||||
loopCount: 0,
|
||||
intervalID: null,
|
||||
timeoutID: null,
|
||||
taskName,
|
||||
callback,
|
||||
...options
|
||||
};
|
||||
this.taskMap.set(taskItem.taskID, taskItem);
|
||||
// console.log(`timer run task:${taskItem.taskName}, task queue size: ${this.taskMap.size}`);
|
||||
if (taskName === NAME.INTERNAL) {
|
||||
this.interval(taskItem);
|
||||
} else {
|
||||
this.timeout(taskItem);
|
||||
}
|
||||
return taskItem.taskID;
|
||||
}
|
||||
|
||||
/**
|
||||
* 定时循环执行回调函数
|
||||
* 可以指定循环的时间间隔
|
||||
* 可以指定循环次数
|
||||
* @param {object} taskItem
|
||||
* @param {function} callback
|
||||
* @param {*} delay
|
||||
* @param {*} count
|
||||
* @returns ID
|
||||
*/
|
||||
static interval(taskItem: any) {
|
||||
// setInterval 缺点,浏览器退后台会降频,循环执行间隔时间不可靠
|
||||
// 创建进入定时器循环的任务函数,函数内:1. 判断是否满足执行条件,2.执行 callback
|
||||
// 通过 setInterval 执行任务函数
|
||||
// 将 intervalID 记录到 taskMap 对应的 item
|
||||
const task = () => {
|
||||
taskItem.callback();
|
||||
taskItem.loopCount += 1;
|
||||
if (this.isBreakLoop(taskItem)) {
|
||||
return;
|
||||
}
|
||||
};
|
||||
return taskItem.intervalID = setInterval(task, taskItem.delay);
|
||||
}
|
||||
/**
|
||||
* 延迟执行回调
|
||||
* count = 0,循环
|
||||
* count = n, 执行n次
|
||||
* @param {object} taskItem
|
||||
*
|
||||
*/
|
||||
static timeout(taskItem: any) {
|
||||
// setTimeout 浏览器退后台,延迟变为至少1s
|
||||
const task: any = () => {
|
||||
// 执行回调
|
||||
taskItem.callback();
|
||||
taskItem.loopCount += 1;
|
||||
if (this.isBreakLoop(taskItem)) {
|
||||
return;
|
||||
}
|
||||
// 不修正延迟,每次callback间隔平均
|
||||
return taskItem.timeoutID = setTimeout(task, taskItem.delay);
|
||||
};
|
||||
return taskItem.timeoutID = setTimeout(task, taskItem.delay);
|
||||
}
|
||||
|
||||
static hasTask(taskID: any) {
|
||||
return this.taskMap.has(taskID);
|
||||
}
|
||||
|
||||
static clearTask(taskID: any) {
|
||||
// console.log('timer clearTask start', `| taskID:${taskID} | size:${this.taskMap.size}`);
|
||||
if (!this.taskMap.has(taskID)) {
|
||||
return true;
|
||||
}
|
||||
const { intervalID, timeoutID, onVisibilitychange } = this.taskMap.get(taskID);
|
||||
if (intervalID) {
|
||||
clearInterval(intervalID);
|
||||
}
|
||||
if (timeoutID) {
|
||||
clearTimeout(timeoutID);
|
||||
}
|
||||
if (onVisibilitychange) {
|
||||
document.removeEventListener('visibilitychange', onVisibilitychange);
|
||||
}
|
||||
this.taskMap.delete(taskID);
|
||||
// console.log('timer clearTask end ', `| size:${this.taskMap.size}`);
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* 1. 如果已移除出定时队列,退出当前任务
|
||||
* 2. 如果当前任务已满足次数限制,则退出当前任务
|
||||
* @param {object} taskItem
|
||||
* @returns
|
||||
*/
|
||||
static isBreakLoop(taskItem: any) {
|
||||
if (!this.taskMap.has(taskItem.taskID)) {
|
||||
return true;
|
||||
}
|
||||
if (taskItem.count !== 0 && taskItem.loopCount >= taskItem.count) {
|
||||
this.clearTask(taskItem.taskID);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
export default Timer;
|
||||
@@ -0,0 +1,34 @@
|
||||
import { NAME } from '../../const/index';
|
||||
|
||||
/**
|
||||
* 装饰器:阻止函数重复调用
|
||||
* @export
|
||||
* @param {Object} options 入参
|
||||
* @param {Function} options.fn 函数
|
||||
* @param {Object} options.context 上下文对象
|
||||
* @param {String} options.name 函数名
|
||||
* @returns {Function} 封装后的函数
|
||||
*/
|
||||
export function avoidRepeatedCall() {
|
||||
return function (target: any, name: string, descriptor: any) {
|
||||
const oldFn = descriptor.value;
|
||||
const isCallingSet = new Set();
|
||||
descriptor.value = async function (...args: any[]) {
|
||||
if (isCallingSet.has(this)) {
|
||||
console.warn((`${NAME.PREFIX}previous ${name}() is ongoing, please avoid repeated calls`));
|
||||
// throw new Error(`previous ${name}() is ongoing, please avoid repeated calls`);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
isCallingSet.add(this);
|
||||
const result = await oldFn.apply(this, args);
|
||||
isCallingSet.delete(this);
|
||||
return result;
|
||||
} catch (error) {
|
||||
isCallingSet.delete(this);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
return descriptor;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { avoidRepeatedCall } from './avoidRepeatedCall';
|
||||
import { paramValidate } from './validateParams';
|
||||
import { VALIDATE_PARAMS } from './validateConfig';
|
||||
// import { apiCallQueue } from "./apiCallQueue";
|
||||
|
||||
export {
|
||||
VALIDATE_PARAMS,
|
||||
paramValidate,
|
||||
avoidRepeatedCall,
|
||||
// apiCallQueue,
|
||||
};
|
||||
@@ -0,0 +1,188 @@
|
||||
import { NAME, MAX_NUMBER_ROOM_ID, VideoResolution, VideoDisplayMode } from "../../const/index";
|
||||
|
||||
export const VALIDATE_PARAMS = {
|
||||
init: {
|
||||
SDKAppID: {
|
||||
required: true,
|
||||
rules: [NAME.NUMBER],
|
||||
allowEmpty: false,
|
||||
},
|
||||
userID: {
|
||||
required: true,
|
||||
rules: [NAME.STRING],
|
||||
allowEmpty: false,
|
||||
},
|
||||
userSig: {
|
||||
required: true,
|
||||
rules: [NAME.STRING],
|
||||
allowEmpty: false,
|
||||
},
|
||||
tim: {
|
||||
required: false,
|
||||
rules: [NAME.OBJECT],
|
||||
},
|
||||
},
|
||||
call: {
|
||||
userID: {
|
||||
required: true,
|
||||
rules: [NAME.STRING],
|
||||
allowEmpty: false
|
||||
},
|
||||
type: {
|
||||
required: true,
|
||||
rules: [NAME.NUMBER],
|
||||
range: [1, 2],
|
||||
allowEmpty: false
|
||||
},
|
||||
roomID: {
|
||||
required: false,
|
||||
rules: [NAME.NUMBER], // 仅支持数字房间号, 后续会支持字符串房间号
|
||||
range: `1~${MAX_NUMBER_ROOM_ID}`,
|
||||
allowEmpty: false,
|
||||
},
|
||||
userData: {
|
||||
required: false,
|
||||
rules: [NAME.STRING],
|
||||
allowEmpty: false,
|
||||
},
|
||||
timeout: {
|
||||
required: false,
|
||||
rules: [NAME.NUMBER],
|
||||
allowEmpty: false
|
||||
}
|
||||
},
|
||||
groupCall: {
|
||||
userIDList: {
|
||||
required: true,
|
||||
rules: [NAME.ARRAY],
|
||||
allowEmpty: false
|
||||
},
|
||||
type: {
|
||||
required: true,
|
||||
rules: [NAME.NUMBER],
|
||||
range: [1, 2],
|
||||
allowEmpty: false
|
||||
},
|
||||
groupID: {
|
||||
required: true,
|
||||
rules: [NAME.STRING],
|
||||
allowEmpty: false
|
||||
},
|
||||
roomID: {
|
||||
required: false,
|
||||
rules: [NAME.NUMBER], // 仅支持数字房间号, 后续会支持字符串房间号
|
||||
range: `1~${MAX_NUMBER_ROOM_ID}`,
|
||||
allowEmpty: false
|
||||
},
|
||||
timeout: {
|
||||
required: false,
|
||||
rules: [NAME.NUMBER],
|
||||
allowEmpty: false
|
||||
},
|
||||
userData: {
|
||||
required: false,
|
||||
rules: [NAME.STRING],
|
||||
allowEmpty: false,
|
||||
},
|
||||
offlinePushInfo: {
|
||||
required: false,
|
||||
rules: [NAME.OBJECT],
|
||||
allowEmpty: false,
|
||||
},
|
||||
},
|
||||
joinInGroupCall: {
|
||||
type: {
|
||||
required: true,
|
||||
rules: [NAME.NUMBER],
|
||||
range: [1, 2],
|
||||
allowEmpty: false
|
||||
},
|
||||
groupID: {
|
||||
required: true,
|
||||
rules: [NAME.STRING],
|
||||
allowEmpty: false
|
||||
},
|
||||
roomID: {
|
||||
required: true,
|
||||
rules: [NAME.NUMBER],
|
||||
allowEmpty: false,
|
||||
},
|
||||
},
|
||||
inviteUser: {
|
||||
userIDList: {
|
||||
required: true,
|
||||
rules: [NAME.ARRAY],
|
||||
allowEmpty: false
|
||||
},
|
||||
},
|
||||
setSelfInfo: {
|
||||
nickName: {
|
||||
required: false,
|
||||
rules: [NAME.STRING],
|
||||
allowEmpty: false,
|
||||
},
|
||||
avatar: {
|
||||
required: false,
|
||||
rules: [NAME.STRING],
|
||||
allowEmpty: false,
|
||||
}
|
||||
},
|
||||
enableFloatWindow: [
|
||||
{
|
||||
key: "enable",
|
||||
required: false,
|
||||
rules: [NAME.BOOLEAN],
|
||||
allowEmpty: false,
|
||||
}
|
||||
],
|
||||
enableAIVoice: [
|
||||
{
|
||||
key: "enable",
|
||||
required: true,
|
||||
rules: [NAME.BOOLEAN],
|
||||
allowEmpty: false,
|
||||
}
|
||||
],
|
||||
enableMuteMode: [
|
||||
{
|
||||
key: "enable",
|
||||
required: true,
|
||||
rules: [NAME.BOOLEAN],
|
||||
allowEmpty: false,
|
||||
}
|
||||
],
|
||||
setCallingBell: [
|
||||
{
|
||||
key: "filePath",
|
||||
required: false,
|
||||
rules: [NAME.STRING],
|
||||
allowEmpty: true,
|
||||
}
|
||||
],
|
||||
setLanguage: [
|
||||
{
|
||||
key: "language",
|
||||
required: true,
|
||||
rules: [NAME.STRING],
|
||||
allowEmpty: false
|
||||
}
|
||||
],
|
||||
setVideoDisplayMode: [
|
||||
{
|
||||
key: "displayMode",
|
||||
required: true,
|
||||
rules: [NAME.STRING],
|
||||
range: [VideoDisplayMode.CONTAIN, VideoDisplayMode.COVER, VideoDisplayMode.FILL],
|
||||
allowEmpty: false
|
||||
}
|
||||
],
|
||||
setVideoResolution: [
|
||||
{
|
||||
key: "resolution",
|
||||
required: true,
|
||||
rules: [NAME.STRING],
|
||||
range: [VideoResolution.RESOLUTION_1080P, VideoResolution.RESOLUTION_480P, VideoResolution.RESOLUTION_720P],
|
||||
allowEmpty: false
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
import { getType, isArray, isString, isUndefined, isNumber, modifyObjectKey } from "../common-utils";
|
||||
import { NAME } from "../../const/index";
|
||||
const PREFIX = NAME.PREFIX + "API";
|
||||
|
||||
export function paramValidate (config: any) {
|
||||
return function (target, propertyName: string, descriptor: PropertyDescriptor) {
|
||||
let method = descriptor.value;
|
||||
descriptor.value = function (...args) {
|
||||
doValidate.call(this, config, args, propertyName);
|
||||
return method.apply(this, args);
|
||||
};
|
||||
return descriptor;
|
||||
};
|
||||
}
|
||||
function doValidate(config, args, name) {
|
||||
try {
|
||||
// 兼容 init 方法中: SDKAppID sdkAppID 两种写法的参数校验判断
|
||||
if (!args[0].SDKAppID) {
|
||||
config = modifyObjectKey(config, "SDKAppID", "sdkAppID");
|
||||
}
|
||||
if (isArray(config)) {
|
||||
for (let i = 0; i < config.length; i++) {
|
||||
check.call(this, {
|
||||
...config[i],
|
||||
value: args[i],
|
||||
name,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
for (const key in config) {
|
||||
if (config.hasOwnProperty(key)) {
|
||||
check.call(this, {
|
||||
...config[key],
|
||||
value: args[0][key],
|
||||
name,
|
||||
key,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
function check({ required, rules, range, value, allowEmpty, name, key }) {
|
||||
// 用户没传指定参数
|
||||
if (isUndefined(value)) {
|
||||
// 检查必填参数, 若配置是必填则报错
|
||||
if (required) {
|
||||
throw new Error(`${PREFIX}<${name}>: ${key} is required.`);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// 判断参数类型是否正确
|
||||
const result = rules.some((item)=>item === getType(value));
|
||||
let type = '';
|
||||
if (!result) {
|
||||
for (let i = 0; i < rules.length; i++) {
|
||||
let str = rules[i];
|
||||
str = str.replace(str[0], str[0].toUpperCase());
|
||||
type += `${str}/`;
|
||||
}
|
||||
type = type.substring(0, type.length - 1);
|
||||
throw new Error(`${PREFIX}<${name}>: ${key} must be ${type}, current ${key} is ${typeof value}.`);
|
||||
}
|
||||
// 不允许传空值, 例如: '', ' '
|
||||
if (allowEmpty === false) {
|
||||
const isEmptyString = isString(value) && value.trim() === '';
|
||||
if (isEmptyString) {
|
||||
throw new Error(`${PREFIX}<${name}>: ${key} is blank.`);
|
||||
}
|
||||
}
|
||||
// 判断是否符合限制条件
|
||||
if (isArray(range)) {
|
||||
if (range && range.indexOf(value) === -1) {
|
||||
throw new Error(`${PREFIX}<${name}>: ${key} error, only be ${range}, current ${key} is ${value}.`);
|
||||
}
|
||||
}
|
||||
// 取值范围, 前闭后闭
|
||||
if (isString(range) && range.indexOf('~') !== -1) {
|
||||
const valueList = range.split('~');
|
||||
if (value < +valueList[0] || value > +valueList[1] || (isNumber(value) && Number.isNaN(value))) {
|
||||
throw new Error(`${PREFIX}<${name}>: ${key} error, only be ${range}, current ${key} is ${value}.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user