UNPKG

t-comm

Version:

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

617 lines (610 loc) 23.6 kB
'use strict'; var _slicedToArray = require('@babel/runtime/helpers/slicedToArray'); var _classCallCheck = require('@babel/runtime/helpers/classCallCheck'); var _createClass = require('@babel/runtime/helpers/createClass'); var _regeneratorRuntime = require('@babel/runtime/regenerator'); var tslib_es6 = require('./tslib.es6-0d92ef81.js'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var _slicedToArray__default = /*#__PURE__*/_interopDefaultLegacy(_slicedToArray); var _classCallCheck__default = /*#__PURE__*/_interopDefaultLegacy(_classCallCheck); var _createClass__default = /*#__PURE__*/_interopDefaultLegacy(_createClass); var _regeneratorRuntime__default = /*#__PURE__*/_interopDefaultLegacy(_regeneratorRuntime); /** * bump-service —— 碰一碰业务编排层类型定义 * * 与 bluetooth-bump(纯蓝牙层)配合使用: * bluetooth-bump 负责:蓝牙广播/扫描/RSSI 判定/去重 * bump-service 负责:状态机/API 调用/缓存/重试/事件派发 */ // ==================== 状态机 ==================== /** 碰一碰阶段 */ exports.BumpPhase = void 0; (function (BumpPhase) { /** 空闲 */ BumpPhase["Idle"] = "idle"; /** 正在启动(调 BumpStart 拿 tempId) */ BumpPhase["Starting"] = "starting"; /** 扫描中(蓝牙已启动,等待用户碰) */ BumpPhase["Scanning"] = "scanning"; /** 上报中(正在调 BumpReport) */ BumpPhase["Reporting"] = "reporting"; /** 已匹配成功 */ BumpPhase["Matched"] = "matched"; /** 失败(蓝牙/网络硬错误) */ BumpPhase["Failed"] = "failed"; })(exports.BumpPhase || (exports.BumpPhase = {})); /** 后台撮合状态(BumpReport 返回) */ exports.BumpMatchStatus = void 0; (function (BumpMatchStatus) { BumpMatchStatus[BumpMatchStatus["Unknown"] = 0] = "Unknown"; BumpMatchStatus[BumpMatchStatus["Waiting"] = 1] = "Waiting"; BumpMatchStatus[BumpMatchStatus["Matched"] = 2] = "Matched"; BumpMatchStatus[BumpMatchStatus["Loser"] = 3] = "Loser"; })(exports.BumpMatchStatus || (exports.BumpMatchStatus = {})); // ==================== 事件名常量 ==================== /** BumpService 派发的事件名 */ var BumpEvent = { /** 阶段变化 */ PhaseChanged: 'bump:phase-changed', /** 发现附近设备 */ PeerFound: 'bump:peer-found', /** 设备离开 */ PeerLost: 'bump:peer-lost', /** 碰蛋成功(含奖励) */ Matched: 'bump:matched', /** 软失败(可继续选下一只) */ SoftFail: 'bump:soft-fail', /** 硬失败(会话中断) */ Failed: 'bump:failed', /** tempId 已刷新 */ TempIdRefreshed: 'bump:tempid-refreshed' }; function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; } function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } var DEFAULT_TEMP_ID_REFRESH_MS = 150000; // 2.5 分钟 var DEFAULT_PEER_CACHE_TTL_MS = 30 * 60 * 1000; // 30 分钟 var STORAGE_KEY_NEARBY_PEER_CACHE = 'bump-service:nearby-peer-cache'; /** 无操作占位 */ var noopLogger = { info: function info() {}, warn: function warn() {}, error: function error() {} }; var noopEventBus = { emit: function emit() {}, on: function on() {}, off: function off() {} }; var BumpService = /*#__PURE__*/function () { function BumpService(options) { _classCallCheck__default["default"](this, BumpService); // ========== 公开只读状态 ========== /** 当前阶段 */ this.phase = exports.BumpPhase.Idle; /** 我方 tempId(BumpStart 后获得) */ this.myTempId = ''; /** tempId 过期时间戳 */ this.expireAt = 0; /** 活动 ID */ this.actId = ''; /** 当前匹配 ID(Report 成功后获得) */ this.matchId = ''; /** 当前正在碰的 peerTempId */ this.currentPeerTempId = ''; /** 附近设备缓存 */ this.nearbyPeers = new Map(); this.refreshTimer = null; this.pruneTimer = null; /** 正在进行中的 bumpPeer 调用(防重复点击) */ this.bumpingSet = new Set(); this.api = options.api; this.eventBus = options.eventBus || noopEventBus; this.storage = options.storage || null; this.logger = options.logger || noopLogger; this.actId = options.actId || ''; this.tempIdRefreshMs = options.tempIdRefreshMs || DEFAULT_TEMP_ID_REFRESH_MS; this.peerCacheTtlMs = options.peerCacheTtlMs || DEFAULT_PEER_CACHE_TTL_MS; } // ==================== 公开 API ==================== /** * 启动碰一碰会话 * 1. 调用 BumpStart 拿 tempId * 2. 启动 tempId 自动刷新定时器 * 3. 加载持久化缓存 * * 返回 tempId 供蓝牙层使用 */ return _createClass__default["default"](BumpService, [{ key: "start", value: function start(options) { return tslib_es6.__awaiter(this, void 0, void 0, /*#__PURE__*/_regeneratorRuntime__default["default"].mark(function _callee() { var actId, rsp, errMsg, _t; return _regeneratorRuntime__default["default"].wrap(function (_context) { while (1) switch (_context.prev = _context.next) { case 0: actId = (options === null || options === void 0 ? void 0 : options.actId) || this.actId; if (actId) { _context.next = 1; break; } this.fail('act_id 缺失'); throw new Error('BumpService.start: act_id 缺失'); case 1: this.actId = actId; this.setPhase(exports.BumpPhase.Starting); _context.prev = 2; _context.next = 3; return this.api.bumpStart(Object.assign({ act_id: actId }, options === null || options === void 0 ? void 0 : options.extra)); case 3: rsp = _context.sent; this.myTempId = rsp.temp_id || rsp.tempId || ''; this.expireAt = rsp.expire_at || rsp.expireAt || 0; if (this.myTempId) { _context.next = 4; break; } this.fail('BumpStart 未返回 tempId'); throw new Error('BumpStart 未返回 tempId'); case 4: this.logger.info("[BumpService] start \u6210\u529F, tempId=".concat(this.myTempId)); this.setPhase(exports.BumpPhase.Scanning); // 启动 tempId 自动刷新 this.startRefreshTimer(); // 加载持久化缓存 this.loadNearbyPeerCache(); // 启动缓存清理 this.startPruneTimer(); return _context.abrupt("return", this.myTempId); case 5: _context.prev = 5; _t = _context["catch"](2); errMsg = (_t === null || _t === void 0 ? void 0 : _t.err_msg) || (_t === null || _t === void 0 ? void 0 : _t.msg) || (_t === null || _t === void 0 ? void 0 : _t.message) || 'BumpStart 失败'; this.fail(errMsg); throw _t; case 6: case "end": return _context.stop(); } }, _callee, this, [[2, 5]]); })); } /** * 停止碰一碰会话 */ }, { key: "stop", value: function stop() { this.clearRefreshTimer(); this.clearPruneTimer(); this.saveNearbyPeerCache(); this.setPhase(exports.BumpPhase.Idle); this.myTempId = ''; this.expireAt = 0; this.matchId = ''; this.currentPeerTempId = ''; this.bumpingSet.clear(); this.logger.info('[BumpService] stopped'); } /** * 碰一碰核心流程:上报 + 领奖 * @param peerTempId 对端的 tempId * @param rssi 信号强度(可选) */ }, { key: "bumpPeer", value: function bumpPeer(peerTempId, rssi) { return tslib_es6.__awaiter(this, void 0, void 0, /*#__PURE__*/_regeneratorRuntime__default["default"].mark(function _callee2() { var reportRsp, status, matchId, reward, errMsg, _errMsg, _errMsg2, ret, _t2, _t3; return _regeneratorRuntime__default["default"].wrap(function (_context2) { while (1) switch (_context2.prev = _context2.next) { case 0: if (!this.bumpingSet.has(peerTempId)) { _context2.next = 1; break; } return _context2.abrupt("return", { success: false, error: '正在处理中,请勿重复点击' }); case 1: this.bumpingSet.add(peerTempId); this.currentPeerTempId = peerTempId; _context2.prev = 2; // 1. 上报撮合 this.setPhase(exports.BumpPhase.Reporting); _context2.next = 3; return this.api.bumpReport({ act_id: this.actId, my_temp_id: this.myTempId, peer_temp_id: peerTempId, rssi: rssi || 0, ts: Math.floor(Date.now() / 1000) }); case 3: reportRsp = _context2.sent; status = reportRsp.status || exports.BumpMatchStatus.Unknown; matchId = reportRsp.match_id || reportRsp.matchId || ''; // 2. 判断撮合结果 if (!(status === exports.BumpMatchStatus.Matched && matchId)) { _context2.next = 7; break; } this.matchId = matchId; // 3. 领取奖励 _context2.prev = 4; _context2.next = 5; return this.api.bumpReward({ act_id: this.actId, match_id: matchId }); case 5: reward = _context2.sent; this.setPhase(exports.BumpPhase.Matched); this.eventBus.emit(BumpEvent.Matched, { matchId: matchId, reward: reward, peerTempId: peerTempId }); return _context2.abrupt("return", { success: true, reward: reward, matchId: matchId }); case 6: _context2.prev = 6; _t2 = _context2["catch"](4); errMsg = (_t2 === null || _t2 === void 0 ? void 0 : _t2.err_msg) || (_t2 === null || _t2 === void 0 ? void 0 : _t2.msg) || '领取奖励失败'; this.softFail(errMsg, peerTempId); return _context2.abrupt("return", { success: false, error: errMsg, matchId: matchId, ret: Number(_t2 === null || _t2 === void 0 ? void 0 : _t2.ret) || 0 }); case 7: if (!(status === exports.BumpMatchStatus.Waiting)) { _context2.next = 8; break; } this.softFail('等待对方上报', peerTempId); return _context2.abrupt("return", { success: false, waiting: true, error: '等待对方上报' }); case 8: _errMsg = reportRsp.err_msg || '撮合失败'; this.softFail(_errMsg, peerTempId); return _context2.abrupt("return", { success: false, error: _errMsg, ret: Number(reportRsp.status) || 0 }); case 9: _context2.next = 11; break; case 10: _context2.prev = 10; _t3 = _context2["catch"](2); _errMsg2 = (_t3 === null || _t3 === void 0 ? void 0 : _t3.err_msg) || (_t3 === null || _t3 === void 0 ? void 0 : _t3.msg) || (_t3 === null || _t3 === void 0 ? void 0 : _t3.message) || 'BumpReport 失败'; ret = Number(_t3 === null || _t3 === void 0 ? void 0 : _t3.ret) || 0; this.softFail(_errMsg2, peerTempId); return _context2.abrupt("return", { success: false, error: _errMsg2, ret: ret }); case 11: _context2.prev = 11; this.bumpingSet["delete"](peerTempId); return _context2.finish(11); case 12: case "end": return _context2.stop(); } }, _callee2, this, [[2, 10, 11, 12], [4, 6]]); })); } /** * 注册附近设备(蓝牙层 onDeviceFound 时调用) * @param peerTempId 对端 tempId(从 BLE payload 解析) * @param deviceId BLE 设备 ID * @param rssi 信号强度 */ }, { key: "registerNearbyPeer", value: function registerNearbyPeer(peerTempId, deviceId, rssi) { if (!peerTempId || !deviceId) return; var existing = this.nearbyPeers.get(deviceId); var now = Date.now(); if (existing) { existing.peerTempId = peerTempId; existing.lastSeenAt = now; existing.rssi = rssi; } else { this.nearbyPeers.set(deviceId, { peerTempId: peerTempId, deviceId: deviceId, firstSeenAt: now, lastSeenAt: now, rssi: rssi }); this.eventBus.emit(BumpEvent.PeerFound, { peerTempId: peerTempId, deviceId: deviceId, rssi: rssi }); } } /** * 获取对端用户信息(用于 UI 展示昵称/头像) */ }, { key: "fetchPeerInfo", value: function fetchPeerInfo(peerTempId) { var _a; return tslib_es6.__awaiter(this, void 0, void 0, /*#__PURE__*/_regeneratorRuntime__default["default"].mark(function _callee3() { var rsp, _iterator, _step, _step$value, peer, _t4; return _regeneratorRuntime__default["default"].wrap(function (_context3) { while (1) switch (_context3.prev = _context3.next) { case 0: _context3.prev = 0; _context3.next = 1; return this.api.getPeerByTempId({ temp_id: peerTempId }); case 1: rsp = _context3.sent; // 回写到缓存 _iterator = _createForOfIteratorHelper(this.nearbyPeers); _context3.prev = 2; _iterator.s(); case 3: if ((_step = _iterator.n()).done) { _context3.next = 5; break; } _step$value = _slicedToArray__default["default"](_step.value, 2), peer = _step$value[1]; if (!(peer.peerTempId === peerTempId)) { _context3.next = 4; break; } peer.nick = rsp.user_nick || ((_a = rsp.pet) === null || _a === void 0 ? void 0 : _a.nick) || ''; peer.userHead = rsp.user_head || ''; peer.petId = rsp.pet_id || ''; return _context3.abrupt("continue", 5); case 4: _context3.next = 3; break; case 5: _context3.next = 7; break; case 6: _context3.prev = 6; _t4 = _context3["catch"](2); _iterator.e(_t4); case 7: _context3.prev = 7; _iterator.f(); return _context3.finish(7); case 8: return _context3.abrupt("return", rsp); case 9: _context3.prev = 9; _context3["catch"](0); return _context3.abrupt("return", null); case 10: case "end": return _context3.stop(); } }, _callee3, this, [[0, 9], [2, 6, 7, 8]]); })); } /** * 获取附近设备缓存快照(只返回 TTL 内的) */ }, { key: "getNearbyPeerCache", value: function getNearbyPeerCache() { var now = Date.now(); var result = []; var _iterator2 = _createForOfIteratorHelper(this.nearbyPeers), _step2; try { for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) { var _step2$value = _slicedToArray__default["default"](_step2.value, 2), peer = _step2$value[1]; if (now - peer.lastSeenAt <= this.peerCacheTtlMs) { result.push(Object.assign({}, peer)); } } } catch (err) { _iterator2.e(err); } finally { _iterator2.f(); } return result; } /** * 手动刷新 tempId(也可由定时器自动触发) */ }, { key: "refreshTempId", value: function refreshTempId() { return tslib_es6.__awaiter(this, void 0, void 0, /*#__PURE__*/_regeneratorRuntime__default["default"].mark(function _callee4() { var rsp, newTempId, _t6; return _regeneratorRuntime__default["default"].wrap(function (_context4) { while (1) switch (_context4.prev = _context4.next) { case 0: _context4.prev = 0; _context4.next = 1; return this.api.bumpStart({ act_id: this.actId }); case 1: rsp = _context4.sent; newTempId = rsp.temp_id || rsp.tempId || ''; if (newTempId) { this.myTempId = newTempId; this.expireAt = rsp.expire_at || rsp.expireAt || 0; this.eventBus.emit(BumpEvent.TempIdRefreshed, { tempId: newTempId }); this.logger.info("[BumpService] tempId \u5DF2\u5237\u65B0: ".concat(newTempId)); } return _context4.abrupt("return", newTempId); case 2: _context4.prev = 2; _t6 = _context4["catch"](0); this.logger.warn("[BumpService] refreshTempId \u5931\u8D25: ".concat(_t6 === null || _t6 === void 0 ? void 0 : _t6.message)); return _context4.abrupt("return", this.myTempId); case 3: case "end": return _context4.stop(); } }, _callee4, this, [[0, 2]]); })); } // ==================== 私有方法 ==================== }, { key: "setPhase", value: function setPhase(phase) { var prev = this.phase; this.phase = phase; if (prev !== phase) { this.eventBus.emit(BumpEvent.PhaseChanged, { from: prev, to: phase }); } } }, { key: "fail", value: function fail(errMsg) { this.logger.error("[BumpService] fail: ".concat(errMsg)); this.setPhase(exports.BumpPhase.Failed); this.eventBus.emit(BumpEvent.Failed, { error: errMsg }); } }, { key: "softFail", value: function softFail(errMsg, peerTempId) { this.logger.warn("[BumpService] softFail: ".concat(errMsg, ", peer=").concat(peerTempId)); // 软失败回退到 Scanning,可继续碰下一只 this.setPhase(exports.BumpPhase.Scanning); this.eventBus.emit(BumpEvent.SoftFail, { error: errMsg, peerTempId: peerTempId }); } // -------- tempId 刷新 -------- }, { key: "startRefreshTimer", value: function startRefreshTimer() { var _this = this; this.clearRefreshTimer(); this.refreshTimer = setInterval(function () { if (_this.phase === exports.BumpPhase.Scanning || _this.phase === exports.BumpPhase.Idle) { void _this.refreshTempId(); } }, this.tempIdRefreshMs); } }, { key: "clearRefreshTimer", value: function clearRefreshTimer() { if (this.refreshTimer) { clearInterval(this.refreshTimer); this.refreshTimer = null; } } // -------- 缓存清理 -------- }, { key: "startPruneTimer", value: function startPruneTimer() { var _this2 = this; this.clearPruneTimer(); this.pruneTimer = setInterval(function () { _this2.pruneExpiredPeers(); }, 10000); // 10s 清理一次 } }, { key: "clearPruneTimer", value: function clearPruneTimer() { if (this.pruneTimer) { clearInterval(this.pruneTimer); this.pruneTimer = null; } } }, { key: "pruneExpiredPeers", value: function pruneExpiredPeers() { var now = Date.now(); var _iterator3 = _createForOfIteratorHelper(this.nearbyPeers), _step3; try { for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) { var _step3$value = _slicedToArray__default["default"](_step3.value, 2), key = _step3$value[0], peer = _step3$value[1]; if (now - peer.lastSeenAt > this.peerCacheTtlMs) { this.nearbyPeers["delete"](key); this.eventBus.emit(BumpEvent.PeerLost, { peerTempId: peer.peerTempId, deviceId: key }); } } } catch (err) { _iterator3.e(err); } finally { _iterator3.f(); } } // -------- 缓存持久化 -------- }, { key: "saveNearbyPeerCache", value: function saveNearbyPeerCache() { if (!this.storage) return; try { var snapshot = this.getNearbyPeerCache(); this.storage.set(STORAGE_KEY_NEARBY_PEER_CACHE, JSON.stringify(snapshot)); } catch (_a) { // 静默 } } }, { key: "loadNearbyPeerCache", value: function loadNearbyPeerCache() { if (!this.storage) return; try { var raw = this.storage.get(STORAGE_KEY_NEARBY_PEER_CACHE); if (!raw) return; var list = JSON.parse(raw); var now = Date.now(); var _iterator4 = _createForOfIteratorHelper(list), _step4; try { for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) { var peer = _step4.value; if (now - peer.lastSeenAt <= this.peerCacheTtlMs && peer.deviceId) { if (!this.nearbyPeers.has(peer.deviceId)) { this.nearbyPeers.set(peer.deviceId, peer); } } } } catch (err) { _iterator4.e(err); } finally { _iterator4.f(); } } catch (_a) { // 静默 } } }]); }(); exports.BumpEvent = BumpEvent; exports.BumpService = BumpService;