
Research
Malicious npm Packages Impersonate Flashbots SDKs, Targeting Ethereum Wallet Credentials
Four npm packages disguised as cryptographic tools steal developer credentials and send them to attacker-controlled Telegram infrastructure.
@plasosdk/plaso-electron-sdk
Advanced tools
MacOS | Windows | Electron |
---|---|---|
x86 | arm64 | ia32 | x64 | 14.0.0~22.3.27 |
安装 @electron/remote
npm install @electron/remote --global-style --legacy-peer-deps
安装 plaso-electron-sdk
npm install @plasosdk/plaso-electron-sdk --global-style
注意: 不同平台需要单独安装,尤其是MacOS,请分别在Intel芯片和Apple芯片的电脑上安装
使用electron-builder打包时,需要对工程的node_modules中的agora-electron-sdk-v4进行特殊处理,该包是在安装@plasosdk/plaso-electron-sdk时动态安装的,因此需要通过配置extraResources单独拷贝。配置入如下
"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": ["**/*"]
}
]
同时,由于这个包是通过extraResources进行拷贝的,需要在签名脚本中额外配置这个包的路径。
另外在打包MacOS的包时有几个需要额外进行授权的可执行文件。建议通过额外的脚本来执行。下面的可执行文件是在安装@plasosdk/plaso-electron-sdk时动态下载的,因此授权的脚本需要保证是在文件下载之后。
// 截图会用到
chmod +x node_modules/@plasosdk/plaso-electron-sdk/lib/flameshot
// 桌面共享会用到
chmod +x node_modules/@plasosdk/plaso-electron-sdk/lib/PlasoALD/PlasoALD.scpt
MacOS打包完毕后,建议安装打好的包后自行验证上述的操作时候生效了。
需要在主进程加载 @plasosdk/plaso-electron-sdk
依赖包
// 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);
打开实时课堂/备课课堂方法入参类型定义
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?: (...args: any[]) => Promise<string>;
}
SDK打开一个新的BrowserWindow
来加载实时课堂UI界面,示例代码如下:
const PlasoElectronSdk = window.require('@plasosdk/plaso-electron-sdk');
const createLiveClassWindowParams: CreateClassWindowPamras = { classOptions: { query } };
PlasoElectronSdk.createLiveClassWindow(createLiveClassWindowParams);
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;
}
interface UserInfo {
/** 用户名,唯一标识 */
loginName: string;
/** 用户昵称 */
name: string;
/** 用户角色 */
upimeRole: 'speaker' | 'assistant' | 'listener';
/** 用户头像 */
displayAvatarUrl?: string;
}
参数名称 | 是否必填 | 类型 | 参数描述 |
---|---|---|---|
query | 是 | string | 带签名的字符串,具体拼接逻辑见下文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 | 是否启用常驻摄像头:只对学生或游客生效。启用后学生进入课堂后摄像头常驻开启,没有开启摄像头权限时也会开启。默认不启用。 |
query本质上是根据IQueryParams
对象中的字段生成带签名的字符串,query示例如下:
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'
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;
}
字段名称 | 是否必填 | 类型 | 字段描述 |
---|---|---|---|
appId | 是 | string | 在申请接入时,伯索平台给予的 appId |
validBegin | 是 | number | 签名query生效的起始时间,Unix Epoch 时间戳,单位为秒 |
validTime | 是 | number | 签名query的有效期,从validBegin 开始计算,单位为秒 |
mediaType | 是 | string | 媒体类型,取值:
|
meetingType | 是 | string | 课堂类型,固定值为public |
meetingId | 是 | string | 课堂ID,唯一标识该课堂;使用ASSIIC字符,不得包含/,,空格等;长度在40字节以内的字符串。 |
userType | 是 | string | 用户角色类型,取值:
|
loginName | 是 | string | 唯一标识该用户的id,不能为空,相同的loginName进入课堂时,后面进入的会使前面进入的登出 |
userName | 是 | string | 用户昵称,头像缺省时会显示 |
d_dimension | 是 | string | 固定值为1280x720 ,定义界面尺寸为16:9界面 |
topic | 否 | string | 课堂名称,在标题栏上显示 |
endTime | 否 | number | 课堂结束时间,格式为 Unix Epoch 时间戳,单位为秒 |
onlineMode | 否 | number | 当mediaType为video 时生效,表示最大能开启的listener 的摄像头的个数,取值:
|
d_delayEndTimes | 否 | number | 单节课最大延时下课次数,取值:
|
d_delayEnd | 否 | number | 单次延时时间,单位为秒,取值:
|
d_enableAvatarFreeScale | 否 | number | 是否开启头像任意比例缩放,仅上课中各端同步,录制头像时历史课堂不支持课堂调整的任意比例,取值:
|
d_enableObjectEraser | 否 | number | 是否启用新版板书,新版板书的橡皮擦支持对象擦除,传入大于0的值启用新版板书。取值:
|
d_vote | 否 | number | 是否启用投票工具。取值:
|
d_sharpness | 否 | number | 当mediaType为video 时生效,表示摄像头画面的清晰度。取值:
|
isNewMT | 否 | number | 是否支持移动授课模式,建议传1 |
recordAvator | 否 | string | 传入老师或助教的loginName 表示录制对应人的头像,传入recordScreen 时SDK会忽略此参数 |
recordScreen | 否 | string | 传入screen 表示录制屏幕(当前仅支持录制老师屏幕) |
enableNewClassExam | 否 | number | 是否启用新版随堂测,取值:
|
d_enableReRecording | 否 | number | 是否允许老师/助教重新录制,取值:
|
根据 queryParams 对象生成签名字符串
注意:为了安全和各端签名统一,建议将签名的计算放在服务端,前端通过接口获取带签名的query
将queryParams传入签名函数生成签名,签名示例参考:签名示例
获取signature
后将signature
加到queryParams
中作为一个字段。
queryParams.signature = signature;
获取完整的queryParams
后,遍历queryParams
生成query
字符串,每个字段的值用encodeURIComponent
编码。完整流程示例如下:
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界面,示例代码如下:
const PlasoElectronSdk = window.require('@plasosdk/plaso-electron-sdk');
const createPrepareClassWindowParams: CreateClassWindowPamras = { classOptions: { loginName: 'hello', userName: 'world' } };
PlasoElectronSdk.createPrepareClassWindow(createPrepareClassWindowParams);
interface IPrepareClassOptions {
loginName: string;
userName: string;
displayAvatarUrl?: string;
topic?: string;
d_enableObjectEraser?: number;
}
参数名称 | 是否必填 | 类型 | 参数描述 |
---|---|---|---|
loginName | 是 | string | 唯一标识该用户的id,不能为空,相同的loginName进入课堂时,后面进入的会使前面进入的登出 |
userName | 是 | string | 用户昵称,头像缺省时会显示 |
displayAvatarUrl | 否 | string | 用户头像地址。不传默认使用用户昵称作为头像。 |
d_enableObjectEraser | 否 | number | 是否启用新版板书,新版板书的橡皮擦支持对象擦除,传入大于0的值启用新版板书。取值:
|
即将弃用: 不推荐传入,SDK内部默认设置了一些窗口参数,为了保证最佳体验,不要传入此参数。
Electron的窗口参数,详情参考 Electron官方文档。
type electronWinOptions = Electron.BrowserWindowConstructiorOptions;
// 课堂窗口打开渲染成功后的回调,回调参数为 窗口id
type onClassWindowReadyFn = (winId: number) => void;
// 课堂窗口关闭后的回调,回调参数为 窗口id
type onClassWindowLeaveFn = (winId: number) => void;
// 课堂结束后的回调,回调参数为 课堂的meetingId
type onClassFinishedFn = (meetingId: string) => void;
注意:
1、filePath 对应的文件资源,用户保存在自己的云端时,需要把 本次保存的备课文件的 相关资源放在同一特定目录下
2、每次保存生成的备课文件 都放在一个新的目录下,不同的备课文件不能共用一个目录
3、备课保存的资源文件名 不能更改,info.pb 是固定的文件名
// 保存板书方法,具体的保存逻辑由外部实现,取消保存板书时,callback传false, 不然传true
type FileParams = {
/** 备课相关资源文件的本地地址*/
filePath: string[];
/** 备课文件 类型*/
fileType: 'png' | 'pb';
};
type onSaveBoardFn = (
params: {
fileInfo: FileParams[];
fileName?: string;
},
callback: (result: boolean) => void,
) => void;
// 通知外部用户打开自己的资料中心,资料中心的具体ui和逻辑由外部用户自己实现
// 推荐:通过onClassWindowReadyFn回调的winId拿到课堂窗口实例,用户的云盘通过一个BrowserWindow
// 和课堂窗口组成父子窗口,云盘是子窗口,并且云盘窗口打开时保持置顶,这样来保证云盘打开时始终可见并且跟随课堂窗口。
type onOpenResourceCenterFn = () => void;
// 通过 insertObject 插入的文件传入 参数 info 时,怎么从info中获取文件的可访问地址的逻辑在用户那,所以需要函数从外部用户获取外部用户传入的文件地址
// 其中info会作为args中的第一个参数回传
type onGetExtFileNameFn = (...args: any[]) => Promise<string>;
初始化课堂窗口日志位置,窗口崩溃时会在同级目录下生成 reports 文件夹存储 dump,在 调用 createLiveClassWindow 前设置
日志默认位置:
require('path').join(require('electron').app.getPath('userData'), 'P403FileTemp');
// Windows:C:\Users\${userName}\AppData\Roaming\${appName}\P403FileTemp
// Mac:/Users/${userName}/Application\ Support/${appName}/P403FileTemp
参数示例:
// 代码示例
const PlasoElectronSdk = window.require('@plasosdk/plaso-electron-sdk');
const logFilePath = 'C:/Users/userName/Desktop/electronDemo/electron12.0.18_x32/resources/app';
PlasoElectronSdk.initLogConfig(logFilePath);
返回包的版本,格式:x.x.x
type getVersion = () => string;
创建实时课堂窗口
function createLiveClassWindow(params: CreateClassWindowPamras): void;
创建备课课堂窗口
function createLiveClassWindow(params: CreateClassWindowPamras): void;
用户从自己的资料中心往实时课堂/备课课堂插入文件
// 插入的文件类型暴露在PlasoElectronSdk上,PlasoElectronSdk.FILE_TYPE
const PlasoElectronSdk = window.require('@plasosdk/plaso-electron-sdk');
const enum FILE_TYPE {
PPT,
IMAGE,
PDF,
/** 这种模式需要用户实际传入的是WORD转成的PDF地址 */
WORD,
/** 这种模式需要用户实际传入的是EXCEL转成的PDF地址 */
EXCEL,
AUDIO,
VIDEO,
/** 这种模式传入真实WORD地址 */
DOC,
/** 这种模式传入真实EXCEL地址 */
XLS,
/** 备课文件 */
PREPARE_LESSONS,
};
// 插入外部云盘里的文件,文件需要遵循特定的数据结构,其中`url`和`info`至少传一个
interface IFileData {
/** 文件类型,使用SDK暴露的枚举 */
type: PlasoElectronSdk.FILE_TYPE;
/** 文件名称,传入后会显示在文件窗口标题栏上,建议带上文件后缀名。默认名字为文件类型 */
title?: string;
/** 访问权限为公开的全地址,建议使用https协议的全地址,传入`info`时会忽略该属性 */
url?: string;
/**
* 当用户插入的文件有签名时效时需要以info属性插入文件,这种方式需要配合onGetExtFileNameFn回调使用。
* `info`的值由SDK用户自行决定,SDK内部会把`info`作为onGetExtFileNameFn回调函数的参数传入,由SDK用户
* 根据`info`计算文件全地址。
*
* 为了避免文件签名失效,SDK内部会通过onGetExtFileNameFn回调向外部获取SDK用户计算好的文件全地址。
* 当使用这种方式插入云盘文件时会忽略`url`属性。
*
* 特别注意:`info`属性默认不支持PPT类型,PPT/WORD/EXCEL/DOC/XLS类型依然通过`url`属性插入。
*/
info?: any[];
}
/**
* @param {IFileData} fileData 文件数据
*/
type insertObject = (fileData) => void;
// 使用方式
const PlasoElectronSdk = window.require('@plasosdk/plaso-electron-sdk');
PlasoElectronSdk.insertObject(fileData);
1、 插入 备课文件 的格式如下,其中 fileLocationPath 为 插入的备课文件资源的地址前缀,比如要本地插入一个备课文件(info.pb),其完整地址为 C:\Users\xxx\AppData\Roaming\plaso_sdk\prepareLessonsTemp\draft.swap\info.pb,则此时 fileLocationPath = C:\Users\xxx\AppData\Roaming\plaso_sdk\prepareLessonsTemp\draft.swap
// 备课仅支持info插入,其中info属性格式固定,不能更改
const fileDataWithInfo = {
type: PlasoElectronSdk.FILE_TYPE.PREPARE_LESSONS,
info: ['pb', , , fileLocationPath],
};
注意:
1、保存备课文件时,需要把 info.pb 和其他的图片文件放在同一个目录下,这样才可以通过一个 目录地址获取 备课的所有资源
// 图片可以通过url或info插入,当插入gif时,title需要带上.gif后缀
const fileDataWithUrl = {
type: PlasoElectronSdk.FILE_TYPE.IMAGE,
title: 'xxx',
url: 'xxx',
};
const fileDataWithInfo = {
type: PlasoElectronSdk.FILE_TYPE.IMAGE,
title: 'xxx',
info: ['xxx', ...],
};
// PPT仅支持url插入
const fileDataWithUrl = {
type: PlasoElectronSdk.FILE_TYPE.PPT,
title: 'xxx',
url: 'xxx',
};
// 音视频可以通过url或info插入
const fileDataWithUrl = {
type: PlasoElectronSdk.FILE_TYPE.AUDIO/.VIDEO,
title: 'xxx',
url: 'xxx',
}
const fileDataWithInfo = {
type: PlasoElectronSdk.FILE_TYPE.AUDIO/.VIDEO,
title: 'xxx',
info: ['xxx', ...],
}
// PDF可以通过url或info插入
const fileDataWithUrl = {
type: PlasoElectronSdk.FILE_TYPE.PDF,
title: 'xxx',
url: 'xxx',
}
const fileDataWithInfo = {
type: PlasoElectronSdk.FILE_TYPE.PDF,
title: 'xxx',
info: ['xxx', ...],
}
// WORD/EXCEL/DOC/XLS仅支持url插入
const fileDataWithUrl = {
type: PlasoElectronSdk.FILE_TYPE/.WORD/.EXCEL/.DOC/.XLS,
title: 'xxx',
url: 'xxx',
}
step1、进课堂时,对象 classOptions.supportShowResourceCenter 需要是 true
step2、在课堂内点击资料中心后,会触发进课堂时传入的回调函数 onOpenResourceCenterFn,此时用户在 onOpenResourceCenterFn 函数内打开自己的云盘
step3、选择文件后,通过 PlasoElectronSdk.insertObject 方法插入文件,方法入参参考 insertObject 说明
step4、insertObject 方法 入参 有 info 时,此时 会触发进课堂时传入的回调函数 onGetExtFileNameFn 来获取文件的全地址
1、参考文档: 播放器SDK-Web播放器
2、当课堂中insertObject使用了info
属性时,SDK内部需要外部传入getExtFileName
,因此历史课堂需要使用jssdk的接入方式, 详见:Web播放器-jssdk接入
(1)用户的课堂外主窗口销毁时需要销毁课堂窗口
(2)基于 此包封装新包时:注意 @electron/remote 这个包的位置需要 和新包处于同级目录,需要把 和该包同级的@electron/remote 移到新包的同级目录处
(3)确保 仅最后的 node_moudles 的顶层有 @electron/remote
FAQs
伯索课堂Electron SDK
The npm package @plasosdk/plaso-electron-sdk receives a total of 16 weekly downloads. As such, @plasosdk/plaso-electron-sdk popularity was classified as not popular.
We found that @plasosdk/plaso-electron-sdk demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 7 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Four npm packages disguised as cryptographic tools steal developer credentials and send them to attacker-controlled Telegram infrastructure.
Security News
Ruby maintainers from Bundler and rbenv teams are building rv to bring Python uv's speed and unified tooling approach to Ruby development.
Security News
Following last week’s supply chain attack, Nx published findings on the GitHub Actions exploit and moved npm publishing to Trusted Publishers.