UNPKG

@plasosdk/plaso-electron-sdk

Version:

伯索课堂Electron SDK

785 lines (638 loc) 32 kB
# Plaso SDK for Electron ## 目录 <!-- toc --> - [环境支持](#环境支持) - [安装](#安装) - [electron-builder打包特别说明](#electron-builder打包特别说明) - [使用](#使用) - [在主进程中使用](#在主进程中使用) - [在渲染进程中使用](#在渲染进程中使用) - [打开实时课堂](#打开实时课堂) - [打开备课课堂](#打开备课课堂) - [打开实时课堂/备课课堂通用参数说明](#打开实时课堂备课课堂通用参数说明) - [API参考](#api参考) - [资料中心](#资料中心) - [播放历史课堂](#播放历史课堂) - [注意点](#注意点) - [Q&A](#qa) <!-- tocstop --> ## 环境支持 - MacOS:x86-64、arm64 - Windows:ia32、x64 - Electron:14.0.0~22.3.27 ## 安装 **安装 @electron/remote** ```shell npm install @electron/remote --global-style --legacy-peer-deps ``` **安装 plaso-electron-sdk** ```shell npm install @plasosdk/plaso-electron-sdk --global-style ``` **<font color=red>注意: 不同平台需要单独安装,尤其是MacOS,请分别在Intel芯片和Apple芯片的电脑上安装</font>** ## electron-builder打包特别说明 ### 文件拷贝相关配置 > ⚠️ 该配置在升级到 1.3.12 以后有所调整 agora-v4包在plaso-electron-sdk内部作为第三方依赖存在,由于electron-builder拷贝应用代码到app目录时对node_modules文件夹的特殊处理和多级符号链接拷贝上的bug,不能够走[files](https://www.electron.build/configuration#files)配置项去对agora-v4包进行拷贝,而应走[extraResources](https://www.electron.build/configuration#extraresources)配置项去拷贝。 同时,由于这个包是通过extraResources进行拷贝的,需要在签名脚本中额外配置这个包的路径。 <details open> <summary><strong>版本 >= 1.3.12 的配置</strong></summary> ```json { "files": [ "!node_modules/@plasosdk/plaso-electron-sdk/lib/agora-v4/*" ], "extraResources": [ { "from": "node_modules/@plasosdk/plaso-electron-sdk/lib/agora-v4", "to": "app/node_modules/@plasosdk/plaso-electron-sdk/lib/agora-v4", "filter": [ "**/*" ] }, { "from": "node_modules/@plasosdk/plaso-electron-sdk/lib/agora-v4/node_modules", "to": "app/node_modules/@plasosdk/plaso-electron-sdk/lib/agora-v4/node_modules", "filter": [ "**/*" ] } ] } ``` </details> <details> <summary><strong>版本 1.3.7 ~ 1.3.11 的配置</strong></summary> 该版本已知问题:给项目安装新依赖时,可能会导致agora-v4包被npm清理掉 ```json { "extraResources": [ { "from": "node_modules/@plasosdk/plaso-electron-sdk/node_modules/agora-electron-sdk-v4", "to": "app/node_modules/@plasosdk/plaso-electron-sdk/node_modules/agora-electron-sdk-v4", "filter": [ "**/*" ] }, { "from": "node_modules/@plasosdk/plaso-electron-sdk/node_modules/agora-electron-sdk-v4/node_modules", "to": "app/node_modules/@plasosdk/plaso-electron-sdk/node_modules/agora-electron-sdk-v4/node_modules", "filter": [ "**/*" ] } ] } ``` </details> <details> <summary><strong>版本 <= 1.3.6 的配置</strong></summary> 该版本已知问题:给项目安装新依赖时,可能会导致agora-v4包被npm清理掉 ```json { "extraResources": [ { "from": "node_modules/agora-electron-sdk-v4", "to": "app/node_modules/agora-electron-sdk-v4", "filter": ["**/*"] }, { "from": "node_modules/agora-electron-sdk-v4/node_modules", "to": "app/node_modules/agora-electron-sdk-v4/node_modules", "filter": ["**/*"] } ] } ``` </details> ### 关闭asar 需要关闭asar,否则SDK会无法加载到node_modules。关闭方式详见electron-buider[官方文档](https://www.electron.build/configuration#asar)。 ```json { "asar": false, // 关闭asar } ``` ### MacOS赋予文件执行权限 打MacOS的包时有几个需要额外进行授权的可执行文件。建议通过额外的脚本来执行。 下面的可执行文件是在安装@plasosdk/plaso-electron-sdk时动态下载的,因此授权的脚本需要保证是在文件下载之后。 ```shell // 截图会用到 chmod +x node_modules/@plasosdk/plaso-electron-sdk/lib/flameshot.app // 桌面共享会用到 chmod +x node_modules/@plasosdk/plaso-electron-sdk/lib/PlasoALD/PlasoALD.scpt ``` ### 验证 MacOS打包完毕后,建议安装打好的包,验证上述操作是否成功。核对清单: - 上课测试截图功能是否正常,截图正常说明授权正确。 - 上课测试桌面共享功能,使用屏幕共享,并在浏览器里面播放一段视频,结束课堂后检查回放中能否听到桌面共享那一段播放的视频的声音,能听到说明授权正确。 ## 使用 ### 在主进程中使用 需要在主进程加载 `@plasosdk/plaso-electron-sdk` 依赖包 ```ts // electron 版本>=14.0.0 时:需要在主进程里 初始化、启动 remote const remoteMain = require('@electron/remote/main'); remoteMain.initialize(); remoteMain.enable(mainWindow.webContents); // mainWindow: 主进程通过loadURL加载的那个渲染进程窗口 // plaso-electron-sdk中依赖@electron/remote,需要通过 initRemoteMain方法 传入 remoteMain const { initRemoteMain } = require('@plasosdk/plaso-electron-sdk'); initRemoteMain(remoteMain); ``` ### 在渲染进程中使用 <a id="open-sdk-window-params"></a> **打开实时课堂/备课课堂方法入参类型定义** ```ts interface CreateClassWindowPamras { /** * 属性说明详见下方classOptions,打开实时课堂时为ILiveClassOptions,备课课堂为IPrepareClassOptions */ classOptions: ILiveClassOptions | IPrepareClassOptions; electronWinOptions?: Electron.BrowserWindowConstructorOptions; onClassWindowReadyFn?: (winId: number) => void onClassWindowLeaveFn?: (winId: number) => void onClassFinishedFn?: (meetingId: string) => void; onSaveBoardFn?: ( params: { fileInfo: FileParams[]; fileName?: string; }, callback: (result: boolean) => void, ) => void; onOpenResourceCenterFn?: () => void; onGetExtFileNameFn?: (info: any[], ...args: any[]) => Promise<string>; onGetPreParseFileNameFn?: (info: any[], option: { suffix: string }) => Promise<string>; onReportIssuesFn?: (Issues: any) => void; } ``` #### 打开实时课堂 SDK打开一个新的`BrowserWindow`来加载实时课堂UI界面,示例代码如下: ```ts const PlasoElectronSdk = window.require('@plasosdk/plaso-electron-sdk'); const createLiveClassWindowParams: CreateClassWindowPamras = { classOptions: { query } }; PlasoElectronSdk.createLiveClassWindow(createLiveClassWindowParams); ``` ##### createLiveClassWindow参数说明 ###### classOptions ```ts interface ILiveClassOptions { /** 进入课堂必须的签名字符串 */ query: string; /** 当前进入课堂用户的头像,取值为https全地址 */ displayAvatarUrl?: string; /** 课堂中的成员,会在成员列表中呈现 */ classMembers?: UserInfo[]; /** 是否启用降噪 */ enableENC?: boolean; /** 是否启用3A */ enableRTC3A?: boolean; /** 是否启用新桌面共享 */ enableLiveNewShare?: boolean; /** 是否启用触屏设备上的新桌面共享 */ enableLiveNewShareInTouch?: boolean; /** 是否启用部分屏幕区域共享 */ enableLiveNewShareRegion?: boolean; /** 是否启用签到 */ enableLiveSign?: boolean; /** 是否启用资料中心(云盘) */ supportShowResourceCenter?: boolean; /** 是否支持保存板书 */ supportSaveBoard?: boolean; /** 是否启用摄像头常驻 */ residentCamera?: boolean; /** 是否启用日志上传。默认启用,进入和退出课堂时自动将日志上传至伯索服务器。 */ enableUploadLog?: boolean; } interface UserInfo { /** 用户名,唯一标识 */ loginName: string; /** 用户昵称 */ name: string; /** 用户角色 */ upimeRole: 'speaker' | 'assistant' | 'listener'; /** 用户头像 */ displayAvatarUrl?: string; } ``` | <span style="white-space: nowrap;">参数名称</span> | <span style="white-space: nowrap;">是否必填</span> | <span style="white-space: nowrap;">类型</span> | <span style="white-space: nowrap;">参数描述</span> | | --- | --- | --- | --- | | query | 是 | string | 带签名的字符串,具体拼接逻辑见下文[query属性说明](#query) | | displayAvatarUrl | 否 | string | 用户头像地址。不传默认使用用户昵称作为头像。 | | classMembers | 否 | UserInfo[] | 成员列表中显示的人员,最多支持 2000 人。**人员超过2000人需要联系伯索平台进行额外申请**。 | | enableRTC3A | 否 | boolean | 是否启用3A:回音消除、噪声抑制、自动增益控制。默认启用。 | | enableENC | 否 | boolean | 是否启用降噪控制,开启后设置界面可以设置`基础降噪`或`增强降噪`。默认启用。 | | enableLiveNewShare | 否 | boolean | 是否启用新桌面共享。启用后共享`屏幕`/`部分屏幕区域`时会对课堂窗口做透明化处理,需要选中工具栏上的`交互模式`工具来交互桌面应用,可能会触发`Electron`长时间透传鼠标事件导致的透传异常问题,谨慎使用。默认不启用。 | | enableLiveNewShareInTouch | 否 | boolean | 是否在触屏设备上启用新桌面共享,启用后通过切换`演示模式`和`互动模式`来交互桌面应用和课堂中的工具。默认不启用。 | | enableLiveNewShareRegion | 否 | boolean | 是否启用`部分屏幕区域`共享。 | | enableLiveSign | 否 | boolean | 是否启用签到,启用后工具箱中显示`签到`按钮。默认不启用。 | | supportShowResourceCenter | 否 | boolean | 是否启用`资料中心(云盘)`,启用后在工具栏显示`资料中心(云盘)`按钮,此按钮需要配合`onOpenResourceCenterFn`回调一起使用。默认不启用。 | | supportSaveBoard | 否 | boolean | 是否启用保存板书,启用后工具箱中显示`保存当页板书`按钮,需要配合云盘使用。默认不启用。 | | residentCamera | 否 | boolean | 是否启用常驻摄像头:只对学生或游客生效。启用后学生进入课堂后摄像头常驻开启,没有开启摄像头权限时也会开启。默认不启用。 | | enableUploadLog | 否 | boolean | 是否启用日志自动上传。默认启用,退出课堂时 SDK 会自动将日志文件压缩并上传至伯索日志服务器,便于问题排查。传入 `false` 可关闭。 | <a id="query"></a> **query属性说明** query本质上是根据`IQueryParams`对象中的字段生成带签名的字符串,query示例如下: ```ts const query = 'appId=plaso&appType=liveclassSDK&d_dimension=1280x720&enableNewClassExam=1&loginName=t_1&mediaType=video&meetingId=test_1742442362&meetingType=public&signature=A226198904A392579B98987FB4CD5478AB3F5587&userName=%E8%80%81%E5%B8%881&userType=speaker&validBegin=1742442364&validTime=99999' ``` ```ts interface IQueryParams { appId: string; validBegin: number; validTime: number; mediaType: string; meetingType: string; meetingId: string; userType: string; loginName: string; userName: string; d_dimension: string; topic?: string; endTime?: number; onlineMode?: number; d_delayEndTimes?: number; d_delayEnd?: number; d_enableAvatarFreeScale?: number; d_enableObjectEraser?: number; d_vote?: number; d_sharpness?: number; isNewMT?: number; enableNewClassExam?: number; d_enableReRecording?: number; d_restrictAssistantPerm?: number; } ``` | <span style="white-space: nowrap;">字段名称</span> | <span style="white-space: nowrap;">是否必填</span> | <span style="white-space: nowrap;">类型</span> | <span style="white-space: nowrap;">字段描述</span> | | --- | --- | --- |--- | | appId | 是 | string | 在申请接入时,伯索平台给予的 appId | | validBegin | 是 | number | 签名query生效的起始时间,Unix Epoch 时间戳,单位为秒 | | validTime | 是 | number | 签名query的有效期,从`validBegin`开始计算,单位为秒 | | mediaType | 是 | string | 媒体类型,取值:<ul><li>audio: 音频课堂</li><li>video: 视频课堂</li></ul> | meetingType | 是 | string | 课堂类型,固定值为`public` | | meetingId | 是 | string | 课堂ID,唯一标识该课堂;使用ASSIIC字符,不得包含/,\,空格等;长度在40字节以内的字符串。 | | userType | 是 | string | 用户角色类型,取值:<ul><li>speaker: 课堂的主讲者,有控制其他listener是否可板书/发言的权限。课堂中只能有一个主讲</li><li>assistant:助教,辅助主讲授课的角色,在课堂中的权限与主讲基本一致。课堂中可以有多个助教</li><li>listener:听众,可以理解为学生</li></ul> | | loginName | 是 | string | 唯一标识该用户的id,不能为空,相同的loginName进入课堂时,后面进入的会使前面进入的登出 | | userName | 是 | string | 用户昵称,头像缺省时会显示 | | d_dimension | 是 | string | 固定值为`1280x720`,定义界面尺寸为16:9界面 | | topic | 否 | string | 课堂名称,在标题栏上显示 | | endTime | 否 | number | 课堂结束时间,格式为 Unix Epoch 时间戳,单位为秒 | | onlineMode | 否 | number | 当mediaType为`video`时生效,表示最大能开启的`listener`的摄像头的个数,取值:<ul><li>1</li><li>6</li><li>12</li></ul>默认值为6 | | d_delayEndTimes | 否 | number | 单节课最大延时下课次数,取值:<ul><li>1</li><li>2</li><li>3</li><li>4</li></ul>默认没有延时 | | d_delayEnd | 否 | number | 单次延时时间,单位为秒,取值:<ul><li>5 * 60</li><li>10 * 60</li><li>20 * 60</li><li>30 * 60</li></ul>默认20分钟 | | d_enableAvatarFreeScale | 否 | number | 是否开启头像任意比例缩放,仅上课中各端同步,录制头像时历史课堂不支持课堂调整的任意比例,取值:<ul><li>0:关闭</li><li>1:开启</li></ul>默认关闭 | | d_enableObjectEraser | 否 | number | 是否启用新版板书,新版板书的橡皮擦支持对象擦除,传入大于0的值启用新版板书。取值:<ul><li>0: 不启用,橡皮功能为点擦</li><li>1: 支持对象擦除手写</li><li>3: 支持对象擦除手写+文本</li><li>5: 支持对象擦除手写+图形</li><li>7: 支持对象擦除手写+文本+图形</li></ul>默认不启用 | | d_vote | 否 | number | 是否启用投票工具。取值:<ul><li>0:关闭</li><li>1:开启</li></ul>默认关闭 | | d_sharpness | 否 | number | 当mediaType为`video`时生效,表示摄像头画面的清晰度。取值:<ul><li>10: 360p</li><li>20: 720p</li><li>30: 1080p</li></ul>默认值为10 | | isNewMT | 否 | number | 是否支持移动授课模式,建议传1 | | recordAvator | 否 | string | 传入老师或助教的`loginName`表示录制对应人的头像,传入`recordScreen`时SDK会忽略此参数 | | recordScreen | 否 | string | 传入`screen`表示录制屏幕(当前仅支持录制老师屏幕)| | enableNewClassExam | 否 | number | 是否启用新版随堂测,取值:<ul><li>0: 不启用</li><li>1: 启用选择题</li><li>2: 启用填空题</li><li>3: 启用选择题+填空题</li></ul>默认不启用,建议传3 | | d_enableReRecording | 否 | number | 是否允许老师/助教重新录制,取值:<ul><li>0: 不允许老师和助教重新录制</li><li>1: 仅允许老师重新录制</li><li>2: 仅允许助教重新录制</li><li>3: 允许老师和助教重新录制</li></ul>默认值为3 | | d_restrictAssistantPerm | 否 | number | 是否启用限制助教权限,取值:<ul><li>0: 不启用</li><li>1: 启用</li></ul>默认不启用 | **根据 queryParams 对象生成签名字符串** > 注意:为了安全和各端签名统一,建议将签名的计算放在服务端,前端通过接口获取带签名的query 将queryParams传入签名函数生成签名,签名示例参考:[签名示例](https://open.plaso.cn/doc-6285173?nav=01HEQ5Y5RXKMCPBPF6S8T3VK56) 获取`signature`后将`signature`加到`queryParams`中作为一个字段。 ```ts queryParams.signature = signature; ``` 获取完整的`queryParams`后,遍历`queryParams`生成`query`字符串,每个字段的值用`encodeURIComponent`编码。完整流程示例如下: ```ts function genSignature(params) { return 'xxx'; } function genQuery(params) { const keys = Object.keys(params).sort(); const res = []; for (const key of keys) { res.push(key + '=' + encodeURIComponent(params[key])); } return res.join('&'); } const queryParams = { appId: 'xxx'; validBegin: 175645206; validTime: 3600; mediaType: 'video'; meetingType: 'public'; meetingId: 1234; userType: 'speaker'; loginName: 'hello'; userName: 'world'; d_dimension: '1280x720'; }; const signature = genSignature(queryParams); queryParams.signature = signature; const query = genQuery(queryParams); ``` #### 打开备课课堂 SDK打开一个新的`BrowserWindow`来加载备课课堂UI界面,示例代码如下: ```ts const PlasoElectronSdk = window.require('@plasosdk/plaso-electron-sdk'); const createPrepareClassWindowParams: CreateClassWindowPamras = { classOptions: { loginName: 'hello', userName: 'world' } }; PlasoElectronSdk.createPrepareClassWindow(createPrepareClassWindowParams); ``` ##### createPrepareClassWindow参数说明 ###### classOptions ```ts interface IPrepareClassOptions { loginName: string; userName: string; displayAvatarUrl?: string; topic?: string; d_enableObjectEraser?: number; } ``` | <span style="white-space: nowrap;">参数名称</span> | <span style="white-space: nowrap;">是否必填</span> | <span style="white-space: nowrap;">类型</span> | <span style="white-space: nowrap;">参数描述</span> | | --- | --- | --- | --- | | loginName | 是 | string | 唯一标识该用户的id,不能为空,相同的loginName进入课堂时,后面进入的会使前面进入的登出 | | userName | 是 | string | 用户昵称,头像缺省时会显示 | | displayAvatarUrl | 否 | string | 用户头像地址。不传默认使用用户昵称作为头像。 | | d_enableObjectEraser | 否 | number | 是否启用新版板书,新版板书的橡皮擦支持对象擦除,传入大于0的值启用新版板书。取值:<ul><li>0: 不启用,橡皮功能为点擦</li><li>1: 支持对象擦除手写</li><li>3: 支持对象擦除手写+文本</li><li>5: 支持对象擦除手写+图形</li><li>7: 支持对象擦除手写+文本+图形</li></ul>默认不启用 | #### 打开实时课堂/备课课堂通用参数说明 ##### electronWinOptions > **即将弃用**: 不推荐传入,SDK内部默认设置了一些窗口参数,为了保证最佳体验,不要传入此参数。 Electron的窗口参数,详情参考 [Electron官方文档](https://www.electronjs.org/zh/docs/latest/api/browser-window#new-browserwindowoptions)。 ```ts type electronWinOptions = Electron.BrowserWindowConstructiorOptions; ``` ##### onClassWindowReadyFn ```ts // 课堂窗口打开渲染成功后的回调,回调参数为 窗口id type onClassWindowReadyFn = (winId: number) => void; ``` ##### onClassWindowLeaveFn ```ts // 课堂窗口关闭后的回调,回调参数为 窗口id type onClassWindowLeaveFn = (winId: number) => void; ``` ##### onClassFinishedFn ```ts // 课堂结束后的回调,回调参数为 课堂的meetingId type onClassFinishedFn = (meetingId: string) => void; ``` ##### onSaveBoardFn **注意:** 1、**filePath 对应的文件资源,用户保存在自己的云端时,需要把 本次保存的备课文件的 相关资源放在同一特定目录下** 2、**每次保存生成的备课文件 都放在一个新的目录下,不同的备课文件不能共用一个目录** 3、**备课保存的资源文件名 不能更改,info.pb 是固定的文件名** ```ts // 保存板书方法,具体的保存逻辑由外部实现,取消保存板书时,callback传false, 不然传true type FileParams = { /** 备课相关资源文件的本地地址*/ filePath: string[]; /** 备课文件 类型*/ fileType: 'png' | 'pb'; }; type onSaveBoardFn = ( params: { fileInfo: FileParams[]; fileName?: string; }, callback: (result: boolean) => void, ) => void; ``` ##### onOpenResourceCenterFn 详见[云盘接入](https://open.plaso.cn/doc-6285183#showresourcecenter) ```ts // 通知外部用户打开自己的资料中心,资料中心的具体ui和逻辑由外部用户自己实现 // 推荐:通过onClassWindowReadyFn回调的winId拿到课堂窗口实例,用户的云盘通过一个BrowserWindow // 和课堂窗口组成父子窗口,云盘是子窗口,并且云盘窗口打开时保持置顶,这样来保证云盘打开时始终可见并且跟随课堂窗口。 type onOpenResourceCenterFn = () => void; ``` ##### onGetExtFileNameFn 详见[云盘接入](https://open.plaso.cn/doc-6285183#getextfilename) ```ts type onGetExtFileNameFn = (info: any, ...args: any[]) => Promise<string>; ``` ##### onGetPreParseFileNameFn 详见[云盘接入](https://open.plaso.cn/doc-6285183#getpreparsefilename) ```ts type onGetPreParseFileNameFn = (info: any, option: { suffix: string }) => Promise<string>; ``` ##### onReportIssuesFn 传入该函数后,设置面板将会显示“异常问题上报”入口。用户输入异常问题信息后,在点击上报按钮时触发该回调,返回异常信息。 ```ts interface Issues { /** 上报时间 */ time: string; /** 用户 loginName */ id: string; /** 用户 name */ name: string; /** 异常信息 */ bugDescription: string; } type onReportIssuesFn = (info: any) => void; ``` #### API参考 ##### PlasoElectronSdk.initLogConfig 该方法用于设置SDK日志位置,SDK崩溃时会在同级目录下生成`reports`文件夹存储 dump,在调用`createLiveClassWindow`前设置,不调用SDK会在默认位置写入日志,默认位置如下: ```js // 获取日志路径的代码如下 require('path').join(require('electron').app.getPath('userData'), 'P403FileTemp'); // Windows:C:\Users\${userName}\AppData\Roaming\${appName}\P403FileTemp // Mac:/Users/${userName}/Application\ Support/${appName}/P403FileTemp ``` 参考示例: ```ts // 代码示例 const PlasoElectronSdk = window.require('@plasosdk/plaso-electron-sdk'); const logDir = 'C:/Users/userName/Desktop/electronDemo/electron12.0.18_x32/resources/app'; PlasoElectronSdk.initLogConfig(logDir); ``` > 重要:SDK 默认会在退出课堂后自动上传日志并清理过期文件(7天前),用户无需自行实现上传逻辑。如需关闭自动上传(设置 `enableUploadLog: false`),则需要对日志目录做定期清理和及时上传日志到自己的服务器或OSS。推荐在退出SDK后做一次清理和上传,建议清理创建时间超过7天的日志即可。 相关代码示例如下: ```ts import fs from 'fs'; import path from 'path'; /** * 清理创建时间超过7天的日志 * @param logDir 日志目录 */ function clearExpireLogs(logDir: string) { try { const logFile = fs.readdirSync(logDir).filter((fileName) => /\.(log|dmp|ips|diag)/.test(path.extname(fileName))); logFile.forEach((fileName) => { const fileInfo = fs.statSync(path.join(logDir, fileName)); const fileDate = new Date(fileInfo.birthtime).setHours(0, 0, 0, 0).valueOf(); const currentDate = new Date().setHours(0, 0, 0, 0); if (currentDate - fileDate > 7 * 24 * 60 * 60 * 1000) { fs.unlinkSync(path.join(logDir, fileName)); } }); } catch (e) { console.error('clear expire log error', e); } } /** * 收集崩溃日志 * @param logDir 日志目录 * @param pendingUploadDir 待上传目录 */ function collectCrashLogs(logDir: string, pendingUploadDir: string) { const platform = require('os').platform(); switch (platform) { case 'darwin': const newDumpFolder = path.join(logDir, '/new/'); const pendingDumpFolder = path.join(logDir, '/pending/'); const completedDumpFolder = path.join(logDir, '/completed/'); const diagnosticReports = path.normalize('/Library/Logs/DiagnosticReports'); collectDumpFiles(newDumpFolder); collectDumpFiles(pendingDumpFolder); collectDumpFiles(completedDumpFolder); collectSystemLogs(diagnosticReports); break; case 'win32': const reportsDumpFolder = path.join(logDir, '/reports/'); collectDumpFiles(reportsDumpFolder); break; default: break; } function collectSystemLogs(folder: string) { try { const files = fs.readdirSync(folder); const todayDate = new Date(); const formatDate = `${todayDate.getFullYear()}-${(todayDate.getMonth() + 1).toString().padStart(2, '0')}-${todayDate .getDate() .toString() .padStart(2, '0')}`; files.forEach((name) => { const reg = new RegExp(`Electron Helper.*${formatDate}`); if (name && name.match(reg)) { fs.copyFileSync(path.join(folder, name), path.join(pendingUploadDir, name)); } }); } catch (e) { console.error(e); } } function collectDumpFiles(folder: string) { try { const files = fs.readdirSync(folder); files.forEach((name) => { if (name) { fs.copyFileSync(path.join(folder, name), path.join(pendingUploadDir, name)); fs.unlinkSync(path.join(folder, name)); } }); } catch (e) { console.error(e); } } } /** * 收集业务日志 * @param logDir 日志目录 * @param pendingUploadDir 待上传目录 */ function collectBussinessLogs(logDir: string, pendingUploadDir: string) { const logFiles = fs.readdirSync(logDir).filter((fileName) => /\.(log|dmp|ips|diag)/.test(path.extname(fileName))); logFiles.forEach((fileName) => { fs.copyFileSync(path.join(logDir, fileName), path.join(pendingUploadDir, fileName)); }); } /** * 收集日志 * @param logDir 日志目录 * @param pendingUploadDir 待上传目录 */ function collectLogs(logDir: string, pendingUploadDir: string) { collectCrashLogs(logDir, pendingUploadDir); collectBussinessLogs(logDir, pendingUploadDir); } /** * 压缩日志 * @param pendingUploadDir 待上传目录 */ function zipLogs(pendingUploadDir: string): Promise<string> { return new Promise((resolve) => { const zipLogPath = path.join(pendingUploadDir, 'xxx.zip'); // 这边的xxx.zip取名时建议将用户名以及上传时间拼接上去,方便查询 // TODO: 这边做压缩,压缩完毕后将zip文件路径返回 resolve(zipLogPath); }); } /** * 上传日志到服务器 * @param filePath zip文件路径 */ function uploadToServer(filePath: string): Promise<boolean> { return new Promise((resolve) => { // TODO: 这边做上传 resolve(true); }); } /** * 上传日志 * @param logDir 日志目录 */ export function uploadLogs(logDir: string): Promise<boolean> { return new Promise(async (resolve) => { try { // 上传前先清理过期的日志,避免垃圾日志太多 clearExpireLogs(logDir); // 创建待上传目录 const pendingUploadDir = path.join(logDir, 'pendingUpload'); if (fs.existsSync(pendingUploadDir)) { fs.rmdirSync(pendingUploadDir, { recursive: true }); } fs.mkdirSync(pendingUploadDir); // 将日志复制到待上传目录 collectLogs(logDir, pendingUploadDir); // 压缩待上传目录 const zipName = await zipLogs(pendingUploadDir); // 将压缩后的日志上传到服务器 await uploadToServer(zipName); // 上传完成后删除临时文件 fs.unlinkSync(zipName); fs.rmdirSync(pendingUploadDir, { recursive: true }); resolve(true); } catch (error) { resolve(false); } }); } ``` ##### PlasoElectronSdk.getVersion 返回包的版本,格式:x.x.x ```ts type getVersion = () => string; ``` ##### PlasoElectronSdk.createLiveClassWindow **创建实时课堂窗口** ```ts function createLiveClassWindow(params: CreateClassWindowPamras): void; ``` 参数详见[打开实时课堂/备课课堂方法入参类型定义](#open-sdk-window-params) ##### PlasoElectronSdk.createPrepareClassWindow **创建备课课堂窗口** ```ts function createLiveClassWindow(params: CreateClassWindowPamras): void; ``` 参数详见[打开实时课堂/备课课堂方法入参类型定义](#open-sdk-window-params) <a id="insert-object"></a> ##### PlasoElectronSdk.insertObject 用户从自己的资料中心往**实时课堂/备课课堂**插入WORD/EXCEL/PPT/PDF、音视频、备课文件等,详见[云盘接入](https://open.plaso.cn/doc-6285183#insertobject) ##### PlasoElectronSdk.updateProhibitedWords 更新严禁词 - 设置严禁词后,消息发送时会受到严禁词列表的限制。 - 严禁词功能只对upimeRole为listener的角色有效。 - 该函数需在课堂准备好后调用(onClassWindowReadyFn回调触发后,即代表课堂准备好) ```ts type updateProhibitedWords = (words: string[]) => void; ``` ## 资料中心 - 进课堂时,对象 `classOptions.supportShowResourceCenter` 需要是 true - 资料中心的接入还涉及 `onOpenResourceCenterFn`、`PlasoElectronSdk.insertObject`、`onGetExtFileNameFn`、`onGetPreParseFileNameFn` 几个函数,具体用法详见[云盘接入](https://open.plaso.cn/doc-6285183)的 `showResourceCenter`、`insertObject`、`getExtFileName`、`getPreParseFileName` 章节 ## 播放历史课堂 1、参考文档: **[播放器SDK-Web播放器](https://open.plaso.cn/doc-6285192)** 2、当课堂中insertObject使用了`info`属性时,SDK内部需要外部传入`getExtFileName`,因此历史课堂需要使用jssdk的接入方式, 详见:**[Web播放器-jssdk接入](https://open.plaso.cn/doc-6285192#jssdk%E6%8E%A5%E5%85%A5)** 3、当课堂中insertObject插入了预解析资源时,SDK内部需要外部传入`getPreParseFileName`,因此历史课堂需要使用jssdk的接入方式,同上。 ## 注意点1)用户的课堂外主窗口销毁时需要销毁课堂窗口 (2)**基于 此包封装新包时**:注意 @electron/remote 这个包的位置需要 和新包处于同级目录,需要把 和该包同级的@electron/remote 移到新包的同级目录处 (3)确保 仅最后的 node_moudles 的顶层有 @electron/remote ## Q&A ### 进课堂遇到闪退/崩溃怎么办? - 检查应用程序路径中是否存在中文,路径中不应出现中文</br> 可通过在主进程中,用`console.log(require.resolve('@plasosdk/plaso-electron-sdk'))`来检查路径 - 如果是Mac,进课堂会需要麦克风权限。打开系统设置->隐私与安全性->麦克风,查看列表里你的应用程序是否被授权。