reader-client.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  1. class ReaderClient {
  2. constructor(options = {}) {
  3. this.wsUrl = options.wsUrl || "ws://127.0.0.1:6689";
  4. this.driveType = options.driveType || "D5200";
  5. this.portName = options.portName || "USB";
  6. this.hidSN = options.hidSN || "";
  7. this.driverIp = options.driverIp || "";
  8. this.driverPort = options.driverPort || "6688";
  9. // ISO14443A 参数
  10. this.keyType = options.keyType || "0";
  11. this.key = options.key || "FFFFFFFFFFFF";
  12. this.blockAddress = options.blockAddress || "1";
  13. this.numberOfBlocksRead = options.numberOfBlocksRead || "1";
  14. // ISO15693 参数
  15. this.readDataType = options.readDataType || "0";
  16. this.readSecurityStatus = options.readSecurityStatus || "0";
  17. this.iso15693BlockAddress = options.iso15693BlockAddress || "0";
  18. this.iso15693NumberOfBlocks = options.iso15693NumberOfBlocks || "7";
  19. this.ws = null;
  20. this.connected = false;
  21. this.handle = null;
  22. this.requestId = 1;
  23. this.pending = new Map();
  24. }
  25. async init() {
  26. if (typeof WebSocket === "undefined") {
  27. throw new Error("当前浏览器不支持 WebSocket");
  28. }
  29. return true;
  30. }
  31. async ensureWsConnected() {
  32. if (this.ws && this.ws.readyState === WebSocket.OPEN) {
  33. return;
  34. }
  35. if (this.ws && this.ws.readyState === WebSocket.CONNECTING) {
  36. await new Promise((resolve, reject) => {
  37. const timeoutRef = setTimeout(
  38. () => reject(new Error("读卡器服务连接超时")),
  39. 5000
  40. );
  41. const onOpen = () => {
  42. clearTimeout(timeoutRef);
  43. this.ws.removeEventListener("open", onOpen);
  44. this.ws.removeEventListener("error", onError);
  45. resolve();
  46. };
  47. const onError = () => {
  48. clearTimeout(timeoutRef);
  49. this.ws.removeEventListener("open", onOpen);
  50. this.ws.removeEventListener("error", onError);
  51. reject(new Error("读卡器服务连接失败"));
  52. };
  53. this.ws.addEventListener("open", onOpen);
  54. this.ws.addEventListener("error", onError);
  55. });
  56. return;
  57. }
  58. this.ws = new WebSocket(this.wsUrl);
  59. this.ws.onmessage = (event) => {
  60. this.handleMessage(event.data);
  61. };
  62. this.ws.onclose = () => {
  63. this.connected = false;
  64. this.handle = null;
  65. for (const [, item] of this.pending) {
  66. clearTimeout(item.timeoutRef);
  67. item.reject(new Error("读卡器服务连接已断开"));
  68. }
  69. this.pending.clear();
  70. };
  71. this.ws.onerror = () => {
  72. // 具体错误在请求超时/close时抛出
  73. };
  74. await new Promise((resolve, reject) => {
  75. const timeoutRef = setTimeout(
  76. () => reject(new Error("读卡器服务连接超时")),
  77. 5000
  78. );
  79. this.ws.onopen = () => {
  80. clearTimeout(timeoutRef);
  81. resolve();
  82. };
  83. this.ws.onerror = () => {
  84. clearTimeout(timeoutRef);
  85. reject(new Error("读卡器服务连接失败,请先启动厂家读卡器服务程序"));
  86. };
  87. });
  88. }
  89. handleMessage(rawData) {
  90. let payload;
  91. try {
  92. payload = JSON.parse(rawData);
  93. } catch {
  94. for (const [, item] of this.pending) {
  95. clearTimeout(item.timeoutRef);
  96. item.resolve(rawData);
  97. }
  98. this.pending.clear();
  99. return;
  100. }
  101. const traceId = payload.traceId;
  102. if (traceId && this.pending.has(traceId)) {
  103. const item = this.pending.get(traceId);
  104. this.pending.delete(traceId);
  105. clearTimeout(item.timeoutRef);
  106. item.resolve(payload);
  107. return;
  108. }
  109. if (this.pending.size === 1) {
  110. const firstKey = this.pending.keys().next().value;
  111. const item = this.pending.get(firstKey);
  112. this.pending.delete(firstKey);
  113. clearTimeout(item.timeoutRef);
  114. item.resolve(payload);
  115. }
  116. }
  117. async sendCommand(command, param = {}, handleOverride, timeoutMs = 6000) {
  118. await this.ensureWsConnected();
  119. const traceId = `r${Date.now()}_${this.requestId++}`;
  120. const message = {
  121. command,
  122. param,
  123. traceId,
  124. };
  125. const finalHandle = handleOverride ?? this.handle;
  126. if (finalHandle !== null && finalHandle !== undefined) {
  127. message.handle = String(finalHandle);
  128. }
  129. const promise = new Promise((resolve, reject) => {
  130. const timeoutRef = setTimeout(() => {
  131. this.pending.delete(traceId);
  132. reject(new Error(`${command} 超时`));
  133. }, timeoutMs);
  134. this.pending.set(traceId, { resolve, reject, timeoutRef });
  135. });
  136. this.ws.send(JSON.stringify(message));
  137. return promise;
  138. }
  139. getPayloadCode(payload) {
  140. if (!payload || typeof payload !== "object") {
  141. return null;
  142. }
  143. if (payload.code != null) {
  144. return Number(payload.code);
  145. }
  146. if (payload.status != null) {
  147. return Number(payload.status);
  148. }
  149. if (payload.result?.code != null) {
  150. return Number(payload.result.code);
  151. }
  152. return null;
  153. }
  154. getPayloadMessage(payload) {
  155. if (!payload || typeof payload !== "object") {
  156. return "";
  157. }
  158. return String(
  159. payload.msg ??
  160. payload.message ??
  161. payload.error ??
  162. payload.result?.msg ??
  163. payload.result?.message ??
  164. ""
  165. );
  166. }
  167. extractHandle(payload) {
  168. if (!payload || typeof payload !== "object") {
  169. return null;
  170. }
  171. const h = payload.handle ?? payload.result?.handle ?? payload.data?.handle;
  172. if (h === null || h === undefined || h === "") {
  173. return null;
  174. }
  175. return String(h);
  176. }
  177. extractTagDataList(payload) {
  178. if (!payload || typeof payload !== "object") {
  179. return [];
  180. }
  181. if (Array.isArray(payload.tagDataList)) {
  182. return payload.tagDataList;
  183. }
  184. if (Array.isArray(payload.data)) {
  185. return payload.data;
  186. }
  187. if (payload.data && Array.isArray(payload.data.tagDataList)) {
  188. return payload.data.tagDataList;
  189. }
  190. if (payload.result && Array.isArray(payload.result.tagDataList)) {
  191. return payload.result.tagDataList;
  192. }
  193. if (payload.result && Array.isArray(payload.result.data)) {
  194. return payload.result.data;
  195. }
  196. return [];
  197. }
  198. parseUidFromTagRow(row) {
  199. if (!row) {
  200. return "";
  201. }
  202. if (Array.isArray(row)) {
  203. return String(row[1] || "").trim();
  204. }
  205. if (typeof row === "object") {
  206. if (Array.isArray(row.value)) {
  207. return String(row.value[1] || "").trim();
  208. }
  209. if (Array.isArray(row.data)) {
  210. return String(row.data[1] || "").trim();
  211. }
  212. if (Array.isArray(row.tag)) {
  213. return String(row.tag[1] || "").trim();
  214. }
  215. for (const value of Object.values(row)) {
  216. if (Array.isArray(value)) {
  217. return String(value[1] || "").trim();
  218. }
  219. }
  220. }
  221. return "";
  222. }
  223. async selectDevice() {
  224. await this.init();
  225. try {
  226. await this.sendCommand("CloseDriver", {}, -1, 3000);
  227. } catch {
  228. // 忽略
  229. }
  230. const openResp = await this.sendCommand(
  231. "OpenDriver",
  232. {
  233. driveType: this.driveType,
  234. portName: this.portName,
  235. hidSN: this.hidSN,
  236. driverIp: this.driverIp,
  237. driverPort: this.driverPort,
  238. driverId: "",
  239. userName: "",
  240. passWord: "",
  241. },
  242. undefined,
  243. 8000
  244. );
  245. const code = this.getPayloadCode(openResp);
  246. if (code != null && code < 0) {
  247. const msg = this.getPayloadMessage(openResp) || "OpenDriver 失败";
  248. throw new Error(msg);
  249. }
  250. const openedHandle = this.extractHandle(openResp);
  251. if (!openedHandle) {
  252. throw new Error("OpenDriver 未返回 handle");
  253. }
  254. this.handle = openedHandle;
  255. this.connected = true;
  256. return true;
  257. }
  258. // ISO14443A 读取 UID
  259. async readUidISO14443A() {
  260. await this.init();
  261. if (!this.connected || !this.handle) {
  262. throw new Error("读卡器未连接,请先连接读卡器");
  263. }
  264. const readResp = await this.sendCommand(
  265. "ISO14443A_BatchReadMultiBlocks",
  266. {
  267. antennaCollection: [1],
  268. readDataType: "0",
  269. keyType: this.keyType,
  270. key: this.key,
  271. blockAddress: this.blockAddress,
  272. numberOfBlocksRead: this.numberOfBlocksRead,
  273. },
  274. this.handle,
  275. 6000
  276. );
  277. const code = this.getPayloadCode(readResp);
  278. if (code != null && code < 0) {
  279. const msg = this.getPayloadMessage(readResp) || "ISO14443A 读卡失败";
  280. throw new Error(msg);
  281. }
  282. const tagDataList = this.extractTagDataList(readResp);
  283. if (!Array.isArray(tagDataList) || tagDataList.length === 0) {
  284. throw new Error("未读取到 UID,请确认 ISO14443A 卡片已到读卡位");
  285. }
  286. const firstRow = tagDataList[0];
  287. const uid = this.parseUidFromTagRow(firstRow);
  288. if (!uid) {
  289. throw new Error("未读取到 UID,请确认 ISO14443A 卡片已到读卡位");
  290. }
  291. return uid.toUpperCase();
  292. }
  293. // ISO15693 读取 UID
  294. async readUidISO15693() {
  295. await this.init();
  296. if (!this.connected || !this.handle) {
  297. throw new Error("读卡器未连接,请先连接读卡器");
  298. }
  299. const readResp = await this.sendCommand(
  300. "ISO15693_BatchReadMultiBlocks",
  301. {
  302. antennaCollection: [1],
  303. readDataType: this.readDataType,
  304. readSecurityStatus: this.readSecurityStatus,
  305. blockAddress: this.iso15693BlockAddress,
  306. numberOfBlocksRead: this.iso15693NumberOfBlocks,
  307. },
  308. this.handle,
  309. 6000
  310. );
  311. const code = this.getPayloadCode(readResp);
  312. if (code != null && code < 0) {
  313. const msg = this.getPayloadMessage(readResp) || "ISO15693 读卡失败";
  314. throw new Error(msg);
  315. }
  316. const tagDataList = this.extractTagDataList(readResp);
  317. if (!Array.isArray(tagDataList) || tagDataList.length === 0) {
  318. throw new Error("未读取到 UID,请确认 ISO15693 标签已到读卡位");
  319. }
  320. const firstRow = tagDataList[0];
  321. const uid = this.parseUidFromTagRow(firstRow);
  322. if (!uid) {
  323. throw new Error("未读取到 UID,请确认 ISO15693 标签已到读卡位");
  324. }
  325. return uid.toUpperCase();
  326. }
  327. // 根据协议类型读取 UID
  328. async readUid(protocol = "ISO14443A") {
  329. if (protocol === "ISO15693") {
  330. return await this.readUidISO15693();
  331. }
  332. return await this.readUidISO14443A();
  333. }
  334. }
  335. window.ReaderClient = ReaderClient;