UNPKG

yoyoawb

Version:

awb交互式应用框架

388 lines (347 loc) 11.8 kB
/** * 构建交互类 * 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;