t-comm
Version:
专业、稳定、纯粹的工具库
403 lines (398 loc) • 14.1 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
var bluetoothBump_utils = require('./utils.js');
require('@babel/runtime/helpers/slicedToArray');
require('@babel/runtime/helpers/typeof');
require('./constants.js');
/**
* 微信蓝牙 API 薄封装
*
* 把所有对 wx.* 的直接调用集中在这一层,外部只看到 Promise / 回调注册接口。
* 这样:
* 1. 主流程类 BluetoothBump 不直接依赖 wx,可注入 mock adapter 做单测
* 2. 这里的实现也能单独被测:mock 一个 (global as any).wx 就够了
*/
// -------------------- 内部小工具 --------------------
function openAdapterWithMode(mode) {
return new Promise(function (resolve, reject) {
var params = {
success: function success() {
return resolve();
},
fail: function fail(err) {
return reject(err);
}
};
if (mode) params.mode = mode;
wx.openBluetoothAdapter(params);
});
}
// -------------------- 默认实现 --------------------
var wxBluetoothAdapter = {
isAvailable: function isAvailable() {
return typeof wx !== 'undefined' && !!wx && typeof wx.openBluetoothAdapter === 'function';
},
detectPlatform: function detectPlatform() {
if (typeof wx === 'undefined' || !wx.getSystemInfoSync) return 'unknown';
try {
var info = wx.getSystemInfoSync();
return (info.platform || 'unknown').toLowerCase();
} catch (e) {
return 'unknown';
}
},
ensurePrivacyAuthorized: function ensurePrivacyAuthorized(privacyContent, log) {
return new Promise(function (resolve) {
if (typeof wx === 'undefined' || !wx) {
resolve();
return;
}
if (typeof wx.onNeedPrivacyAuthorization !== 'function') {
resolve();
return;
}
var resolved = false;
var safeResolve = function safeResolve() {
if (resolved) return;
resolved = true;
resolve();
};
wx.onNeedPrivacyAuthorization(function (resolveAuth) {
log('微信请求隐私授权,弹出协议确认');
wx.showModal({
title: '隐私保护指引',
content: privacyContent,
confirmText: '同意',
cancelText: '拒绝',
success: function success(modalRes) {
if (modalRes.confirm) {
log('✅ 用户同意隐私协议');
resolveAuth({
event: 'agree',
buttonId: 'agree-btn'
});
} else {
log('⚠️ 用户拒绝隐私协议');
resolveAuth({
event: 'disagree'
});
}
},
fail: function fail() {
return resolveAuth({
event: 'disagree'
});
}
});
});
if (typeof wx.requirePrivacyAuthorize === 'function') {
wx.requirePrivacyAuthorize({
success: function success() {
return safeResolve();
},
fail: function fail() {
return safeResolve();
}
});
} else {
safeResolve();
}
});
},
openAdapter: function openAdapter(platform, log) {
if (platform === 'ios') {
return openAdapterWithMode('central').then(function () {
log('✅ Central 模式适配器已打开');
return openAdapterWithMode('peripheral').then(function () {
log('✅ Peripheral 模式适配器已打开');
return {
skipAdvertising: false
};
})["catch"](function (err) {
log("\u26A0\uFE0F Peripheral \u6A21\u5F0F\u521D\u59CB\u5316\u5931\u8D25\uFF0C\u964D\u7EA7\u4E3A\u53EA\u626B\u63CF\uFF1A".concat((err === null || err === void 0 ? void 0 : err.errMsg) ? err.errMsg : err));
return {
skipAdvertising: true
};
});
});
}
return openAdapterWithMode().then(function () {
return {
skipAdvertising: false
};
});
},
startAdvertising: function startAdvertising(params) {
return new Promise(function (resolve, reject) {
wx.createBLEPeripheralServer({
success: function success(res) {
var server = res === null || res === void 0 ? void 0 : res.server;
if (!server || typeof server.addService !== 'function') {
reject(new Error('createBLEPeripheralServer 返回的 server 无效(不支持外围模式)'));
return;
}
var valueStr = params.payload || params.tempId;
var valueBuf = bluetoothBump_utils.str2ArrayBuffer(valueStr);
// 从 payload 里拆出 myTempId / seenPeerId,用来生成设备名
// 设备名是 iOS↔iOS 唯一可靠的广播通道(CoreBluetooth 不允许发 ServiceData)
var parsed = bluetoothBump_utils.parsePayloadString(valueStr);
var myTempId = (parsed === null || parsed === void 0 ? void 0 : parsed.myTempId) || params.tempId;
var seenPeerId = (parsed === null || parsed === void 0 ? void 0 : parsed.seenPeerId) || '';
var bumpName = bluetoothBump_utils.encodeBumpName(myTempId, seenPeerId);
server.addService({
service: {
uuid: params.serviceUuid,
characteristics: [{
uuid: params.characteristicUuid,
properties: {
read: true,
write: true,
notify: false,
indicate: false
},
permission: {
readable: true,
writeable: true
},
value: valueBuf
}]
},
success: function success() {
// ⚠️ 关键:iOS 不能在广播包里发 ServiceData,但能发 deviceName + serviceUuids。
// 所以把 payload 同时写进:
// a) deviceName: BUMP_<myTempId>_<seenPeerId>——iOS↔iOS 唯一可靠通道
// b) serviceData(数组/对象两种格式)——Android 能读到更多位
// 三重带 fallback,谁能走通谁走
var _tryAdvertise = function tryAdvertise(advertiseRequest, fallbacks) {
server.startAdvertising({
advertiseRequest: advertiseRequest,
success: function success() {
return resolve({
server: server
});
},
fail: function fail(err) {
if (fallbacks.length) {
var next = fallbacks.shift();
_tryAdvertise(next, fallbacks);
} else {
reject(err);
}
}
});
};
var serviceDataObj = {};
serviceDataObj[params.serviceUuid] = valueBuf;
_tryAdvertise({
// 首选:名字 + serviceUuids + serviceData(数组)
deviceName: bumpName,
serviceUuids: [params.serviceUuid],
serviceData: [{
uuid: params.serviceUuid,
data: valueBuf
}]
}, [
// 备选 1:名字 + serviceUuids + serviceData(对象格式)
{
deviceName: bumpName,
serviceUuids: [params.serviceUuid],
serviceData: serviceDataObj
},
// 备选 2:仅名字 + serviceUuids(iOS 上实际走这里,payload 靠名字传)
{
deviceName: bumpName,
serviceUuids: [params.serviceUuid]
},
// 备选 3:裸跑(极端降级,对端可能拿不到 payload)
{
serviceUuids: [params.serviceUuid]
}]);
},
fail: function fail(err) {
return reject(err);
}
});
},
fail: function fail(err) {
return reject(err);
}
});
});
},
updateAdvertisedValue: function updateAdvertisedValue(server, params) {
return new Promise(function (resolve, reject) {
if (!server || typeof server.addService !== 'function') {
reject(new Error('server 无效,无法更新广播 value'));
return;
}
var valueStr = params.payload || params.tempId;
var valueBuf = bluetoothBump_utils.str2ArrayBuffer(valueStr);
var parsed = bluetoothBump_utils.parsePayloadString(valueStr);
var myTempId = (parsed === null || parsed === void 0 ? void 0 : parsed.myTempId) || params.tempId;
var seenPeerId = (parsed === null || parsed === void 0 ? void 0 : parsed.seenPeerId) || '';
var bumpName = bluetoothBump_utils.encodeBumpName(myTempId, seenPeerId);
var doAddAndStart = function doAddAndStart() {
server.addService({
service: {
uuid: params.serviceUuid,
characteristics: [{
uuid: params.characteristicUuid,
properties: {
read: true,
write: true,
notify: false,
indicate: false
},
permission: {
readable: true,
writeable: true
},
value: valueBuf
}]
},
success: function success() {
// 同 startAdvertising:名字 + serviceData 三重 fallback
var _tryAdvertise2 = function tryAdvertise(advertiseRequest, fallbacks) {
server.startAdvertising({
advertiseRequest: advertiseRequest,
success: function success() {
return resolve();
},
fail: function fail(err) {
if (fallbacks.length) {
var next = fallbacks.shift();
_tryAdvertise2(next, fallbacks);
} else {
reject(err);
}
}
});
};
var serviceDataObj = {};
serviceDataObj[params.serviceUuid] = valueBuf;
_tryAdvertise2({
deviceName: bumpName,
serviceUuids: [params.serviceUuid],
serviceData: [{
uuid: params.serviceUuid,
data: valueBuf
}]
}, [{
deviceName: bumpName,
serviceUuids: [params.serviceUuid],
serviceData: serviceDataObj
}, {
deviceName: bumpName,
serviceUuids: [params.serviceUuid]
}, {
serviceUuids: [params.serviceUuid]
}]);
},
fail: function fail(err) {
return reject(err);
}
});
};
var afterStop = function afterStop() {
if (typeof server.removeService === 'function') {
server.removeService({
serviceId: params.serviceUuid,
success: function success() {
return doAddAndStart();
},
fail: function fail() {
return doAddAndStart();
} // removeService 失败也继续,addService 重复注册会报错再在上层回退
});
} else {
doAddAndStart();
}
};
if (typeof server.stopAdvertising === 'function') {
server.stopAdvertising({
success: function success() {
return afterStop();
},
fail: function fail() {
return afterStop();
} // 忽略停止失败,继续尝试更新
});
} else {
afterStop();
}
});
},
startDiscovery: function startDiscovery(params) {
return new Promise(function (resolve, reject) {
var wxParams = {
allowDuplicatesKey: true,
interval: 0,
success: function success() {
return resolve();
},
fail: function fail(err) {
return reject(err);
}
};
// ⚠️ iOS 不传 services 过滤,否则系统重新包装广播包后会扫不到
if (params.platform !== 'ios') {
wxParams.services = [params.serviceUuid];
}
wx.startBluetoothDevicesDiscovery(wxParams);
});
},
onDeviceFound: function onDeviceFound(cb) {
wx.onBluetoothDeviceFound(cb);
},
offDeviceFound: function offDeviceFound(cb) {
if (wx && typeof wx.offBluetoothDeviceFound === 'function') {
wx.offBluetoothDeviceFound(cb);
}
},
onAdapterStateChange: function onAdapterStateChange(cb) {
wx.onBluetoothAdapterStateChange(cb);
},
offAdapterStateChange: function offAdapterStateChange(cb) {
if (wx && typeof wx.offBluetoothAdapterStateChange === 'function') {
wx.offBluetoothAdapterStateChange(cb);
}
},
getDevices: function getDevices() {
return new Promise(function (resolve) {
if (!wx || typeof wx.getBluetoothDevices !== 'function') {
resolve({
devices: []
});
return;
}
wx.getBluetoothDevices({
success: function success(res) {
return resolve({
devices: (res === null || res === void 0 ? void 0 : res.devices) || []
});
},
fail: function fail() {
return resolve({
devices: []
});
}
});
});
},
stopDiscovery: function stopDiscovery() {
if (wx && typeof wx.stopBluetoothDevicesDiscovery === 'function') {
wx.stopBluetoothDevicesDiscovery({});
}
},
stopAdvertising: function stopAdvertising(server) {
if (server && typeof server.stopAdvertising === 'function') {
server.stopAdvertising({});
}
},
closeAdapter: function closeAdapter() {
if (wx && typeof wx.closeBluetoothAdapter === 'function') {
wx.closeBluetoothAdapter({});
}
}
};
exports.wxBluetoothAdapter = wxBluetoothAdapter;