@plasosdk/plaso-electron-sdk
Version:
伯索课堂Electron SDK
785 lines (638 loc) • 32 kB
Markdown
# 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,进课堂会需要麦克风权限。打开系统设置->隐私与安全性->麦克风,查看列表里你的应用程序是否被授权。