nativeMethods.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. import { getCapturePhotoYunSign, saveCapturePhoto } from "@/api/login";
  2. import { HOST_FILE_HASH_MAP } from "@/constants/constants";
  3. import { throttle, xor } from "lodash-es";
  4. export function isElectron() {
  5. return typeof window.nodeRequire != "undefined";
  6. }
  7. const fs: typeof import("fs") = isElectron() && window.nodeRequire("fs");
  8. /** 执行本地exe文件 */
  9. export function execLocal(exeName: string): Promise<void> {
  10. if (!isElectron()) {
  11. logger({
  12. pgu: "AUTO",
  13. cnl: ["local", "server"],
  14. dtl: "不在Electron中,调用 " + exeName + " 失败",
  15. });
  16. throw new Error("不在Electron中,调用 fs 失败");
  17. }
  18. if (import.meta.env.DEV) {
  19. return Promise.resolve();
  20. }
  21. return new Promise<void>((resolve) => {
  22. // eslint-disable-next-line
  23. window
  24. .nodeRequire("node-cmd")
  25. .get(
  26. "cmd /c chcp 65001>nul && " + exeName,
  27. async (err: Error, data: string, stderr: string) => {
  28. console.log(exeName, err, data); // 未免过多日志,此处后续可以关闭
  29. logger({
  30. pgu: "AUTO",
  31. cnl: ["console", "local", "server"],
  32. act: exeName + " called",
  33. ejn: JSON.stringify(err),
  34. dtl: data,
  35. ext: {
  36. stderr: JSON.stringify(stderr),
  37. absPath: exeName.includes(":"),
  38. },
  39. });
  40. // 如果相对路径没找到,则通过绝对路径来执行
  41. if (!exeName.includes(":") && err) {
  42. const remote: typeof import("electron").remote =
  43. window.nodeRequire("electron").remote;
  44. const fs: typeof import("fs") = remote.require("fs");
  45. const path: typeof import("path") = remote.require("path");
  46. const [exePath, exeParams] = exeName.split(" ");
  47. const absPath = path.join(
  48. remote.app.getAppPath(),
  49. "../../",
  50. exePath
  51. );
  52. if (fs.existsSync(absPath)) {
  53. try {
  54. await execLocal([absPath, exeParams].join(" ").trim());
  55. } catch (error) {
  56. console.log("second try error", absPath);
  57. logger({
  58. cnl: ["local", "server"],
  59. dtl: "second try error: " + absPath,
  60. });
  61. }
  62. }
  63. }
  64. resolve();
  65. }
  66. );
  67. });
  68. }
  69. /** 文件路径是否存在 */
  70. export function fileExists(file: string): boolean {
  71. if (!isElectron()) {
  72. logger({
  73. pgu: "AUTO",
  74. cnl: ["local", "server"],
  75. dtl: "不在Electron中,调用 fs 失败",
  76. });
  77. throw new Error("不在Electron中,调用 fs 失败");
  78. }
  79. return fs.existsSync(file);
  80. }
  81. /** 检测当前运行的进程中是否有“向日葵” */
  82. export function nodeCheckRemoteDesktop(): boolean {
  83. logger({
  84. pgu: "AUTO",
  85. cnl: ["local", "server"],
  86. key: "nodeCheckRemoteDesktop",
  87. });
  88. // eslint-disable-next-line @typescript-eslint/no-unsafe-call
  89. const appList: string = window
  90. .nodeRequire("child_process")
  91. .execSync("cmd /c chcp 65001>nul && tasklist /FO CSV")
  92. .toString();
  93. // console.debug(appList);
  94. const regex = new RegExp("sunloginclient|选择免安装运行,截图识别", "gi");
  95. return !!(appList?.match(regex) || []).length;
  96. }
  97. let previousAppList: string[] = [];
  98. /** 将电脑运行的进程记录到日志 */
  99. export function nodeCheckProcess(): void {
  100. try {
  101. // eslint-disable-next-line @typescript-eslint/no-unsafe-call
  102. const appListCP: string = window
  103. .nodeRequire("child_process")
  104. .execSync("cmd /c chcp 65001>nul && tasklist /FO CSV")
  105. .toString();
  106. // 不能打印,electron-log 不能接收
  107. // console.log(appList);
  108. let appList = appListCP.split("\r\n");
  109. appList.shift();
  110. appList = appList.map((v) => v.split(",")[0]);
  111. const xorRes = xor(previousAppList, appList);
  112. if (xorRes && xorRes.length > 0) {
  113. logger({
  114. pgu: "AUTO",
  115. cnl: ["local", "server"],
  116. key: "nodeCheckProcess",
  117. dtl: xorRes.join(" ||| "),
  118. });
  119. }
  120. previousAppList = appList;
  121. } catch (error) {
  122. logger({
  123. cnl: ["console", "local", "server"],
  124. pgu: "AUTO",
  125. ejn: JSON.stringify(error),
  126. });
  127. }
  128. }
  129. /** 检测当前程序包的完整性。 */
  130. export function checkMainExe(): boolean {
  131. try {
  132. let iid: string = window.nodeRequire("process").pid;
  133. const cp: typeof import("child_process") =
  134. window.nodeRequire("child_process");
  135. iid = cp
  136. .execSync(
  137. `cmd /c chcp 65001>nul && C:\\Windows\\System32\\wbem\\wmic process where ^(processid^=${iid}^) get parentprocessid /value`
  138. )
  139. .toString();
  140. iid = iid.replace("ParentProcessId=", "").trim();
  141. console.log(iid);
  142. logger({
  143. cnl: ["local", "console", "server"],
  144. key: "checkMainExe",
  145. dtl: `iid1: ${iid}`,
  146. });
  147. iid = cp
  148. .execSync(
  149. `cmd /c chcp 65001>nul && C:\\Windows\\System32\\wbem\\wmic process where ^(processid^=${iid}^) get parentprocessid /value`
  150. )
  151. .toString();
  152. iid = iid.replace("ParentProcessId=", "").trim();
  153. logger({
  154. cnl: ["local", "console", "server"],
  155. key: "checkMainExe",
  156. dtl: `iid2: ${iid}`,
  157. });
  158. const executablePathBuffer = cp.execSync(
  159. `cmd /c chcp 65001>nul && C:\\Windows\\System32\\wbem\\wmic process where ^(processid^=${iid}^) get executablepath /value`
  160. );
  161. console.log(executablePathBuffer);
  162. const encoding = window.nodeRequire("encoding");
  163. // eslint-disable-next-line @typescript-eslint/no-unsafe-call
  164. let executablePath: string = encoding
  165. .convert(executablePathBuffer, "utf8", "gbk")
  166. .toString();
  167. logger({
  168. cnl: ["local", "console", "server"],
  169. key: "checkMainExe",
  170. dtl: executablePath,
  171. });
  172. executablePath = executablePath
  173. .replace("ExecutablePath=", "")
  174. .trim()
  175. .replace(/&amp;/g, "&");
  176. if (executablePath === eval(`proceess.env.PORTABLE_EXECUTABLE_FILE`)) {
  177. const crypto: typeof import("crypto") = window.nodeRequire("crypto");
  178. const getHash = crypto
  179. .createHmac("sha256", "abcdefg")
  180. .update(fs.readFileSync(executablePath))
  181. .digest("hex");
  182. console.log("the hash: ", getHash);
  183. logger({
  184. cnl: ["local", "console", "server"],
  185. key: "checkMainExe",
  186. dtl: `the hash: ${getHash}`,
  187. });
  188. if (HOST_FILE_HASH_MAP.get(window.location.hostname) === getHash) {
  189. return true;
  190. }
  191. }
  192. // check filepath executablePath md5
  193. } catch (error) {
  194. console.log(error);
  195. logger({
  196. cnl: ["local", "console", "server"],
  197. key: "checkMainExe",
  198. ejn: JSON.stringify(error),
  199. });
  200. }
  201. return false;
  202. }
  203. /** 初始化桌面抓拍 */
  204. export function initScreenShot() {
  205. if (import.meta.env.DEV) return;
  206. if (!isElectron()) return;
  207. function handleStream(stream: MediaStream) {
  208. const video: HTMLVideoElement = document.querySelector("#ssVideo")!;
  209. video.srcObject = stream;
  210. video.onloadedmetadata = () => video.play();
  211. }
  212. const electron: typeof import("electron") = window.nodeRequire("electron");
  213. electron.desktopCapturer.getSources(
  214. { types: ["window", "screen"] },
  215. (e: any, sources: { id: string; name: string }[]) => {
  216. console.log(e, sources);
  217. for (const source of sources) {
  218. console.log(source);
  219. if (source.name === "Entire screen") {
  220. try {
  221. navigator.mediaDevices
  222. .getUserMedia({
  223. audio: false,
  224. video: {
  225. // @ts-expect-error 不确定是chrome/electron标准是否不一样,需要测试
  226. mandatory: {
  227. chromeMediaSource: "desktop",
  228. chromeMediaSourceId: source.id,
  229. minWidth: 600,
  230. maxWidth: 600,
  231. minHeight: 480,
  232. maxHeight: 480,
  233. },
  234. },
  235. })
  236. .then((stream) => handleStream(stream))
  237. .catch((e) =>
  238. logger({
  239. cnl: ["local", "server"],
  240. pgu: "AUTO",
  241. act: "ss-failed",
  242. possibleError: e,
  243. })
  244. );
  245. } catch (err) {
  246. logger({
  247. cnl: ["local", "server"],
  248. pgu: "AUTO",
  249. act: "ss-failed",
  250. ejn: JSON.stringify(err),
  251. });
  252. }
  253. return;
  254. }
  255. }
  256. }
  257. );
  258. }
  259. /** 保存当前屏幕截图 */
  260. export async function getScreenShot({ cause = "ss-none" }) {
  261. const video: HTMLVideoElement = document.querySelector("#ssVideo")!;
  262. async function getSnapShot() {
  263. return new Promise((resolve, reject) => {
  264. if (video.readyState !== 4 || !(<MediaStream>video.srcObject)?.active) {
  265. reject("desktop没有正常启用");
  266. }
  267. video.pause();
  268. const canvas = document.createElement("canvas");
  269. canvas.width = 220;
  270. canvas.height = 165;
  271. const context = canvas.getContext("2d");
  272. context?.drawImage(video, 0, 0, 220, 165);
  273. canvas.toBlob(resolve, "image/png", 0.95);
  274. });
  275. }
  276. if (window.location.pathname.includes("/login")) return;
  277. const captureBlob = await getSnapShot();
  278. const res = await getCapturePhotoYunSign({ fileSuffix: "png" });
  279. try {
  280. await saveCapturePhoto(res.data.formUrl, res.data.formParams, {
  281. file: captureBlob,
  282. });
  283. logger({
  284. pgu: "AUTO",
  285. cnl: ["server"],
  286. dtl: "桌面抓拍保存成功",
  287. ext: {
  288. resultUrl: res.data.accessUrl,
  289. cause,
  290. },
  291. });
  292. } catch (error) {
  293. console.log(error);
  294. logger({
  295. pgu: "AUTO",
  296. cnl: ["server"],
  297. dtl: "桌面抓拍失败",
  298. ejn: JSON.stringify(error),
  299. ext: {
  300. cause,
  301. },
  302. });
  303. } finally {
  304. video && video.play();
  305. }
  306. }
  307. /** 在app resize时触发 */
  308. export function registerOnResize() {
  309. const throttledResizeLog = throttle(() => {
  310. logger({
  311. cnl: ["local", "server"],
  312. pgu: "AUTO",
  313. act: "registerOnResize",
  314. ext: {
  315. width: window.screen.width,
  316. height: window.screen.height,
  317. screenX: window.screen.availWidth,
  318. screenY: window.screen.availHeight,
  319. clientWidth: document.documentElement.clientWidth,
  320. clientHeight: document.documentElement.clientHeight,
  321. windowInnerWidth: window.innerWidth,
  322. windowInnerHeight: window.innerHeight,
  323. windowOuterWidth: window.outerWidth,
  324. windowOuterHeight: window.outerHeight,
  325. },
  326. });
  327. void getScreenShot({ cause: "ss-registerOnResize" });
  328. }, 3000);
  329. window.onresize = throttledResizeLog;
  330. }