t-comm
Version:
专业、稳定、纯粹的工具库
617 lines (610 loc) • 23.6 kB
JavaScript
'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;