SDK · PC/Electron
Electron 适配层把真实 ImcoreClient、JWT / 登录身份、缓存、离线队列和媒体上传放在主进程。preload 只向 renderer 暴露受限 transport;renderer 仍使用同一套类型化 dm、group、room、community、ai、rtc 与 operations API,但拿不到凭据和 Electron ipcRenderer。
安装
npm install @imcore/sdk ws
适配层不依赖也不打包 Electron。main 与 renderer 是两个独立入口,确保 node:fs 不会进入 renderer 依赖图。
主进程
在 app.whenReady() 后创建客户端。token 刷新和登录身份必须留在这里,不要从 renderer IPC 接收。
import { app, BrowserWindow, ipcMain } from 'electron';
import { join } from 'node:path';
import WebSocket from 'ws';
import { ImcoreClient } from '@imcore/sdk';
import {
createElectronFileStorage,
registerElectronMainBridge,
} from '@imcore/sdk/electron/main';
await app.whenReady();
const win = new BrowserWindow({
webPreferences: {
preload: join(import.meta.dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false,
sandbox: true,
},
});
const storage = createElectronFileStorage({
filePath: join(app.getPath('userData'), 'imcore-state.json'),
});
const client = new ImcoreClient({
url: 'wss://your-host/acc',
webSocketImpl: WebSocket,
auth: {
getToken: () => readTokenFromKeychain(),
getLogin: () => ({ userID: currentUserID(), userName: currentUserName() }),
},
storage,
});
const bridge = registerElectronMainBridge({
client,
ipcMain,
webContents: () => win.webContents,
});
win.on('closed', () => bridge.dispose());
createElectronFileStorage() 会串行并原子替换写入。文件应放到 app.getPath('userData') 下;这里只存 SDK 缓存和离线队列,不存 JWT。凭据请放操作系统钥匙串,例如 Electron safeStorage 或项目已有的凭据服务。
registerElectronMainBridge() 默认只接受协议目录中已声明的命令,并只接受目标 webContents 发来的消息。renderer 信任级别较低时,可传 allowedCommands 进一步缩小范围。
Preload
打包下面的 preload 文件。它暴露的是窄 transport,不是 ipcRenderer:
import { contextBridge, ipcRenderer } from 'electron';
import { exposeElectronRendererTransport } from '@imcore/sdk/electron/renderer';
exposeElectronRendererTransport(contextBridge, ipcRenderer);
Renderer
用 preload 暴露的 transport 创建类型化 facade:
import {
createElectronRendererClient,
type ElectronRendererTransport,
} from '@imcore/sdk/electron/renderer';
declare global {
interface Window {
imcoreTransport: ElectronRendererTransport;
}
}
const im = createElectronRendererClient(window.imcoreTransport);
await im.connect();
await im.dm.send({ targetUserID: '1002', text: '来自 PC 客户端' });
const off = im.on('chat_message', (message) => renderMessage(message));
im.onStatus((status) => renderConnectionStatus(status));
// File/Blob 字节经 IPC 传递;HTTP 上传与 Authorization header 留在主进程。
await im.media.upload({ file, type: 'file', scene: 'dm', targetUserID: '1002' });
// renderer 销毁时:
off();
im.dispose();
错误会在 renderer 还原为 ElectronImcoreError,服务端错误和本地预检错误的数字 code 保持不变。Electron facade 的 send() 因序号需要跨 IPC 而返回 Promise;原有 typed feature 方法本身就是 Promise,调用形态不变。
多窗口
每个已登录窗口使用一套 bridge 和唯一 channelPrefix,并在 registerElectronMainBridge() 与 exposeElectronRendererTransport() 两端保持一致。bridge 会拒绝其他 webContents ID 的调用;dispose() 会清理 IPC handler、事件订阅和连接。