提交 e5fd303c 编写于 作者: fxy060608's avatar fxy060608

wip(uts): runtime

上级 cdefd570
import {
normalizeArg,
initUtsProxyFunction,
initUtsProxyClass,
} from '../src/service/api/plugin/uts'
describe('uts-module', () => {
test('normalize args', () => {
expect(normalizeArg(1)).toBe(1)
expect(normalizeArg('hello')).toBe('hello')
expect(normalizeArg(true)).toBe(true)
expect(normalizeArg({ callback: () => {} })).toEqual({
callback: 1,
})
expect(
normalizeArg({ success: () => {}, fail: () => {}, complete: () => {} })
).toEqual({
success: 2,
fail: 3,
complete: 4,
})
expect(
normalizeArg({
user: {
name: 'test',
age: 10,
callback() {},
},
success() {},
})
).toEqual({
user: {
name: 'test',
age: 10,
callback: 5,
},
success: 6,
})
})
test(`initProxyFunction`, () => {
;[true, false].forEach((async) => {
const preparePermission = initUtsProxyFunction(async, {
package: 'uts.modules.TestPlugin',
class: 'TestKt',
name: 'preparePermission',
params: [
{ name: 'options', type: 'PermissionOptions' },
{ name: 'callback', type: 'UTSCallback' },
],
})
/**
* {"package":"testPlugin","class":"","method":"preparePermission","params":[{"name":"foo","age":10,"success":7,"fail":8},9]}
*/
const options = {
family: {
father: 'f',
mother: 'm',
},
name: 'foo',
age: 10,
success(res: any) {
console.log('success', res)
},
fail(res: any) {
console.log('fail', res)
},
}
const callback = (res: any) => {
console.log('callback', res)
}
preparePermission(options, callback)
preparePermission(options, callback)
const errMsg = 'xx插件编译失败,无法使用'
expect(
initUtsProxyFunction(async, {
errMsg,
} as any)
).toThrowError(errMsg)
})
})
test(`initProxyClass`, () => {
const WifiManager = initUtsProxyClass({
package: 'uni.modules.TestPlugin',
class: 'WifiManager',
constructor: {
params: [],
},
methods: {
preparePermission: {
params: [
{ name: 'options', type: 'PermissionOptions' },
{ name: 'callback', type: 'UTSCallback' },
],
},
},
staticMethods: {
staticPreparePermission: {
async: true,
params: [{ name: 'num', type: 'number' }],
},
},
props: ['count'],
staticProps: ['staticCount'],
})
const wifi = new WifiManager()
wifi.preparePermission(1, 2, 3, () => {})
wifi.count
WifiManager.staticCount
WifiManager.staticPreparePermission(1)
const errMsg = 'xx插件编译失败,无法使用'
const WifiManagerError = initUtsProxyClass({
errMsg,
staticMethods: {
staticPreparePermission: {
params: [],
},
},
staticProps: ['staticCount'],
} as any)
expect(() => {
new WifiManagerError()
}).toThrowError(errMsg)
expect(WifiManagerError.staticPreparePermission).toThrowError(errMsg)
expect(() => {
WifiManagerError.staticCount
}).toThrowError(errMsg)
})
})
......@@ -1669,17 +1669,17 @@ function getPageId() {
}
let channel;
let globalEvent$1;
const callbacks$2 = {};
const callbacks$3 = {};
function onPlusMessage$1(res) {
const message = res.data && res.data.__message;
if (!message || !message.__page) {
return;
}
const pageId = message.__page;
const callback = callbacks$2[pageId];
const callback = callbacks$3[pageId];
callback && callback(message);
if (!message.keep) {
delete callbacks$2[pageId];
delete callbacks$3[pageId];
}
}
function addEventListener(pageId, callback) {
......@@ -1698,7 +1698,7 @@ function addEventListener(pageId, callback) {
// @ts-ignore
window.__plusMessage = onPlusMessage$1;
}
callbacks$2[pageId] = callback;
callbacks$3[pageId] = callback;
}
class Page {
constructor(webview) {
......@@ -14576,7 +14576,7 @@ const Recorder = {
}
},
};
const callbacks$1 = {
const callbacks$2 = {
pause: null,
resume: null,
start: null,
......@@ -14587,29 +14587,29 @@ function onRecorderStateChange(res) {
const state = res.state;
delete res.state;
delete res.errMsg;
if (state && isFunction(callbacks$1[state])) {
callbacks$1[state](res);
if (state && isFunction(callbacks$2[state])) {
callbacks$2[state](res);
}
}
class RecorderManager {
constructor() { }
onError(callback) {
callbacks$1.error = callback;
callbacks$2.error = callback;
}
onFrameRecorded(callback) { }
onInterruptionBegin(callback) { }
onInterruptionEnd(callback) { }
onPause(callback) {
callbacks$1.pause = callback;
callbacks$2.pause = callback;
}
onResume(callback) {
callbacks$1.resume = callback;
callbacks$2.resume = callback;
}
onStart(callback) {
callbacks$1.start = callback;
callbacks$2.start = callback;
}
onStop(callback) {
callbacks$1.stop = callback;
callbacks$2.stop = callback;
}
pause() {
Recorder.pause();
......@@ -15712,7 +15712,7 @@ const eventNames = [
'error',
'waiting',
];
const callbacks = {
const callbacks$1 = {
canplay: [],
play: [],
pause: [],
......@@ -15900,7 +15900,7 @@ function operateBackgroundAudio({ operationType, src, startTime, currentTime, })
});
}
function onBackgroundAudioStateChange({ state, errMsg, errCode, dataUrl, }) {
callbacks[state].forEach((callback) => {
callbacks$1[state].forEach((callback) => {
if (isFunction(callback)) {
callback(state === 'error'
? {
......@@ -15915,7 +15915,7 @@ const onInitBackgroundAudioManager = /*#__PURE__*/ once(() => {
eventNames.forEach((item) => {
BackgroundAudioManager.prototype[`on${capitalize(item)}`] =
function (callback) {
callbacks[item].push(callback);
callbacks$1[item].push(callback);
};
});
});
......@@ -17314,6 +17314,193 @@ function normalizeLog(type, filename, args) {
return msgs.join('---COMMA---') + ' ' + filename;
}
let callbackId = 1;
let proxy;
const callbacks = {};
function normalizeArg(arg) {
if (typeof arg === 'function') {
// 查找该函数是否已缓存
const oldId = Object.keys(callbacks).find((id) => callbacks[id] === arg);
const id = oldId ? parseInt(oldId) : callbackId++;
callbacks[id] = arg;
return id;
}
else if (isPlainObject(arg)) {
Object.keys(arg).forEach((name) => {
arg[name] = normalizeArg(arg[name]);
});
}
return arg;
}
function initUtsInstanceMethod(async, opts, instanceId) {
return initProxyFunction(async, opts, instanceId);
}
function getProxy() {
if (!proxy) {
proxy = uni.requireNativePlugin('UTS-Proxy');
}
return proxy;
}
function resolveSyncResult(res) {
if (res.errMsg) {
throw new Error(res.errMsg);
}
return res.params;
}
function invokePropGetter(args) {
if (args.errMsg) {
throw new Error(args.errMsg);
}
delete args.errMsg;
return resolveSyncResult(getProxy().invokeSync(args, () => { }));
}
function initProxyFunction(async, { package: pkg, class: cls, name: propOrMethod, method, companion, params: methodParams, errMsg, }, instanceId) {
const invokeCallback = ({ id, name, params, keepAlive, }) => {
const callback = callbacks[id];
if (callback) {
callback(...params);
if (!keepAlive) {
delete callbacks[id];
}
}
else {
console.error(`${pkg}${cls}.${propOrMethod} ${name} is not found`);
}
};
const baseArgs = instanceId
? { id: instanceId, name: propOrMethod, method: methodParams }
: {
package: pkg,
class: cls,
name: method || propOrMethod,
companion,
method: methodParams,
};
return (...args) => {
if (errMsg) {
throw new Error(errMsg);
}
const invokeArgs = extend({}, baseArgs, {
params: args.map((arg) => normalizeArg(arg)),
});
if (async) {
return new Promise((resolve, reject) => {
getProxy().invokeAsync(invokeArgs, (res) => {
if (res.type !== 'return') {
invokeCallback(res);
}
else {
if (res.errMsg) {
reject(res.errMsg);
}
else {
resolve(res.params);
}
}
});
});
}
return resolveSyncResult(getProxy().invokeSync(invokeArgs, invokeCallback));
};
}
function initUtsStaticMethod(async, opts) {
if (opts.main && !opts.method) {
if (typeof plus !== 'undefined' && plus.os.name === 'iOS') {
opts.method = 's_' + opts.name;
}
}
return initProxyFunction(async, opts, 0);
}
const initUtsProxyFunction = initUtsStaticMethod;
function initUtsProxyClass({ package: pkg, class: cls, constructor: { params: constructorParams }, methods, props, staticProps, staticMethods, errMsg, }) {
const baseOptions = {
package: pkg,
class: cls,
errMsg,
};
const ProxyClass = class UtsClass {
constructor(...params) {
if (errMsg) {
throw new Error(errMsg);
}
const target = {};
// 初始化实例 ID
const instanceId = initProxyFunction(false, extend({ name: 'constructor', params: constructorParams }, baseOptions), 0).apply(null, params);
if (!instanceId) {
throw new Error(`new ${cls} is failed`);
}
return new Proxy(this, {
get(_, name) {
if (!target[name]) {
//实例方法
if (hasOwn$1(methods, name)) {
const { async, params } = methods[name];
target[name] = initUtsInstanceMethod(!!async, extend({
name,
params,
}, baseOptions), instanceId);
}
else if (props.includes(name)) {
// 实例属性
return invokePropGetter({
id: instanceId,
name: name,
errMsg,
});
}
}
return target[name];
},
});
}
};
const staticMethodCache = {};
return new Proxy(ProxyClass, {
get(target, name, receiver) {
if (hasOwn$1(staticMethods, name)) {
if (!staticMethodCache[name]) {
const { async, params } = staticMethods[name];
// 静态方法
staticMethodCache[name] = initUtsStaticMethod(!!async, extend({ name, companion: true, params }, baseOptions));
}
return staticMethodCache[name];
}
if (staticProps.includes(name)) {
// 静态属性
return invokePropGetter(extend({ name: name, companion: true }, baseOptions));
}
return Reflect.get(target, name, receiver);
},
});
}
function initUtsPackageName(name, is_uni_modules) {
if (typeof plus !== 'undefined' && plus.os.name === 'Android') {
return 'uts.sdk.' + (is_uni_modules ? 'modules.' : '') + name;
}
return '';
}
function initUtsIndexClassName(moduleName, is_uni_modules) {
if (typeof plus === 'undefined') {
return '';
}
return initUtsClassName(moduleName, plus.os.name === 'iOS' ? 'IndexSwift' : 'IndexKt', is_uni_modules);
}
function initUtsClassName(moduleName, className, is_uni_modules) {
if (typeof plus === 'undefined') {
return '';
}
if (plus.os.name === 'Android') {
return className;
}
if (plus.os.name === 'iOS') {
return ('UTSSDK' +
(is_uni_modules ? 'Modules' : '') +
capitalize(moduleName) +
capitalize(className));
}
return '';
}
const EventType = {
load: 'load',
close: 'close',
......@@ -19398,6 +19585,11 @@ var uni$1 = {
onHostEventReceive: onHostEventReceive,
onNativeEventReceive: onNativeEventReceive,
__log__: __log__,
initUtsProxyClass: initUtsProxyClass,
initUtsProxyFunction: initUtsProxyFunction,
initUtsIndexClassName: initUtsIndexClassName,
initUtsClassName: initUtsClassName,
initUtsPackageName: initUtsPackageName,
navigateTo: navigateTo,
reLaunch: reLaunch,
switchTab: switchTab,
......
......@@ -81,7 +81,16 @@ export {
onHostEventReceive,
onNativeEventReceive,
} from './plugin/sdk'
// 内部使用
export { __log__ } from './plugin/log'
// 内部使用
export {
initUtsProxyClass,
initUtsProxyFunction,
initUtsIndexClassName,
initUtsClassName,
initUtsPackageName,
} from './plugin/uts'
export * from './ad/rewardedVideoAd'
export * from './ad/fullScreenVideoAd'
......
import { isPlainObject, hasOwn, extend, capitalize } from '@vue/shared'
declare const uni: any
declare const plus: any
let callbackId = 1
let proxy: any
const callbacks: Record<string, Function> = {}
export function normalizeArg(arg: unknown) {
if (typeof arg === 'function') {
// 查找该函数是否已缓存
const oldId = Object.keys(callbacks).find((id) => callbacks[id] === arg)
const id = oldId ? parseInt(oldId) : callbackId++
callbacks[id] = arg
return id
} else if (isPlainObject(arg)) {
Object.keys(arg).forEach((name) => {
;(arg as any)[name] = normalizeArg((arg as any)[name])
})
}
return arg
}
function initUtsInstanceMethod(
async: boolean,
opts: ProxyFunctionOptions,
instanceId: number
) {
return initProxyFunction(async, opts, instanceId)
}
interface Parameter {
name: string
type: string
}
interface ProxyFunctionOptions {
/**
* 是否是入口类
*/
main?: boolean
/**
* 包名
*/
package: string
/**
* 类名
*/
class: string
/**
* 属性名或方法名
*/
name: string
/**
* 方法名 指定的方法名(用于 IndexSwift 静态方法,自动补充前缀 s_)
*/
method?: string
/**
* 是否伴生对象
*/
companion?: boolean
/**
* 方法参数列表
*/
params: Parameter[]
/**
* 运行时提示的错误信息
*/
errMsg?: string
}
interface ProxyClassOptions {
package: string
class: string
constructor: {
params: Parameter[]
}
props: string[]
staticProps: string[]
methods: {
[name: string]: {
async?: boolean
params: Parameter[]
}
}
staticMethods: {
[name: string]: {
async?: boolean
params: Parameter[]
}
}
/**
* 运行时提示的错误信息
*/
errMsg?: string
}
interface InvokeInstanceArgs {
id: number
name: string
params?: unknown[]
method?: Parameter[]
/**
* 运行时提示的错误信息
*/
errMsg?: string
}
interface InvokeStaticArgs {
/**
* 包名
*/
package: string
/**
* 类名
*/
class: string
/**
* 属性名或方法名
*/
name: string
/**
* 执行方法时的真实参数列表
*/
params?: unknown[]
/**
* 方法定义的参数列表
*/
method?: Parameter[]
/**
* 是否是伴生对象
*/
companion?: boolean
/**
* 运行时提示的错误信息
*/
errMsg?: string
}
type InvokeArgs = InvokeInstanceArgs | InvokeStaticArgs
interface InvokeCallbackReturnRes {
type: 'return'
params?: unknown[]
errMsg?: string
errStackTrace?: string
}
interface InvokeCallbackParamsRes {
type: 'params'
id: number
name: string
params: unknown[]
keepAlive?: boolean
}
interface InvokeSyncRes {
type: 'return'
errMsg?: string
errStackTrace?: string
params: unknown
}
type InvokeSyncCallback = (res: InvokeCallbackParamsRes) => void
type InvokeAsyncCallback = (
res: InvokeCallbackReturnRes | InvokeCallbackParamsRes
) => void
function getProxy(): {
invokeSync: (args: InvokeArgs, callback: InvokeSyncCallback) => InvokeSyncRes
invokeAsync: (args: InvokeArgs, callback: InvokeAsyncCallback) => void
} {
if (!proxy) {
proxy = uni.requireNativePlugin('UTS-Proxy') as any
}
return proxy
}
function resolveSyncResult(res: InvokeSyncRes) {
if (res.errMsg) {
throw new Error(res.errMsg)
}
return res.params
}
function invokePropGetter(args: InvokeArgs) {
if (args.errMsg) {
throw new Error(args.errMsg)
}
delete args.errMsg
return resolveSyncResult(getProxy().invokeSync(args, () => {}))
}
function initProxyFunction(
async: boolean,
{
package: pkg,
class: cls,
name: propOrMethod,
method,
companion,
params: methodParams,
errMsg,
}: ProxyFunctionOptions,
instanceId: number
) {
const invokeCallback = ({
id,
name,
params,
keepAlive,
}: InvokeCallbackParamsRes) => {
const callback = callbacks[id!]
if (callback) {
callback(...params)
if (!keepAlive) {
delete callbacks[id]
}
} else {
console.error(`${pkg}${cls}.${propOrMethod} ${name} is not found`)
}
}
const baseArgs: InvokeArgs = instanceId
? { id: instanceId, name: propOrMethod, method: methodParams }
: {
package: pkg,
class: cls,
name: method || propOrMethod,
companion,
method: methodParams,
}
return (...args: unknown[]) => {
if (errMsg) {
throw new Error(errMsg)
}
const invokeArgs = extend({}, baseArgs, {
params: args.map((arg) => normalizeArg(arg)),
})
if (async) {
return new Promise((resolve, reject) => {
getProxy().invokeAsync(invokeArgs, (res) => {
if (res.type !== 'return') {
invokeCallback(res)
} else {
if (res.errMsg) {
reject(res.errMsg)
} else {
resolve(res.params)
}
}
})
})
}
return resolveSyncResult(getProxy().invokeSync(invokeArgs, invokeCallback))
}
}
function initUtsStaticMethod(async: boolean, opts: ProxyFunctionOptions) {
if (opts.main && !opts.method) {
if (typeof plus !== 'undefined' && plus.os.name === 'iOS') {
opts.method = 's_' + opts.name
}
}
return initProxyFunction(async, opts, 0)
}
export const initUtsProxyFunction = initUtsStaticMethod
export function initUtsProxyClass({
package: pkg,
class: cls,
constructor: { params: constructorParams },
methods,
props,
staticProps,
staticMethods,
errMsg,
}: ProxyClassOptions): any {
const baseOptions = {
package: pkg,
class: cls,
errMsg,
}
const ProxyClass = class UtsClass {
constructor(...params: unknown[]) {
if (errMsg) {
throw new Error(errMsg)
}
const target: Record<string, Function> = {}
// 初始化实例 ID
const instanceId = initProxyFunction(
false,
extend({ name: 'constructor', params: constructorParams }, baseOptions),
0
).apply(null, params) as number
if (!instanceId) {
throw new Error(`new ${cls} is failed`)
}
return new Proxy(this, {
get(_, name) {
if (!target[name as string]) {
//实例方法
if (hasOwn(methods, name)) {
const { async, params } = methods[name]
target[name] = initUtsInstanceMethod(
!!async,
extend(
{
name,
params,
},
baseOptions
),
instanceId
)
} else if (props.includes(name as string)) {
// 实例属性
return invokePropGetter({
id: instanceId,
name: name as string,
errMsg,
})
}
}
return target[name as string]
},
})
}
}
const staticMethodCache: Record<string, Function> = {}
return new Proxy(ProxyClass, {
get(target, name, receiver) {
if (hasOwn(staticMethods, name)) {
if (!staticMethodCache[name as string]) {
const { async, params } = staticMethods[name]
// 静态方法
staticMethodCache[name] = initUtsStaticMethod(
!!async,
extend({ name, companion: true, params }, baseOptions)
)
}
return staticMethodCache[name]
}
if (staticProps.includes(name as string)) {
// 静态属性
return invokePropGetter(
extend({ name: name as string, companion: true }, baseOptions)
)
}
return Reflect.get(target, name, receiver)
},
})
}
export function initUtsPackageName(name: string, is_uni_modules: boolean) {
if (typeof plus !== 'undefined' && plus.os.name === 'Android') {
return 'uts.sdk.' + (is_uni_modules ? 'modules.' : '') + name
}
return ''
}
export function initUtsIndexClassName(
moduleName: string,
is_uni_modules: boolean
) {
if (typeof plus === 'undefined') {
return ''
}
return initUtsClassName(
moduleName,
plus.os.name === 'iOS' ? 'IndexSwift' : 'IndexKt',
is_uni_modules
)
}
export function initUtsClassName(
moduleName: string,
className: string,
is_uni_modules: boolean
) {
if (typeof plus === 'undefined') {
return ''
}
if (plus.os.name === 'Android') {
return className
}
if (plus.os.name === 'iOS') {
return (
'UTSSDK' +
(is_uni_modules ? 'Modules' : '') +
capitalize(moduleName) +
capitalize(className)
)
}
return ''
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册