js-bridge-adapter
js-bridge-adapter copied to clipboard
web 端 bridge 适配器,用以对接不同 webview 中任意模式的 jsBridge 交互
JsBridgeAdapter
web
端 bridge
适配器,适用于对接任意 native
端 bridge
方案
基于动态函数制作
安装
yarn add js-bridge-adapter
# or
npm install js-bridge-adapter --save
目的
市面上的 jsBridge
方案大多为 native
端方案,缺乏 web
端对接 native
的方案
此处基于对 jsBridge
调用、注册要求的理解,制作了此适配器
适用于对接任意 native
端 bridge
方案,并具有以下特点
- 交互的支持检测
- 交互的执行定制
- 自定义交互
- 交互的版本控制
示例
假设 native
提供的 bridge
调用方式与 native
侧执行方式为以下等价代码
window.AndroidBridge = {
androidLogin(callback) {
setTimeout(() => {
callback('login success')
})
}
}
使用适配器的对接代码如下
import Bridge from 'js-bridge-adapter'
const bridge = new Bridge('android')
// 模拟 native 环境下 bridge 延时就绪的情况
let isReady = false
// 此处配置 bridge 对象如何对接 native 端,主要为以下两点
// 1、如何做支持检测
// 2、如何执行
bridge.config({
support: key => isReady && key in window.AndroidBridge,
api: key => (...args) => window.AndroidBridge[key](...args)
})
bridge.register({
login: bridge.api('androidLogin')
})
// bridge 未就绪时
bridge.support('login') // false
bridge.call('login', res => console.log(res)) // 调用无反应
// bridge 就绪后
isReady = true
bridge.support('login') // true
bridge.call('login', res => console.log(res)) // log 'login success'
定制交互的执行方式
假设 native
提供的交互如下,由于某种原因,我们期望在 调用前对参数进行处理,或者参数位置对调 等
使用 customize
来定制 bridge
交互以完成上述需求
window.AndroidBridge = {
androidTest(param1, param2) {
console.log(param1, param2)
}
}
...
bridge.register({
test: bridge.api('androidTest'), // 不做处理
test1: bridge.api('androidTest') // 执行前先处理参数
.customize(runner =>
(param1, param2) =>
runner(param1 + 1, param2 + 1)
),
test2: bridge.api('androidTest') // 参数位置对调
.customize(runner =>
(param1, param2) => runner(param2, param1)
),
test3: bridge.api('androidTest') // 可定制多次,注意:由下到上执行
.customize(runner =>
(param1, param2) =>
runner(param1 + 1, param2 + 1)
)
.customize(runner =>
(param1, param2) =>
runner(param1 * 2, param2 * 2)
)
})
bridge.call('test', 1, 2) // log '1, 2'
bridge.call('test1', 1, 2) // log '2, 3'
bridge.call('test2', 1, 2) // log '2, 1'
bridge.call('test3', 1, 2) // log '3, 5' 由下到上执行,所以并不是 '4, 6'
自定义交互
Api
是一个简易优化过后的动态函数,它具有比动态函数更直观的参数名称和声明方式如 isSupported
和 runner
配置项
注意:使用 Api
生成的自定义交互不受 bridge.config.support
的影响
基本使用方式如下
...
import { Api } from 'js-bridge-adapter'
...
const test = new Api((a, b) => a + b)
test.isSupported() // true
test(1, 2) // 3
bridge.register({ test })
bridge.support('test') // true
bridge.call('test', 1, 2) // 3
自定义交互也可以设置 isSupported
支持检测器
...
import { Api } from 'js-bridge-adapter'
...
let isReady = false
const test = new Api({
isSupported: () => isReady,
runner: (a, b) => a + b
})
bridge.register({ test })
test.isSupported() // false
test(1, 2) // 无反应
bridge.support('test') // false
bridge.call('test', 1, 2) // 无反应
isReady = true
test.isSupported() // true
test(1, 2) // 3
bridge.support('test') // true
bridge.call('test', 1, 2) // 3
如果配置时只提供了 isSupported
,而 runner
为非函数,那么 isSupported
执行结果依然会为 false
...
const test = new Api({
isSupported: () => true
})
bridge.register({ test })
test.isSupported() // false
bridge.support('test') // false
调整 Api 的默认配置
...
// 假设 bridge 尚未就绪
let isReady = false
Api.default.isSupported = () => isReady
Api.default.runner = () => console.warn('你可能忘了提供执行体')
const test = new Api()
test.isSupported() // false
isReady = true
test() // warn '你可能忘了提供执行体'
Api
的动态函数模式
使用 getRunner
配置以启用动态函数模式
注意:当使用动态函数模式时,isSupported
和 runner
将不生效
...
let isReady = false
const test = new Api({
getRunner() {
if (!isReady) {
return null
}
return (a, b) => a + b
}
})
test.isSupported() // false
test(1, 2) // 没反应
isReady = true
test.isSupported() // true
test(1, 2) // 3
交互的版本控制
以下使用 compare-versions 来做演示
首先,我们仅控制接口的支持性,不改变接口的行为
...
import compareVerions from 'compare-versions'
import { Api } from 'js-bridge-adapter'
...
// 此处假设原生版本号
let version = '1.0.1'
// 该交互仅在版本 1.2.0 及其以上生效
const test = new Api({
isSupported: () => compareVerions(version, '1.2.0') > -1,
runner: bridge.api('androidTest')
})
test.isSupported() // false
version = '1.2.1'
test.isSupported() // true
接下来,在了解了动态函数、以及 Api
的动态函数模式后,我们可以实现完全的版本控制
window.AndroidBridge = {
androidTest(param1, param2) {
console.log(param1, param2)
}
}
...
let version = '1.0.1'
let isReady = false
bridge.register({
test: new Api({
getRunner() {
// bridge 未就绪时不支持
if (!isReady) {
return null
}
// 当版本大于等于 1.2.0 且小于 1.3.0 时支持交互
if (
compareVerions(version, '1.2.0') > -1 &&
compareVerions(version, '1.3.0') === -1
) {
const test = bridge.api('androidTest')
// 假设 1.2.6 时接口参数被错误反转,需要做行为修正
if (compareVerions(version, '1.2.6') === 0) {
return test.customize(runner => (p1, p2) => runner(p2, p1))
}
return test
}
return null
}
})
})
bridge.support('test') // false,因为 bridge 未就绪
isReady = true
bridge.support('test') // false 因为版本号条件未满足
version = '1.2.0'
bridge.support('test') // true
bridge.call('test', 1, 2) // log '1 2'
version = '1.2.6'
bridge.call('test', 1, 2) // log '2 1
version = '1.3.3'
bridge.support('test') // false 因为版本号条件未满足
异步检测
当 bridge.config.support
为异步函数时,bridge.support/call
方法均为异步
...
const delay = time => new Promise(resolve => setTimeout(resolve, time))
const TestBridge = {
test() {
return 'Test Log'
}
}
const bridge = new Bridge()
bridge.config({
support: async api => {
await delay(1000)
return api in TestBridge
},
api: key => (...args) => TestBridge[key](...args)
})
const test = bridge.api('test')
bridge.register({ test })
await bridge.support('test') // log 'true' after 1000ms
await test.isSupported() // log 'true' after 1000ms
await bridge.call('test') // log 'Test Log' after 1000ms
await test() // log 'Test Log' after 1000ms
同时,Api
与 DynamicFunction
函数享有类似的异步功能
...
const delay = time => new Promise(resolve => setTimeout(resolve, time))
const test = new Api({
getRunner: async () => {
await delay(1000)
return (a, b) => a + b
}
})
await test.isSupported() // log 'true' after 1000ms
await test(1, 2) // log '3' after 1000ms
其他功能
bridge.has(apiName)
检测 bridge
中交互是否已注册
...
bridge.register({
test: bridge.api('androidTest')
})
bridge.has('test') // true
bridge.has('test2') // false
...
bridge.get(apiName)
获取 bridge
中已注册交互的执行体
...
bridge.register({
test: new Api((a, b) => a + b)
})
const test = bridge.get('test')
test.isSupported() // true
test(1, 2) // 3
uniqueId([prefix = ''])
生成一个随机的、唯一的 id 值
import { uniqueId } from 'js-bridge-adapter'
生成规则如下
let uuid = 0
const uniqueId = (prefix = '') =>
`${prefix}_${++uuid}_${Date.now()}_${Math.floor(Math.random() * 1000000)}`
globalize(localFunc[, config])
将局部函数转为全局函数,执行后得到生成的全局函数名,默认全局函数名通过 uniqueId()
生成
import { globalize } from 'js-bridge-adapter'
let localFunc = (a, b) => a + b
// 转换为全局函数后默认执行后不会自动销毁
let globalFuncName = globalize(localFunc)
window[globalFuncName](1, 2) // 3
globalFuncName in window // true
// 自定义转为全局函数后的函数名
let customizedGlobalFuncName = globalize(localFunc, {
name: 'test'
})
customizedGlobalFuncName === 'test' // true
window.test(1, 2) // 3
// 声明全局函数仅执行一次后自动销毁
let globalFuncName2 = globalize(localFunc, {
once: true
})
window[globalFuncName2](1, 2) // 3
globalFuncName2 in window // false