616 lines
19 KiB
JavaScript
616 lines
19 KiB
JavaScript
import { isArray, hasOwn, isString, isPlainObject, isObject, toRawType, capitalize, makeMap, isFunction, isPromise, extend, remove } from '@vue/shared';
|
||
|
||
function validateProtocolFail(name, msg) {
|
||
console.warn(`${name}: ${msg}`);
|
||
}
|
||
function validateProtocol(name, data, protocol, onFail) {
|
||
if (!onFail) {
|
||
onFail = validateProtocolFail;
|
||
}
|
||
for (const key in protocol) {
|
||
const errMsg = validateProp(key, data[key], protocol[key], !hasOwn(data, key));
|
||
if (isString(errMsg)) {
|
||
onFail(name, errMsg);
|
||
}
|
||
}
|
||
}
|
||
function validateProtocols(name, args, protocol, onFail) {
|
||
if (!protocol) {
|
||
return;
|
||
}
|
||
if (!isArray(protocol)) {
|
||
return validateProtocol(name, args[0] || Object.create(null), protocol, onFail);
|
||
}
|
||
const len = protocol.length;
|
||
const argsLen = args.length;
|
||
for (let i = 0; i < len; i++) {
|
||
const opts = protocol[i];
|
||
const data = Object.create(null);
|
||
if (argsLen > i) {
|
||
data[opts.name] = args[i];
|
||
}
|
||
validateProtocol(name, data, { [opts.name]: opts }, onFail);
|
||
}
|
||
}
|
||
function validateProp(name, value, prop, isAbsent) {
|
||
if (!isPlainObject(prop)) {
|
||
prop = { type: prop };
|
||
}
|
||
const { type, required, validator } = prop;
|
||
// required!
|
||
if (required && isAbsent) {
|
||
return 'Missing required args: "' + name + '"';
|
||
}
|
||
// missing but optional
|
||
if (value == null && !required) {
|
||
return;
|
||
}
|
||
// type check
|
||
if (type != null) {
|
||
let isValid = false;
|
||
const types = isArray(type) ? type : [type];
|
||
const expectedTypes = [];
|
||
// value is valid as long as one of the specified types match
|
||
for (let i = 0; i < types.length && !isValid; i++) {
|
||
const { valid, expectedType } = assertType(value, types[i]);
|
||
expectedTypes.push(expectedType || '');
|
||
isValid = valid;
|
||
}
|
||
if (!isValid) {
|
||
return getInvalidTypeMessage(name, value, expectedTypes);
|
||
}
|
||
}
|
||
// custom validator
|
||
if (validator) {
|
||
return validator(value);
|
||
}
|
||
}
|
||
const isSimpleType = /*#__PURE__*/ makeMap('String,Number,Boolean,Function,Symbol');
|
||
function assertType(value, type) {
|
||
let valid;
|
||
const expectedType = getType(type);
|
||
if (isSimpleType(expectedType)) {
|
||
const t = typeof value;
|
||
valid = t === expectedType.toLowerCase();
|
||
// for primitive wrapper objects
|
||
if (!valid && t === 'object') {
|
||
valid = value instanceof type;
|
||
}
|
||
}
|
||
else if (expectedType === 'Object') {
|
||
valid = isObject(value);
|
||
}
|
||
else if (expectedType === 'Array') {
|
||
valid = isArray(value);
|
||
}
|
||
else {
|
||
if (__PLATFORM__ === 'app') {
|
||
// App平台ArrayBuffer等参数跨实例传输,无法通过 instanceof 识别
|
||
valid = value instanceof type || toRawType(value) === getType(type);
|
||
}
|
||
else {
|
||
valid = value instanceof type;
|
||
}
|
||
}
|
||
return {
|
||
valid,
|
||
expectedType,
|
||
};
|
||
}
|
||
function getInvalidTypeMessage(name, value, expectedTypes) {
|
||
let message = `Invalid args: type check failed for args "${name}".` +
|
||
` Expected ${expectedTypes.map(capitalize).join(', ')}`;
|
||
const expectedType = expectedTypes[0];
|
||
const receivedType = toRawType(value);
|
||
const expectedValue = styleValue(value, expectedType);
|
||
const receivedValue = styleValue(value, receivedType);
|
||
// check if we need to specify expected value
|
||
if (expectedTypes.length === 1 &&
|
||
isExplicable(expectedType) &&
|
||
!isBoolean(expectedType, receivedType)) {
|
||
message += ` with value ${expectedValue}`;
|
||
}
|
||
message += `, got ${receivedType} `;
|
||
// check if we need to specify received value
|
||
if (isExplicable(receivedType)) {
|
||
message += `with value ${receivedValue}.`;
|
||
}
|
||
return message;
|
||
}
|
||
function getType(ctor) {
|
||
const match = ctor && ctor.toString().match(/^\s*function (\w+)/);
|
||
return match ? match[1] : '';
|
||
}
|
||
function styleValue(value, type) {
|
||
if (type === 'String') {
|
||
return `"${value}"`;
|
||
}
|
||
else if (type === 'Number') {
|
||
return `${Number(value)}`;
|
||
}
|
||
else {
|
||
return `${value}`;
|
||
}
|
||
}
|
||
function isExplicable(type) {
|
||
const explicitTypes = ['string', 'number', 'boolean'];
|
||
return explicitTypes.some((elem) => type.toLowerCase() === elem);
|
||
}
|
||
function isBoolean(...args) {
|
||
return args.some((elem) => elem.toLowerCase() === 'boolean');
|
||
}
|
||
|
||
function tryCatch(fn) {
|
||
return function () {
|
||
try {
|
||
return fn.apply(fn, arguments);
|
||
}
|
||
catch (e) {
|
||
// TODO
|
||
console.error(e);
|
||
}
|
||
};
|
||
}
|
||
|
||
let invokeCallbackId = 1;
|
||
const invokeCallbacks = {};
|
||
function addInvokeCallback(id, name, callback, keepAlive = false) {
|
||
invokeCallbacks[id] = {
|
||
name,
|
||
keepAlive,
|
||
callback,
|
||
};
|
||
return id;
|
||
}
|
||
// onNativeEventReceive((event,data)=>{}) 需要两个参数,目前写死最多两个参数
|
||
function invokeCallback(id, res, extras) {
|
||
if (typeof id === 'number') {
|
||
const opts = invokeCallbacks[id];
|
||
if (opts) {
|
||
if (!opts.keepAlive) {
|
||
delete invokeCallbacks[id];
|
||
}
|
||
return opts.callback(res, extras);
|
||
}
|
||
}
|
||
return res;
|
||
}
|
||
function findInvokeCallbackByName(name) {
|
||
for (const key in invokeCallbacks) {
|
||
if (invokeCallbacks[key].name === name) {
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
function removeKeepAliveApiCallback(name, callback) {
|
||
for (const key in invokeCallbacks) {
|
||
const item = invokeCallbacks[key];
|
||
if (item.callback === callback && item.name === name) {
|
||
delete invokeCallbacks[key];
|
||
}
|
||
}
|
||
}
|
||
function offKeepAliveApiCallback(name) {
|
||
UniServiceJSBridge.off('api.' + name);
|
||
}
|
||
function onKeepAliveApiCallback(name) {
|
||
UniServiceJSBridge.on('api.' + name, (res) => {
|
||
for (const key in invokeCallbacks) {
|
||
const opts = invokeCallbacks[key];
|
||
if (opts.name === name) {
|
||
opts.callback(res);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
function createKeepAliveApiCallback(name, callback) {
|
||
return addInvokeCallback(invokeCallbackId++, name, callback, true);
|
||
}
|
||
const API_SUCCESS = 'success';
|
||
const API_FAIL = 'fail';
|
||
const API_COMPLETE = 'complete';
|
||
function getApiCallbacks(args) {
|
||
const apiCallbacks = {};
|
||
for (const name in args) {
|
||
const fn = args[name];
|
||
if (isFunction(fn)) {
|
||
apiCallbacks[name] = tryCatch(fn);
|
||
delete args[name];
|
||
}
|
||
}
|
||
return apiCallbacks;
|
||
}
|
||
function normalizeErrMsg(errMsg, name) {
|
||
if (!errMsg || errMsg.indexOf(':fail') === -1) {
|
||
return name + ':ok';
|
||
}
|
||
return name + errMsg.substring(errMsg.indexOf(':fail'));
|
||
}
|
||
function createAsyncApiCallback(name, args = {}, { beforeAll, beforeSuccess } = {}) {
|
||
if (!isPlainObject(args)) {
|
||
args = {};
|
||
}
|
||
const { success, fail, complete } = getApiCallbacks(args);
|
||
const hasSuccess = isFunction(success);
|
||
const hasFail = isFunction(fail);
|
||
const hasComplete = isFunction(complete);
|
||
const callbackId = invokeCallbackId++;
|
||
addInvokeCallback(callbackId, name, (res) => {
|
||
res = res || {};
|
||
res.errMsg = normalizeErrMsg(res.errMsg, name);
|
||
isFunction(beforeAll) && beforeAll(res);
|
||
if (res.errMsg === name + ':ok') {
|
||
isFunction(beforeSuccess) && beforeSuccess(res, args);
|
||
hasSuccess && success(res);
|
||
}
|
||
else {
|
||
hasFail && fail(res);
|
||
}
|
||
hasComplete && complete(res);
|
||
});
|
||
return callbackId;
|
||
}
|
||
|
||
const HOOK_SUCCESS = 'success';
|
||
const HOOK_FAIL = 'fail';
|
||
const HOOK_COMPLETE = 'complete';
|
||
const globalInterceptors = {};
|
||
const scopedInterceptors = {};
|
||
function wrapperHook(hook, params) {
|
||
return function (data) {
|
||
return hook(data, params) || data;
|
||
};
|
||
}
|
||
function queue(hooks, data, params) {
|
||
let promise = false;
|
||
for (let i = 0; i < hooks.length; i++) {
|
||
const hook = hooks[i];
|
||
if (promise) {
|
||
promise = Promise.resolve(wrapperHook(hook, params));
|
||
}
|
||
else {
|
||
const res = hook(data, params);
|
||
if (isPromise(res)) {
|
||
promise = Promise.resolve(res);
|
||
}
|
||
if (res === false) {
|
||
return {
|
||
then() { },
|
||
catch() { },
|
||
};
|
||
}
|
||
}
|
||
}
|
||
return (promise || {
|
||
then(callback) {
|
||
return callback(data);
|
||
},
|
||
catch() { },
|
||
});
|
||
}
|
||
function wrapperOptions(interceptors, options = {}) {
|
||
[HOOK_SUCCESS, HOOK_FAIL, HOOK_COMPLETE].forEach((name) => {
|
||
const hooks = interceptors[name];
|
||
if (!isArray(hooks)) {
|
||
return;
|
||
}
|
||
const oldCallback = options[name];
|
||
options[name] = function callbackInterceptor(res) {
|
||
queue(hooks, res, options).then((res) => {
|
||
return (isFunction(oldCallback) && oldCallback(res)) || res;
|
||
});
|
||
};
|
||
});
|
||
return options;
|
||
}
|
||
function wrapperReturnValue(method, returnValue) {
|
||
const returnValueHooks = [];
|
||
if (isArray(globalInterceptors.returnValue)) {
|
||
returnValueHooks.push(...globalInterceptors.returnValue);
|
||
}
|
||
const interceptor = scopedInterceptors[method];
|
||
if (interceptor && isArray(interceptor.returnValue)) {
|
||
returnValueHooks.push(...interceptor.returnValue);
|
||
}
|
||
returnValueHooks.forEach((hook) => {
|
||
returnValue = hook(returnValue) || returnValue;
|
||
});
|
||
return returnValue;
|
||
}
|
||
function getApiInterceptorHooks(method) {
|
||
const interceptor = Object.create(null);
|
||
Object.keys(globalInterceptors).forEach((hook) => {
|
||
if (hook !== 'returnValue') {
|
||
interceptor[hook] = globalInterceptors[hook].slice();
|
||
}
|
||
});
|
||
const scopedInterceptor = scopedInterceptors[method];
|
||
if (scopedInterceptor) {
|
||
Object.keys(scopedInterceptor).forEach((hook) => {
|
||
if (hook !== 'returnValue') {
|
||
interceptor[hook] = (interceptor[hook] || []).concat(scopedInterceptor[hook]);
|
||
}
|
||
});
|
||
}
|
||
return interceptor;
|
||
}
|
||
function invokeApi(method, api, options, params) {
|
||
const interceptor = getApiInterceptorHooks(method);
|
||
if (interceptor && Object.keys(interceptor).length) {
|
||
if (isArray(interceptor.invoke)) {
|
||
const res = queue(interceptor.invoke, options);
|
||
return res.then((options) => {
|
||
// 重新访问 getApiInterceptorHooks, 允许 invoke 中再次调用 addInterceptor,removeInterceptor
|
||
return api(wrapperOptions(getApiInterceptorHooks(method), options), ...params);
|
||
});
|
||
}
|
||
else {
|
||
return api(wrapperOptions(interceptor, options), ...params);
|
||
}
|
||
}
|
||
return api(options, ...params);
|
||
}
|
||
|
||
function hasCallback(args) {
|
||
if (isPlainObject(args) &&
|
||
[API_SUCCESS, API_FAIL, API_COMPLETE].find((cb) => isFunction(args[cb]))) {
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
function handlePromise(promise) {
|
||
// if (__UNI_FEATURE_PROMISE__) {
|
||
// return promise
|
||
// .then((data) => {
|
||
// return [null, data]
|
||
// })
|
||
// .catch((err) => [err])
|
||
// }
|
||
return promise;
|
||
}
|
||
function promisify(name, fn) {
|
||
return (args = {}, ...rest) => {
|
||
if (hasCallback(args)) {
|
||
return wrapperReturnValue(name, invokeApi(name, fn, args, rest));
|
||
}
|
||
return wrapperReturnValue(name, handlePromise(new Promise((resolve, reject) => {
|
||
invokeApi(name, fn, extend(args, { success: resolve, fail: reject }), rest);
|
||
})));
|
||
};
|
||
}
|
||
|
||
function formatApiArgs(args, options) {
|
||
const params = args[0];
|
||
if (!options ||
|
||
!options.formatArgs ||
|
||
(!isPlainObject(options.formatArgs) && isPlainObject(params))) {
|
||
return;
|
||
}
|
||
const formatArgs = options.formatArgs;
|
||
const keys = Object.keys(formatArgs);
|
||
for (let i = 0; i < keys.length; i++) {
|
||
const name = keys[i];
|
||
const formatterOrDefaultValue = formatArgs[name];
|
||
if (isFunction(formatterOrDefaultValue)) {
|
||
const errMsg = formatterOrDefaultValue(args[0][name], params);
|
||
if (isString(errMsg)) {
|
||
return errMsg;
|
||
}
|
||
}
|
||
else {
|
||
// defaultValue
|
||
if (!hasOwn(params, name)) {
|
||
params[name] = formatterOrDefaultValue;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
function invokeSuccess(id, name, res) {
|
||
const result = {
|
||
errMsg: name + ':ok',
|
||
};
|
||
{
|
||
result.errSubject = name;
|
||
}
|
||
return invokeCallback(id, extend((res || {}), result));
|
||
}
|
||
function invokeFail(id, name, errMsg, errRes = {}) {
|
||
const errMsgPrefix = name + ':fail';
|
||
let apiErrMsg = '';
|
||
if (!errMsg) {
|
||
apiErrMsg = errMsgPrefix;
|
||
}
|
||
else if (errMsg.indexOf(errMsgPrefix) === 0) {
|
||
apiErrMsg = errMsg;
|
||
}
|
||
else {
|
||
apiErrMsg = errMsgPrefix + ' ' + errMsg;
|
||
}
|
||
let res = extend({ errMsg: apiErrMsg }, errRes);
|
||
{
|
||
if (typeof UniError !== 'undefined') {
|
||
res =
|
||
typeof errRes.errCode !== 'undefined'
|
||
? new UniError(name, errRes.errCode, apiErrMsg)
|
||
: new UniError(apiErrMsg, errRes);
|
||
}
|
||
}
|
||
return invokeCallback(id, res);
|
||
}
|
||
function beforeInvokeApi(name, args, protocol, options) {
|
||
if ((process.env.NODE_ENV !== 'production')) {
|
||
validateProtocols(name, args, protocol);
|
||
}
|
||
if (options && options.beforeInvoke) {
|
||
const errMsg = options.beforeInvoke(args);
|
||
if (isString(errMsg)) {
|
||
return errMsg;
|
||
}
|
||
}
|
||
const errMsg = formatApiArgs(args, options);
|
||
if (errMsg) {
|
||
return errMsg;
|
||
}
|
||
}
|
||
function checkCallback(callback) {
|
||
if (!isFunction(callback)) {
|
||
throw new Error('Invalid args: type check failed for args "callback". Expected Function');
|
||
}
|
||
}
|
||
function wrapperOnApi(name, fn, options) {
|
||
return (callback) => {
|
||
checkCallback(callback);
|
||
const errMsg = beforeInvokeApi(name, [callback], undefined, options);
|
||
if (errMsg) {
|
||
throw new Error(errMsg);
|
||
}
|
||
// 是否是首次调用on,如果是首次,需要初始化onMethod监听
|
||
const isFirstInvokeOnApi = !findInvokeCallbackByName(name);
|
||
createKeepAliveApiCallback(name, callback);
|
||
if (isFirstInvokeOnApi) {
|
||
onKeepAliveApiCallback(name);
|
||
fn();
|
||
}
|
||
};
|
||
}
|
||
function wrapperOffApi(name, fn, options) {
|
||
return (callback) => {
|
||
checkCallback(callback);
|
||
const errMsg = beforeInvokeApi(name, [callback], undefined, options);
|
||
if (errMsg) {
|
||
throw new Error(errMsg);
|
||
}
|
||
name = name.replace('off', 'on');
|
||
removeKeepAliveApiCallback(name, callback);
|
||
// 是否还存在监听,若已不存在,则移除onMethod监听
|
||
const hasInvokeOnApi = findInvokeCallbackByName(name);
|
||
if (!hasInvokeOnApi) {
|
||
offKeepAliveApiCallback(name);
|
||
fn();
|
||
}
|
||
};
|
||
}
|
||
function parseErrMsg(errMsg) {
|
||
if (!errMsg || isString(errMsg)) {
|
||
return errMsg;
|
||
}
|
||
if (errMsg.stack) {
|
||
return errMsg.message;
|
||
}
|
||
return errMsg;
|
||
}
|
||
function wrapperTaskApi(name, fn, protocol, options) {
|
||
return (args) => {
|
||
const id = createAsyncApiCallback(name, args, options);
|
||
const errMsg = beforeInvokeApi(name, [args], protocol, options);
|
||
if (errMsg) {
|
||
return invokeFail(id, name, errMsg);
|
||
}
|
||
return fn(args, {
|
||
resolve: (res) => invokeSuccess(id, name, res),
|
||
reject: (errMsg, errRes) => invokeFail(id, name, parseErrMsg(errMsg), errRes),
|
||
});
|
||
};
|
||
}
|
||
function wrapperSyncApi(name, fn, protocol, options) {
|
||
return (...args) => {
|
||
const errMsg = beforeInvokeApi(name, args, protocol, options);
|
||
if (errMsg) {
|
||
throw new Error(errMsg);
|
||
}
|
||
return fn.apply(null, args);
|
||
};
|
||
}
|
||
function wrapperAsyncApi(name, fn, protocol, options) {
|
||
return wrapperTaskApi(name, fn, protocol, options);
|
||
}
|
||
function defineOnApi(name, fn, options) {
|
||
return wrapperOnApi(name, fn, options);
|
||
}
|
||
function defineOffApi(name, fn, options) {
|
||
return wrapperOffApi(name, fn, options);
|
||
}
|
||
function defineTaskApi(name, fn, protocol, options) {
|
||
return promisify(name, wrapperTaskApi(name, fn, (process.env.NODE_ENV !== 'production') ? protocol : undefined, options));
|
||
}
|
||
function defineSyncApi(name, fn, protocol, options) {
|
||
return wrapperSyncApi(name, fn, (process.env.NODE_ENV !== 'production') ? protocol : undefined, options);
|
||
}
|
||
function defineAsyncApi(name, fn, protocol, options) {
|
||
return promisify(name, wrapperAsyncApi(name, fn, (process.env.NODE_ENV !== 'production') ? protocol : undefined, options));
|
||
}
|
||
|
||
const API_ADD_INTERCEPTOR = 'addInterceptor';
|
||
const API_REMOVE_INTERCEPTOR = 'removeInterceptor';
|
||
const AddInterceptorProtocol = [
|
||
{
|
||
name: 'method',
|
||
type: [String, Object],
|
||
required: true,
|
||
},
|
||
];
|
||
const RemoveInterceptorProtocol = AddInterceptorProtocol;
|
||
|
||
function mergeInterceptorHook(interceptors, interceptor) {
|
||
Object.keys(interceptor).forEach((hook) => {
|
||
if (isFunction(interceptor[hook])) {
|
||
interceptors[hook] = mergeHook(interceptors[hook], interceptor[hook]);
|
||
}
|
||
});
|
||
}
|
||
function removeInterceptorHook(interceptors, interceptor) {
|
||
if (!interceptors || !interceptor) {
|
||
return;
|
||
}
|
||
Object.keys(interceptor).forEach((name) => {
|
||
const hooks = interceptors[name];
|
||
const hook = interceptor[name];
|
||
if (isArray(hooks) && isFunction(hook)) {
|
||
remove(hooks, hook);
|
||
}
|
||
});
|
||
}
|
||
function mergeHook(parentVal, childVal) {
|
||
const res = childVal
|
||
? parentVal
|
||
? parentVal.concat(childVal)
|
||
: isArray(childVal)
|
||
? childVal
|
||
: [childVal]
|
||
: parentVal;
|
||
return res ? dedupeHooks(res) : res;
|
||
}
|
||
function dedupeHooks(hooks) {
|
||
const res = [];
|
||
for (let i = 0; i < hooks.length; i++) {
|
||
if (res.indexOf(hooks[i]) === -1) {
|
||
res.push(hooks[i]);
|
||
}
|
||
}
|
||
return res;
|
||
}
|
||
const addInterceptor = defineSyncApi(API_ADD_INTERCEPTOR, (method, interceptor) => {
|
||
if (isString(method) && isPlainObject(interceptor)) {
|
||
mergeInterceptorHook(scopedInterceptors[method] || (scopedInterceptors[method] = {}), interceptor);
|
||
}
|
||
else if (isPlainObject(method)) {
|
||
mergeInterceptorHook(globalInterceptors, method);
|
||
}
|
||
}, AddInterceptorProtocol);
|
||
const removeInterceptor = defineSyncApi(API_REMOVE_INTERCEPTOR, (method, interceptor) => {
|
||
if (isString(method)) {
|
||
if (isPlainObject(interceptor)) {
|
||
removeInterceptorHook(scopedInterceptors[method], interceptor);
|
||
}
|
||
else {
|
||
delete scopedInterceptors[method];
|
||
}
|
||
}
|
||
else if (isPlainObject(method)) {
|
||
removeInterceptorHook(globalInterceptors, method);
|
||
}
|
||
}, RemoveInterceptorProtocol);
|
||
|
||
export { addInterceptor, defineAsyncApi, defineOffApi, defineOnApi, defineSyncApi, defineTaskApi, removeInterceptor };
|