UNPKG

@plasosdk/plaso-electron-sdk

Version:

伯索课堂Electron SDK

449 lines (403 loc) 20.5 kB
const LogFormatter = require('../scripts/logger'); const path = require('path'); const { getElectronRemote, parseUrlParams, getEnvironment, getDisplayMatching } = require('./util'); const remote = getElectronRemote(); const defaultLoggerPath = remote ? path.join(remote.app.getPath('userData'), '/P403FileTemp/') : ''; const logger = new LogFormatter(defaultLoggerPath); logger.init(); const getConfig = require('../scripts/getConfig'); const { platform } = getConfig(); const isMac = platform === 'darwin'; const { ERROR_CODE, LESSON_TYPE, CLASS_WINDOW_MESG_TYPE, RENDER_TO_MAIN_MESG_TYPE, FILE_TYPE } = require('./macro'); let currentWinId = null; let currentWebContentsId = null; /** * @typedef {Object} classOptionsType * @property {boolean} [debug] - 是否开启课堂窗口debug模式 * @property {string} query - 进课堂的必备query * @property {string} [version] - 格式参考:1.53.901 */ /** * @typedef {Object} FileParams * @property {string[]} filePath - 备课文件本地地址 * @property {'png' | 'pb' | string} fileType */ /** * @typedef {Object} classWindowType * @property {classOptionsType} classOptions 进课堂参数对象 * @property {Object} [electronWinOptions] 自定义electron的窗口参数 * @property {(winId: number)=>void} [onClassWindowReadyFn] 课堂窗口打开渲染成功后的回调 * @property {(winId: number)=>void} [onClassWindowLeaveFn] 课堂窗口关闭后的回调 * @property {(meetingId: string)=>void} [onClassFinishedFn] 课堂结束后的回调 * @property {( * params: { * fileInfo: FileParams[], * fileName?: string * }, * callback: (result: boolean) => void * ) => void} [onSaveBoardFn] 保存板书,具体的保存逻辑由外部实现,取消保存板书时,callback传false, 不然传true * @property {()=>void} [onOpenResourceCenterFn] 通知外部用户打开自己的资料中心,资料中心的具体ui和逻辑由外部用户自己实现 * @property {(info: any, option: any)=>Promise<string>} [onGetExtFileNameFn] 通过insertObject插入的文件传入 参数 info 时,怎么从info中获取文件的可访问地址的逻辑在用户那,所以需要函数从外部用户获取外部用户传入的文件地址 * @property {(info: any, option: { suffix: string })=>Promise<string>} [onGetPreParseFileNameFn] 类似onGetExtFileNameFn,只是获取的文件地址是预解析文件地址,通过suffix得到预解析文件路径 * @property {(info: { * time: string; * id: string; * name: string; * bugDescription: string; * })=>void} [onReportIssuesFn] 上报问题,具体的上报逻辑由外部实现 * */ /** * @param {classWindowType} classWindowProps * @returns */ function createClassWindow(classWindowProps) { const { classOptions, electronWinOptions, onClassWindowReadyFn, onClassWindowLeaveFn, onClassFinishedFn, onSaveBoardFn, onOpenResourceCenterFn, onGetExtFileNameFn, onGetPreParseFileNameFn, onReportIssuesFn, } = classWindowProps; logger.info( `课堂窗口创建信息: version is ${getVersion()}, classOptions is ${JSON.stringify(classOptions)},electronWinOptions is ${JSON.stringify( electronWinOptions, )}`, ); if (currentWinId) { logger.warn('课堂窗口同时仅能存在一个'); return ERROR_CODE.CLASS_WINODW_GREATER_THAN_ONE; } const isElectron = !!process?.versions?.['electron']; if (isElectron) { try { const appProxy = require('@plasosdk/winproxy'); const { ipcRenderer } = window.require('electron'); const displayInfo = getDisplayMatching(); if (remote && displayInfo) { const webContents = remote.getCurrentWebContents(); const win = remote.getCurrentWindow(); /**----------------------------------------------------课堂入参相关-----------------------------------*/ const classInfo = classOptions.query ? parseUrlParams(classOptions.query) : {}; const autoMaximized = true; /** 课堂最大化按钮是否是切换全屏模式 */ let fullScreenable = false; if (classInfo.userType === 'listener') { fullScreenable = true; } // 环境确认 const env = classOptions.env ? classOptions.env.toLowerCase() : 'www'; let rhost, dhost; if (env == 'local') { rhost = 'http://localhost:4399/'; dhost = 'https://dev.plaso.cn/'; } else if (env == 'dev') { rhost = `https://${env}.plaso.cn/static/yxtelectronsdk/`; dhost = `https://${env}.plaso.cn/`; } else if (env == 'test' || env == 'itest' || env == 'ftest') { rhost = `https://${env}.plaso.cn/static/yxtsdk/`; dhost = `https://${env}.plaso.cn/`; } else { rhost = `https://wwwr.plaso.cn/static/sdk/styleupime/${classOptions.version ?? '1.60.139'}/`; dhost = 'https://www.plaso.cn/'; } // 截图和虚拟声卡地址确认 let flameshotPath = path.join(__dirname, '../lib/flameshot/flameshot.exe'); let PlasoALD = ''; if (isMac) { flameshotPath = path.join(__dirname, '../lib/flameshot.app/Contents/MacOS/flameshot'); PlasoALD = path.join(__dirname, '../lib/PlasoALD'); } const classOptionsObj = { ...classOptions, ...classInfo, rhost, dhost, query: classOptions.query, appType: classOptions.appType, markString: env, env, classType: classOptions.classType, // 让课堂里退出时标识状态,详情见commFunction.js onClose: 'close', electronSdkLoggerPath: logger.loggerPath ?? '', electronSdkLoggerName: logger.logFileName ?? '', flameshotPath: flameshotPath ?? '', PlasoALDPath: PlasoALD ?? '', clientType: 'electron', isElectronSdk: true, openerId: webContents.id, memoryConfigKey: classOptions.memoryConfigKey ?? classInfo.loginName ?? classOptions.loginName ?? '', maximized: autoMaximized, fullScreenable, supportOnReportIssues: !!onReportIssuesFn, }; if (classOptionsObj.userType === 'monitor') { classOptionsObj.monitor = true; } /**----------------------------------------------------electron 窗口相关设置-----------------------------------*/ const bounds = win.getBounds(); const screenWidth = displayInfo.size.width; const screenWorkAreaWidth = displayInfo.workAreaSize.width; const screenHeight = displayInfo.size.height; const screenWorkAreaHeight = displayInfo.workAreaSize.height; const changeBoundsDate = (ischangeWidth, baseWidthOrHeight) => { const newWidthOrHeight = baseWidthOrHeight - 10; if (ischangeWidth) { bounds.height = Math.round((bounds.height * newWidthOrHeight) / bounds.width); bounds.width = newWidthOrHeight; } else { bounds.width = Math.round((bounds.width * newWidthOrHeight) / bounds.height); bounds.height = newWidthOrHeight; } }; // 课堂窗口创建时的宽或高和 屏幕宽高或屏幕工作区宽高 一致时,此时透明窗口会失效,需要调整进课堂时的窗口宽高 if (bounds.width === screenWidth || bounds.width === screenWorkAreaWidth) { changeBoundsDate(true, Math.min(screenWidth, screenWorkAreaWidth)); } if (bounds.height === screenHeight || bounds.height === screenWorkAreaHeight) { changeBoundsDate(false, Math.min(screenHeight, screenWorkAreaHeight)); } const defaultElecteonWinOptions = { frame: false, /** * electron 12.0.18之后,resizable设为true才能全屏和最大化, * 而课堂窗口不允许通过electron自身的缩放行为来改变大小,因此进入课堂后会将resizable设为false */ resizable: true, /** 设置为true,mac上setFullScreen(true)才生效,setSimpleFullScreen(true)不依赖此参数 */ fullscreenable: true, ...bounds, webPreferences: { nodeIntegration: true, enableRemoteModule: true, contextIsolation: false, nodeIntegrationInWorker: true, }, }; if (isMac) { // 在Mac下要显式设置fullscreen为false,防止客户端全屏时进入课堂,课堂窗口也默认全屏, // 导致部分按钮失效,以及独立窗口打开异常 defaultElecteonWinOptions.fullscreen = false; } // 学生无需使用透明窗口(透明窗口问题多),减少影响 if (classInfo.userType !== 'listener') { defaultElecteonWinOptions.transparent = true; defaultElecteonWinOptions.backgroundColor = '#00ffffff'; } if (classInfo.topic) defaultElecteonWinOptions.title = classInfo.topic + ''; if (classOptions.topic && classOptions.classType === LESSON_TYPE.PREPARE_LESSONS) defaultElecteonWinOptions.title = classOptions.topic + ''; const _electeonWinOptions = electronWinOptions ? { ...defaultElecteonWinOptions, ...electronWinOptions, } : defaultElecteonWinOptions; /**---------------------------------------------------- 创建课堂/备课窗口-----------------------------------*/ const onClassWindowReady = (event) => { const classWindow = remote.BrowserWindow.fromId(currentWinId); classWindow.moveTop(); classWindow.focus(); logger.info(`课堂窗口ready,id:${currentWinId}`); if (onClassWindowReadyFn && currentWinId) onClassWindowReadyFn(currentWinId); }; const onClassFinished = (event, value) => { const meetingId = value?.meetingId; logger.info(`课堂已结束,id:${currentWinId}, meetingId is ${meetingId}`); if (onClassFinishedFn && currentWinId) onClassFinishedFn(meetingId); }; const onHtmlReady = (event, value) => { currentWebContentsId = value?.webContentId; logger.info(`课堂窗口 html ready,web contents id:${currentWebContentsId}`); if (currentWebContentsId) { ipcRenderer.sendTo(currentWebContentsId, CLASS_WINDOW_MESG_TYPE.INIT_GLOBAL_APPINFO, classOptionsObj); } }; const onSaveBoard = (event, value) => { logger.info(`保存板书:${JSON.stringify(value)}`); const cb = (value) => { if (currentWebContentsId) { ipcRenderer.sendTo(currentWebContentsId, CLASS_WINDOW_MESG_TYPE.RESP_SAVE_BOARD, value); } }; if (onSaveBoardFn && value) onSaveBoardFn(value, cb); }; const onOpenResourceCenter = (event) => { if (onOpenResourceCenterFn) onOpenResourceCenterFn(); }; const onGetExtFileName = async (event, requestId, ...args) => { if (onGetExtFileNameFn && args.length > 0) { const url = await onGetExtFileNameFn(...args); if (url) { if (currentWebContentsId) { ipcRenderer.sendTo(currentWebContentsId, CLASS_WINDOW_MESG_TYPE.RESP_GET_EXT_FILE_NAME, requestId, url); } } } }; const onGetPreParseFileName = async (event, requestId, ...args) => { if (onGetPreParseFileNameFn && args.length > 0) { const url = await onGetPreParseFileNameFn(...args); if (url) { ipcRenderer.sendTo(currentWebContentsId, CLASS_WINDOW_MESG_TYPE.RESP_GET_PRE_PARSE_FILE_NAME, requestId, url); } } }; const onReportIssues = (event, info) => { onReportIssuesFn?.(info); }; ipcRenderer.on(CLASS_WINDOW_MESG_TYPE.LIVE_WINDOW_READY, onClassWindowReady); ipcRenderer.on(CLASS_WINDOW_MESG_TYPE.ON_CLASS_FINISHED, onClassFinished); ipcRenderer.on(CLASS_WINDOW_MESG_TYPE.HTML_READY, onHtmlReady); ipcRenderer.on(CLASS_WINDOW_MESG_TYPE.SAVE_BOARD, onSaveBoard); ipcRenderer.on(CLASS_WINDOW_MESG_TYPE.OPEN_RESOURCE_CENTER, onOpenResourceCenter); ipcRenderer.on(CLASS_WINDOW_MESG_TYPE.GET_EXT_FILE_NAME, onGetExtFileName); ipcRenderer.on(CLASS_WINDOW_MESG_TYPE.GET_PRE_PARSE_FILE_NAME, onGetPreParseFileName); ipcRenderer.on(CLASS_WINDOW_MESG_TYPE.REPORT_ISSUES, onReportIssues); const url = require('url'); const htmlPath = (isMac ? url.format({ pathname: path.join(__dirname, '../index.html'), protocol: 'file:', slashes: true, }) : path.join(__dirname, '../index.html')) + `?openerId=${webContents.id}`; appProxy.createWindow( { path: htmlPath, debug: classOptions?.debug ?? false, maximize: autoMaximized, windowOptions: _electeonWinOptions, }, async (id) => { currentWinId = id; appProxy.addWinEventListener(id, 'closed', () => { logger.info(`课堂窗口关闭,id:${currentWinId}`); if (onClassWindowLeaveFn) onClassWindowLeaveFn(currentWinId); currentWinId = null; currentWebContentsId = null; ipcRenderer.removeListener(CLASS_WINDOW_MESG_TYPE.LIVE_WINDOW_READY, onClassWindowReady); ipcRenderer.removeListener(CLASS_WINDOW_MESG_TYPE.ON_CLASS_FINISHED, onClassFinished); ipcRenderer.removeListener(CLASS_WINDOW_MESG_TYPE.HTML_READY, onHtmlReady); ipcRenderer.removeListener(CLASS_WINDOW_MESG_TYPE.SAVE_BOARD, onSaveBoard); ipcRenderer.removeListener(CLASS_WINDOW_MESG_TYPE.OPEN_RESOURCE_CENTER, onOpenResourceCenter); ipcRenderer.removeListener(CLASS_WINDOW_MESG_TYPE.GET_EXT_FILE_NAME, onGetExtFileName); ipcRenderer.removeListener(CLASS_WINDOW_MESG_TYPE.GET_PRE_PARSE_FILE_NAME, onGetPreParseFileName); ipcRenderer.removeListener(CLASS_WINDOW_MESG_TYPE.REPORT_ISSUES, onReportIssues); }); }, ); return 0; } else { logger.error('获取remote、displayInfo 失败'); if (!remote) return ERROR_CODE.GET_REMOTE_FAIL; if (!displayInfo) return ERROR_CODE.GET_DISPLAY_INFO_FAIL; } } catch (error) { logger.error(`异常错误,${JSON.stringify(error)}`); return ERROR_CODE.COMMOM_ERROR; } } else { logger.error('非Electron环境'); return ERROR_CODE.NO_ELECTRON_ENVIRONMENT; } } /** * @param {classWindowType} liveClassWindowProps * @returns */ function createLiveClassWindow(liveClassWindowProps) { const classWindowProps = { ...liveClassWindowProps, classOptions: { ...liveClassWindowProps.classOptions, appType: 'liveclassSDK', classType: LESSON_TYPE.LIVECLASS, }, }; return createClassWindow(classWindowProps); } /** * @param {classWindowType} prepareClassWindowProps * @returns */ function createPrepareClassWindow(prepareClassWindowProps) { const classWindowProps = { ...prepareClassWindowProps, classOptions: { ...prepareClassWindowProps.classOptions, appType: 'prepareLessonSdk', meetingType: LESSON_TYPE.PREPARE_LESSONS, classType: LESSON_TYPE.PREPARE_LESSONS, userType: 'speaker', topic: prepareClassWindowProps.classOptions.topic ?? '备课课堂', }, }; return createClassWindow(classWindowProps); } /** 插入外部云盘里的文件,文件需要遵循特定的数据结构 */ /** * @typedef {Object} fileDataObj * @property {number} type 插入文件的格式,内容参考 FILE_TYPE * @property {string} [title] 文件名称,传入后会显示在文件窗口标题栏上,建议带上文件后缀名 * @property {string} [url] 公开的访问权限的文件全地址,建议https协议全地址 * @property {any[]} [info] 具体的文件信息,除备课外,内容都由用户自己定义 */ /** * @param {fileDataObj} fileData 文件数据 */ function insertObject(fileData) { try { const { ipcRenderer } = window.require('electron'); if (currentWebContentsId) ipcRenderer.sendTo(currentWebContentsId, CLASS_WINDOW_MESG_TYPE.INSERT_OBJECT, fileData); } catch (error) { logger.error(`insertObject error is: ${JSON.stringify(error)}}`); return ERROR_CODE.COMMOM_ERROR; } } /** * 更新严禁词列表 * @param {string[]} words 严禁词数组 */ function updateProhibitedWords(words) { try { const { ipcRenderer } = window.require('electron'); if (currentWebContentsId) ipcRenderer.sendTo(currentWebContentsId, CLASS_WINDOW_MESG_TYPE.UPDATE_SENSITIVE_WORDS, words || []); } catch (error) { logger.error(`updateProhibitedWords error is: ${JSON.stringify(error)}}`); } } function getVersion() { try { const packageJson = require('../package.json'); return packageJson.version; } catch (error) { logger.error(`getVersion error is: ${JSON.stringify(error)}}`); return ERROR_CODE.COMMOM_ERROR; } } /** 日志文件地址 */ function initLogConfig(logFilePath) { if (logFilePath) { logger.initLoggerFilePath(logFilePath); const { ipcRenderer } = require('electron'); ipcRenderer.send(RENDER_TO_MAIN_MESG_TYPE.PLASO_INIT_LOG_PATH, logFilePath); } } const PlasoElectronSdk = { createLiveClassWindow: createLiveClassWindow, createPrepareClassWindow: createPrepareClassWindow, getVersion: getVersion, initLogConfig: initLogConfig, insertObject: insertObject, updateProhibitedWords: updateProhibitedWords, FILE_TYPE: FILE_TYPE, }; module.exports = PlasoElectronSdk;