UNPKG

t-comm

Version:

专业、稳定、纯粹的工具库

403 lines (398 loc) 14.1 kB
'use strict'; 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;