yoyoawb
Version:
awb交互式应用框架
388 lines (347 loc) • 11.8 kB
JavaScript
/**
* 构建交互类
* v 1.0.97
* 作者:Yoyo,星辰
* 介绍:交互式App 依赖库
* 修复:不存在的api解析报错
*/
const __mutual_cache = new Map(); //储存交互api回调
const __mutual_event = new Map(); //储存事件回调
class Mutual {
App;
constructor() {
this.#initAsyncJsPrompt(); //初始化
this.#appInit(); //初始化app的代理
}
isJSON(str) {
try {
JSON.parse(str);
return true;
} catch (e) {
return false;
}
}
/**
* 初始化app的代理
*/
#appInit() {
this.App = new Proxy({}, {
get: (target, key) => new Proxy(target, {
get: (target1, key1) => (args, configJson) => {
args ??= {};
configJson ??= {};
configJson._time ??= 5000; //默认5秒 0就是不限制
configJson._time = Number(configJson._time); //转换类型
if (isNaN(Number(configJson._time))) configJson._time = 5000;
configJson._time = Math.abs(configJson._time); //只要绝对值
const ID = this.#generateUniqueID();
// key相当于类名 key1是方法名
const isSuccessful = this.#invoke(ID, key, key1, args,
configJson);
// 返回是对象
let isAppResult = this.isJSON(isSuccessful);
let AppResultObj = {};
if (isAppResult) {
AppResultObj = JSON.parse(isSuccessful);
if (AppResultObj.status) {
// 当成功 才会解析值
const binaryData = atob(String(AppResultObj
.value)); // 对 Base64 编码字符串进行解码
const utf8DecodedString = new TextDecoder('utf-8')
.decode(new Uint8Array([...binaryData].map(c => c.charCodeAt(
0)))); // 将解码后的二进制数据转换为 UTF-8 编码的字符串
AppResultObj.value = this.isJSON(utf8DecodedString) ? JSON.parse(
utf8DecodedString) : utf8DecodedString;
}
}
// 如果没有传入 success / fail / complete 参数,则会返回封装后的 Promise 对象
if (
typeof args?.success == 'function' ||
typeof args?.fail == 'function' ||
typeof args?.complete == 'function'
) {
const PrObj = {};
const resultObj = new Proxy(PrObj, {
get: (t, k) => {
// t 是空对象 k 是用户调用该对象里面的指定方法
return (args) => {
//用户调用指定方法 (不支持用户,获取指定属性的值)
window.Android?.asyncTaskPrompt(ID, k, JSON
.stringify(args));
}
},
set: (t, p, newValue, rec) => {
return t[p] = newValue; //帮用户指定该对象的插入值以及方法
}
});
// app那边直接返回交互失败
if (!isSuccessful || !isAppResult) {
typeof args.fail == 'function' && args.fail({
id: ID,
message:'Interaction failure !'
});
typeof args.complete == 'function' && args
.complete(); //不管成功还是失败都会调用它
return null;
}
// isSuccessful 是成功的那么判断是不是对象了
if (!AppResultObj.status) {
// app直接返回失败
typeof args.fail == 'function' && args.fail({
id: ID,
message: AppResultObj.value??AppResultObj.message
});
typeof args.complete == 'function' && args
.complete(); //不管成功还是失败都会调用它
return null;
}
//存入方法
const cacheObj = {
base: {},
result: PrObj, //返回事件的注册对象(用户自定义注入事件的),
timeoutId: undefined //超时id
};
//存入回调
if (typeof args?.success == 'function') cacheObj.base.success = args
?.success;
if (typeof args?.fail == 'function') cacheObj.base.fail = args?.fail;
if (typeof args?.complete == 'function') cacheObj.base.complete = args
?.complete;
// 返回的是成功的 (判断是同步还是异步) 调类型1同步 2异步回调
if (AppResultObj.exportType == 1) {
//同步 (直接执行成功)
typeof args.success == 'function' && args.success(AppResultObj
.value);
typeof args.complete == 'function' && args
.complete(); //不管成功还是失败都会调用它
return null;
}
if (configJson._time !== 0) {
cacheObj.timeoutId = setTimeout(() => {
__mutual_cache.delete(ID); //删除缓存
typeof args?.fail == 'function' && args?.fail({
id: ID,
message: 'request timeout !'
});
typeof args?.complete == 'function' && args
?.complete(); //不管成功还是失败都会调用它
}, configJson._time);
}
__mutual_cache.set(ID, cacheObj); //保存自定义函数
return resultObj;
}
return new Promise((resolve, reject) => {
// app那边直接返回交互失败
if (!isSuccessful || !isAppResult) return reject({
id: ID,
message: 'Interaction failure !'
});
if (!AppResultObj.status) return reject({
id: ID,
message: AppResultObj.value??AppResultObj.message
});
// 返回的是成功的 (判断是同步还是异步) 调类型1同步 2异步回调
if (AppResultObj.exportType == 1) {
resolve(AppResultObj.value);
return null;
}
let timeoutId = undefined; //超时id
if (configJson._time !== 0) {
timeoutId = setTimeout(() => {
__mutual_cache.delete(ID); //删除缓存
reject({
id: ID,
message: 'request timeout !'
});
}, configJson._time);
}
__mutual_cache.set(ID, [resolve, reject,
timeoutId
]); //保存resolve函数
});
}
})
});
}
/**
* 调用应用的方法并注册回调
*/
#invoke(ID, className, functionName, data, configJson) {
const isInvokeSucceed = window.Android?.asyncJavaPrompt(ID, JSON.stringify({
...configJson,
// 类名和方法名 不可以通过configJson来修改
class: className,
function: functionName
}), JSON.stringify(data));
return isInvokeSucceed;
}
/**
* 初始化AsyncJsPrompt方便安卓交互
*/
#initAsyncJsPrompt() {
Object.defineProperty(window, 'asyncJsPrompt', {
value: (ID, data, plan) => {
ID ??= '';
data ??= '';
plan ??= 100;
// 先判断ID是否存在
if (!__mutual_cache.has(ID)) return false; //错误的事件ID
const binaryData = atob(data); // 对 Base64 编码字符串进行解码
const utf8DecodedString = new TextDecoder('utf-8')
.decode(new Uint8Array([...binaryData].map(c => c.charCodeAt(
0)))); // 将解码后的二进制数据转换为 UTF-8 编码的字符串
try {
const jsonData = JSON.parse(utf8DecodedString);
//获取事件ID的回调
const eventCallBack = __mutual_cache.get(ID);
if (Array.isArray(eventCallBack)) {
//是 Promise 对象(异步只能一次,所以只处理进度100的时候,其他直接忽略)
if (plan >= 100) {
//调用回调 0是resolve 1是reject
if (jsonData.status) {
eventCallBack[0](jsonData.data);
} else {
eventCallBack[1](jsonData);
}
// 如果有超时任务id,直接去除掉
if (eventCallBack[2]) clearTimeout(eventCallBack[2]);
__mutual_cache.delete(ID); //完成事件回调应释放(异步只执行一次)
}
} else {
// 是普通回调 success / fail / complete
const {
base,
result,
timeoutId
} = eventCallBack;
//事件并未结束
if (jsonData.event) {
const callbackFun = result[jsonData.event];
typeof callbackFun == 'function' && callbackFun(jsonData.data);
}
if (plan >= 100) {
if (jsonData.status) {
//返回是成功的
typeof base.success == 'function' && base.success(jsonData.data);
} else {
//返回是失败的
typeof base.fail == 'function' && base.fail(jsonData);
}
typeof base.complete == 'function' && base.complete(); //不管成功还是失败都会调用它
// 如果有超时任务id,直接去除掉
if (timeoutId) clearTimeout(timeoutId);
// 判断回调是否完成(进度完成事件回调应释放)
__mutual_cache.delete(ID);
}
}
} catch (e) {
//TODO handle the exception
console.error('应用调用数据格式错误:', e.message);
return false; //data解析错误
}
return true;
}
});
}
/**
* 生成唯一 ID (经过测试(同一时间,如果有毫秒间隔(理论上不会出现))间运行,100万次 会出现 1-2次重复)
* @returns {string}
*/
#generateUniqueID() {
const characters = {
str1: 'abcdefghij',
str2: 'klmnopqrst',
str3: 'uvwxyztsrq',
str4: 'jihgfedcba',
str5: 'a1b2c3d4e5',
str6: 'f6g7h8i9j0',
};
let charactersStr = '';
const randtf = Math.floor(Math.random() * 6) + 1;
charactersStr = characters[`str${randtf}`];
let timestamp = new Date().getTime().toString();
timestamp = timestamp.split('').reverse().join('');
const snRand = Math.floor(Math.random() * 9999) + 1000;
const snRand2 = Math.floor(Math.random() * 9999) + 1000;
timestamp += snRand.toString().padStart(4, '0') + snRand2.toString().padStart(4, '0');
let id = '';
for (let i = 0; i < timestamp.length; i++) {
const digit = parseInt(timestamp[i], 10);
let character;
if (digit % 3 === 0) {
character = charactersStr[digit % 10];
} else if (digit % 3 === 1) {
character = digit % 10;
} else {
character = charactersStr[digit % 10].toUpperCase();
}
id += character;
}
return id;
}
}
// app利用 asyncJsEventEmit(事件名,参数)
class EventEmitter {
constructor() {
this.#initAsyncJsEventEmit();
}
/**
* 初始化app交互事件
*/
#initAsyncJsEventEmit() {
Object.defineProperty(window, 'asyncJsEventEmit', {
value: (eventName, data) => {
if (!__mutual_event.has(eventName)) return false; //当前浏览器没有对应的监听(直接丢弃|不接收)
const binaryData = atob(data); // 对 Base64 编码字符串进行解码
const utf8DecodedString = new TextDecoder('utf-8')
.decode(new Uint8Array([...binaryData].map(c => c.charCodeAt(
0)))); // 将解码后的二进制数据转换为 UTF-8 编码的字符串
try {
const jsonData = JSON.parse(utf8DecodedString);
// 循环遍历调用(指定触发事件)
for (const listener of __mutual_event.get(eventName)) {
listener.call(this, jsonData);
}
} catch (e) {
//TODO handle the exception
console.error('应用调用数据格式错误:', e.message);
return false; //data解析错误
}
return true;
}
});
}
on(eventName, listener) {
if (!__mutual_event.has(eventName)) {
__mutual_event.set(eventName, new Set());
}
__mutual_event.get(eventName).add(listener);
}
once(eventName, listener) {
const wrapper = (...args) => {
listener.apply(this, args);
this.off(eventName, wrapper);
};
this.on(eventName, wrapper);
}
off(eventName, listener) {
if (__mutual_event.has(eventName)) {
const listeners = __mutual_event.get(eventName);
const matchingListeners = Array.from(listeners).filter(fn => fn === listener);
for (const matchingListener of matchingListeners) {
listeners.delete(matchingListener);
}
}
}
emit(eventName, ...args) {
if (__mutual_event.has(eventName)) {
for (const listener of __mutual_event.get(eventName)) {
listener.apply(this, args);
}
}
}
}
const mutualClass = new Mutual();
const eventEmitterClass = new EventEmitter();
export const app = mutualClass.App;
export const eventEmitter = eventEmitterClass;