desktop-pet-app 0.1.1 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/out/main/index.js +67 -9
- package/out/preload/index.js +2 -0
- package/out/renderer/assets/{index-DqBgThfZ.js → index-CHc4qvDr.js} +31 -8
- package/out/renderer/assets/{p2p-Ch5g1MsK.js → p2p-LRPQ9Jzg.js} +34 -37
- package/out/renderer/index.html +1 -1
- package/out/renderer/p2p.html +1 -1
- package/package.json +5 -3
package/out/main/index.js
CHANGED
|
@@ -110,6 +110,7 @@ const PET_EVENT = "pet:event";
|
|
|
110
110
|
const PET_MOVE = "pet:move";
|
|
111
111
|
const PET_DRAG = "pet:drag";
|
|
112
112
|
const PET_MENU = "pet:menu";
|
|
113
|
+
const PET_MOUSE_PASSTHROUGH = "pet:mouse-passthrough";
|
|
113
114
|
const P2P_SIGNAL_IN = "p2p:signal-in";
|
|
114
115
|
const P2P_SIGNAL_OUT = "p2p:signal-out";
|
|
115
116
|
const P2P_CONFIG = "p2p:config";
|
|
@@ -342,7 +343,7 @@ function validate(credentials) {
|
|
|
342
343
|
throw new Error("设备配置的 serverUrl 必须使用 http 或 https");
|
|
343
344
|
}
|
|
344
345
|
}
|
|
345
|
-
const DEFAULT_DESKTOP_PET_HOST = "
|
|
346
|
+
const DEFAULT_DESKTOP_PET_HOST = "https://deskpalai.com";
|
|
346
347
|
function configuredRelayURL() {
|
|
347
348
|
const host = process.env.DESKTOP_PET_HOST?.trim();
|
|
348
349
|
if (host) return relayURLFromHost(host);
|
|
@@ -443,6 +444,32 @@ function showDesktopNotification(title, body) {
|
|
|
443
444
|
if (!electron.Notification.isSupported()) return;
|
|
444
445
|
new electron.Notification({ title, body }).show();
|
|
445
446
|
}
|
|
447
|
+
function p2pSigningPayload(message, fromDeviceId) {
|
|
448
|
+
const signalJSON = JSON.stringify(message.signal ?? null);
|
|
449
|
+
const signalHash = node_crypto.createHash("sha256").update(signalJSON).digest("hex");
|
|
450
|
+
return [
|
|
451
|
+
message.type,
|
|
452
|
+
message.taskId ?? "",
|
|
453
|
+
message.sessionId ?? "",
|
|
454
|
+
fromDeviceId,
|
|
455
|
+
message.toDeviceId ?? "",
|
|
456
|
+
message.expiresAt ?? "",
|
|
457
|
+
signalHash
|
|
458
|
+
].join("\n");
|
|
459
|
+
}
|
|
460
|
+
function verifyP2PSignal(message) {
|
|
461
|
+
if (!message.signature || !message.publicKey || !message.fromDeviceId || !message.taskId || !message.sessionId || !message.toDeviceId || !message.expiresAt) return false;
|
|
462
|
+
try {
|
|
463
|
+
return node_crypto.verify(
|
|
464
|
+
null,
|
|
465
|
+
Buffer.from(p2pSigningPayload(message, message.fromDeviceId)),
|
|
466
|
+
message.publicKey,
|
|
467
|
+
Buffer.from(message.signature, "base64")
|
|
468
|
+
);
|
|
469
|
+
} catch {
|
|
470
|
+
return false;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
446
473
|
let serverURL = configuredRelayURL();
|
|
447
474
|
let userID = process.env.DESKTOP_PET_USER ?? os.hostname();
|
|
448
475
|
const SKIN = process.env.DESKTOP_PET_SKIN ?? "";
|
|
@@ -623,7 +650,16 @@ function ensureConnected() {
|
|
|
623
650
|
showCapabilityGapResult(msg.task);
|
|
624
651
|
notifyResultWaiters(msg.task);
|
|
625
652
|
} else if (msg.type.startsWith("p2p.") && msg.type !== "p2p.forwarded") {
|
|
626
|
-
|
|
653
|
+
if (!verifyP2PSignal(msg)) {
|
|
654
|
+
logger.warn("received invalid p2p signal", {
|
|
655
|
+
type: msg.type,
|
|
656
|
+
taskId: msg.taskId,
|
|
657
|
+
sessionId: msg.sessionId,
|
|
658
|
+
fromDeviceId: msg.fromDeviceId
|
|
659
|
+
});
|
|
660
|
+
} else {
|
|
661
|
+
for (const listener of p2pSignalListeners) listener(msg);
|
|
662
|
+
}
|
|
627
663
|
} else if (msg.type === "help.request" && msg.from) {
|
|
628
664
|
dispatch({ type: "state", state: "excited", ttl: 10 });
|
|
629
665
|
dispatch({ type: "bubble", text: `@${msg.from} 想请你帮忙,右键我可以同意或婉拒`, ttl: 12e3 });
|
|
@@ -954,14 +990,9 @@ function onP2PSignal(listener) {
|
|
|
954
990
|
async function sendP2PSignal(message) {
|
|
955
991
|
if (!signingPrivateKey) throw new Error("设备未配置 P2P 签名密钥");
|
|
956
992
|
const outgoing = { ...message, protocolVersion: TASK_PROTOCOL_VERSION };
|
|
957
|
-
const signature = crypto.sign(null, Buffer.from(p2pSigningPayload(outgoing)), signingPrivateKey).toString("base64");
|
|
993
|
+
const signature = crypto.sign(null, Buffer.from(p2pSigningPayload(outgoing, deviceID)), signingPrivateKey).toString("base64");
|
|
958
994
|
await relayRPC({ ...outgoing, signature }, 1e4);
|
|
959
995
|
}
|
|
960
|
-
function p2pSigningPayload(message) {
|
|
961
|
-
const signalJSON = JSON.stringify(message.signal ?? null);
|
|
962
|
-
const signalHash = crypto.createHash("sha256").update(signalJSON).digest("hex");
|
|
963
|
-
return [message.type, message.taskId, message.sessionId, deviceID, message.toDeviceId, message.expiresAt, signalHash].join("\n");
|
|
964
|
-
}
|
|
965
996
|
async function requestRemoteTaskContext(taskId, request) {
|
|
966
997
|
const response = await relayRPC(
|
|
967
998
|
{ type: "task.context.request", protocolVersion: TASK_PROTOCOL_VERSION, taskId, payload: request },
|
|
@@ -1550,7 +1581,15 @@ async function sendFriendChatPreferred(input) {
|
|
|
1550
1581
|
}
|
|
1551
1582
|
function onRendererSignal(event, message) {
|
|
1552
1583
|
if (!transportWindow || event.sender !== transportWindow.webContents) return;
|
|
1553
|
-
void sendP2PSignal(message).
|
|
1584
|
+
void sendP2PSignal(message).then(() => {
|
|
1585
|
+
if (message.type !== "p2p.ice-candidate") {
|
|
1586
|
+
logger.info("p2p signal forwarded", {
|
|
1587
|
+
type: message.type,
|
|
1588
|
+
taskId: message.taskId,
|
|
1589
|
+
sessionId: message.sessionId
|
|
1590
|
+
});
|
|
1591
|
+
}
|
|
1592
|
+
}).catch((err) => {
|
|
1554
1593
|
logger.warn("p2p signal failed", { type: message.type, taskId: message.taskId, error: String(err) });
|
|
1555
1594
|
if (message.type === "p2p.offer" && message.taskId.startsWith("chat.")) {
|
|
1556
1595
|
void fallbackChat(message.taskId.slice("chat.".length));
|
|
@@ -1560,6 +1599,18 @@ function onRendererSignal(event, message) {
|
|
|
1560
1599
|
function onChatEvent(event, message) {
|
|
1561
1600
|
if (!transportWindow || event.sender !== transportWindow.webContents || !message || typeof message !== "object") return;
|
|
1562
1601
|
const value = message;
|
|
1602
|
+
if (value.type === "diagnostic") {
|
|
1603
|
+
logger.info("p2p diagnostic", {
|
|
1604
|
+
stage: value.stage,
|
|
1605
|
+
sessionId: value.sessionId,
|
|
1606
|
+
signalType: value.signalType,
|
|
1607
|
+
connectionState: value.connectionState,
|
|
1608
|
+
iceConnectionState: value.iceConnectionState,
|
|
1609
|
+
iceGatheringState: value.iceGatheringState,
|
|
1610
|
+
error: value.error
|
|
1611
|
+
});
|
|
1612
|
+
return;
|
|
1613
|
+
}
|
|
1563
1614
|
if (value.type === "received" && value.text) {
|
|
1564
1615
|
dispatch({ type: "state", state: "excited", ttl: 6 });
|
|
1565
1616
|
dispatch({ type: "bubble", text: `${value.from ?? "好友"}:${value.text}`, ttl: 1e4 });
|
|
@@ -1899,6 +1950,7 @@ function createPetWindow(skin, options) {
|
|
|
1899
1950
|
});
|
|
1900
1951
|
win.setAlwaysOnTop(true, "screen-saver");
|
|
1901
1952
|
win.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true });
|
|
1953
|
+
win.setIgnoreMouseEvents(true, { forward: true });
|
|
1902
1954
|
registerPetWindow(id, win);
|
|
1903
1955
|
petManager.register(id, win, skin, identity);
|
|
1904
1956
|
if (process.env.ELECTRON_RENDERER_URL) {
|
|
@@ -1930,6 +1982,10 @@ function createPetWindow(skin, options) {
|
|
|
1930
1982
|
if (win.isDestroyed() || e.sender !== win.webContents) return;
|
|
1931
1983
|
void showPetMenu();
|
|
1932
1984
|
};
|
|
1985
|
+
const onMousePassthrough = (e, enabled) => {
|
|
1986
|
+
if (win.isDestroyed() || e.sender !== win.webContents || typeof enabled !== "boolean") return;
|
|
1987
|
+
win.setIgnoreMouseEvents(enabled, { forward: enabled });
|
|
1988
|
+
};
|
|
1933
1989
|
const showPetMenu = async () => {
|
|
1934
1990
|
const accountUsername = currentAccountUsername();
|
|
1935
1991
|
const identityPresentation = publishIdentity();
|
|
@@ -2022,10 +2078,12 @@ function createPetWindow(skin, options) {
|
|
|
2022
2078
|
electron.ipcMain.on(PET_MOVE, onMove);
|
|
2023
2079
|
electron.ipcMain.on(PET_DRAG, onDrag);
|
|
2024
2080
|
electron.ipcMain.on(PET_MENU, onMenu);
|
|
2081
|
+
electron.ipcMain.on(PET_MOUSE_PASSTHROUGH, onMousePassthrough);
|
|
2025
2082
|
win.on("closed", () => {
|
|
2026
2083
|
electron.ipcMain.removeListener(PET_MOVE, onMove);
|
|
2027
2084
|
electron.ipcMain.removeListener(PET_DRAG, onDrag);
|
|
2028
2085
|
electron.ipcMain.removeListener(PET_MENU, onMenu);
|
|
2086
|
+
electron.ipcMain.removeListener(PET_MOUSE_PASSTHROUGH, onMousePassthrough);
|
|
2029
2087
|
});
|
|
2030
2088
|
return id;
|
|
2031
2089
|
}
|
package/out/preload/index.js
CHANGED
|
@@ -4,6 +4,7 @@ const PET_EVENT = "pet:event";
|
|
|
4
4
|
const PET_MOVE = "pet:move";
|
|
5
5
|
const PET_DRAG = "pet:drag";
|
|
6
6
|
const PET_MENU = "pet:menu";
|
|
7
|
+
const PET_MOUSE_PASSTHROUGH = "pet:mouse-passthrough";
|
|
7
8
|
const P2P_SIGNAL_IN = "p2p:signal-in";
|
|
8
9
|
const P2P_SIGNAL_OUT = "p2p:signal-out";
|
|
9
10
|
const P2P_START = "p2p:start";
|
|
@@ -13,6 +14,7 @@ const P2P_CHAT_EVENT = "p2p:chat-event";
|
|
|
13
14
|
electron.contextBridge.exposeInMainWorld("petApi", {
|
|
14
15
|
move: (dx, dy) => electron.ipcRenderer.send(PET_MOVE, dx, dy),
|
|
15
16
|
drag: (phase, dx = 0, dy = 0) => electron.ipcRenderer.send(PET_DRAG, phase, dx, dy),
|
|
17
|
+
setMousePassthrough: (enabled) => electron.ipcRenderer.send(PET_MOUSE_PASSTHROUGH, enabled),
|
|
16
18
|
openMenu: () => electron.ipcRenderer.send(PET_MENU),
|
|
17
19
|
onEvent: (cb) => {
|
|
18
20
|
electron.ipcRenderer.on(PET_EVENT, (_e, ev) => cb(ev));
|
|
@@ -1618,9 +1618,28 @@ window.petApi.onEvent((ev) => {
|
|
|
1618
1618
|
let dragging = false;
|
|
1619
1619
|
let lastX = 0;
|
|
1620
1620
|
let lastY = 0;
|
|
1621
|
+
let mousePassthrough = false;
|
|
1622
|
+
function updateMousePassthrough(clientX, clientY) {
|
|
1623
|
+
const rect = canvas.getBoundingClientRect();
|
|
1624
|
+
const x = Math.floor((clientX - rect.left) * canvas.width / rect.width);
|
|
1625
|
+
const y = Math.floor((clientY - rect.top) * canvas.height / rect.height);
|
|
1626
|
+
let overPet = false;
|
|
1627
|
+
if (x >= 0 && x < canvas.width && y >= 0 && y < canvas.height) {
|
|
1628
|
+
const alpha = canvas.getContext("2d").getImageData(x, y, 1, 1).data[3];
|
|
1629
|
+
overPet = alpha > 16;
|
|
1630
|
+
}
|
|
1631
|
+
const next = !dragging && !overPet;
|
|
1632
|
+
if (next === mousePassthrough) return;
|
|
1633
|
+
mousePassthrough = next;
|
|
1634
|
+
window.petApi.setMousePassthrough(next);
|
|
1635
|
+
}
|
|
1621
1636
|
canvas.addEventListener("mousedown", (e) => {
|
|
1622
1637
|
if (e.button !== 0) return;
|
|
1623
1638
|
dragging = true;
|
|
1639
|
+
if (mousePassthrough) {
|
|
1640
|
+
mousePassthrough = false;
|
|
1641
|
+
window.petApi.setMousePassthrough(false);
|
|
1642
|
+
}
|
|
1624
1643
|
lastX = e.screenX;
|
|
1625
1644
|
lastY = e.screenY;
|
|
1626
1645
|
animator.setDragging(true);
|
|
@@ -1628,21 +1647,24 @@ canvas.addEventListener("mousedown", (e) => {
|
|
|
1628
1647
|
draw();
|
|
1629
1648
|
});
|
|
1630
1649
|
window.addEventListener("mousemove", (e) => {
|
|
1631
|
-
if (
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1650
|
+
if (dragging) {
|
|
1651
|
+
const dx = e.screenX - lastX;
|
|
1652
|
+
const dy = e.screenY - lastY;
|
|
1653
|
+
if (dx !== 0 || dy !== 0) window.petApi.drag("move", dx, dy);
|
|
1654
|
+
lastX = e.screenX;
|
|
1655
|
+
lastY = e.screenY;
|
|
1656
|
+
}
|
|
1657
|
+
updateMousePassthrough(e.clientX, e.clientY);
|
|
1637
1658
|
});
|
|
1638
|
-
window.addEventListener("mouseup", () => {
|
|
1659
|
+
window.addEventListener("mouseup", (e) => {
|
|
1639
1660
|
if (!dragging) return;
|
|
1640
1661
|
dragging = false;
|
|
1641
1662
|
window.petApi.drag("end");
|
|
1642
1663
|
animator.setDragging(false);
|
|
1643
1664
|
draw();
|
|
1665
|
+
updateMousePassthrough(e.clientX, e.clientY);
|
|
1644
1666
|
});
|
|
1645
|
-
|
|
1667
|
+
canvas.addEventListener("contextmenu", (e) => {
|
|
1646
1668
|
e.preventDefault();
|
|
1647
1669
|
if (dragging) {
|
|
1648
1670
|
dragging = false;
|
|
@@ -1659,3 +1681,4 @@ window.addEventListener("blur", () => {
|
|
|
1659
1681
|
animator.setDragging(false);
|
|
1660
1682
|
draw();
|
|
1661
1683
|
});
|
|
1684
|
+
window.petApi.setMousePassthrough(true);
|
|
@@ -53,11 +53,16 @@ function decodeArtifactChunk(buffer) {
|
|
|
53
53
|
};
|
|
54
54
|
}
|
|
55
55
|
class IceCandidateBuffer {
|
|
56
|
-
constructor(maxPerSession = 64) {
|
|
56
|
+
constructor(maxPerSession = 64, maxSessions = 64) {
|
|
57
57
|
this.maxPerSession = maxPerSession;
|
|
58
|
+
this.maxSessions = maxSessions;
|
|
58
59
|
}
|
|
59
60
|
candidates = /* @__PURE__ */ new Map();
|
|
60
61
|
add(sessionId, candidate) {
|
|
62
|
+
if (!this.candidates.has(sessionId) && this.candidates.size >= this.maxSessions) {
|
|
63
|
+
const oldestSessionId = this.candidates.keys().next().value;
|
|
64
|
+
if (oldestSessionId) this.candidates.delete(oldestSessionId);
|
|
65
|
+
}
|
|
61
66
|
const queued = this.candidates.get(sessionId) ?? [];
|
|
62
67
|
if (queued.length < this.maxPerSession) queued.push(candidate);
|
|
63
68
|
this.candidates.set(sessionId, queued);
|
|
@@ -211,7 +216,13 @@ let iceServers = parseIceServers();
|
|
|
211
216
|
window.p2pApi.onStart((request) => void createOffer(request));
|
|
212
217
|
window.p2pApi.onChatSend((request) => void createOffer(request));
|
|
213
218
|
window.p2pApi.onSignal((message) => {
|
|
214
|
-
void receiveSignal(message).catch(() =>
|
|
219
|
+
void receiveSignal(message).catch((error) => {
|
|
220
|
+
reportDiagnostic("signal-error", message.sessionId, {
|
|
221
|
+
signalType: message.type,
|
|
222
|
+
error: error instanceof Error ? `${error.name}: ${error.message}` : String(error)
|
|
223
|
+
});
|
|
224
|
+
failSession(message.sessionId);
|
|
225
|
+
});
|
|
215
226
|
});
|
|
216
227
|
window.p2pApi.onConfig((config) => {
|
|
217
228
|
if (config.iceServers?.length) iceServers = config.iceServers;
|
|
@@ -228,24 +239,28 @@ async function createOffer(request) {
|
|
|
228
239
|
const session = sessions.get(sessionId);
|
|
229
240
|
session.channel = new ReliableArtifactChannel(channel);
|
|
230
241
|
session.operationId = request.operationId;
|
|
231
|
-
wireChatChannel(channel, request.toUsername, request.operationId, request.text);
|
|
242
|
+
wireChatChannel(channel, sessionId, request.toUsername, request.operationId, request.text);
|
|
232
243
|
const offer = await peer.createOffer();
|
|
233
244
|
await peer.setLocalDescription(offer);
|
|
245
|
+
reportDiagnostic("offer-created", sessionId, peerState(peer));
|
|
234
246
|
send({ type: "p2p.offer", taskId: request.taskId, toDeviceId: request.toDeviceId, sessionId, signal: offer });
|
|
235
247
|
}
|
|
236
248
|
async function receiveSignal(message) {
|
|
237
249
|
if (new Date(message.expiresAt).getTime() <= Date.now()) return;
|
|
238
|
-
if (
|
|
250
|
+
if (message.type !== "p2p.ice-candidate") {
|
|
251
|
+
reportDiagnostic("signal-received", message.sessionId, { signalType: message.type });
|
|
252
|
+
}
|
|
239
253
|
if (message.type === "p2p.offer" && message.fromDeviceId && message.signal) {
|
|
240
254
|
const peer = createPeer(message.taskId, message.sessionId, message.fromDeviceId);
|
|
241
255
|
peer.ondatachannel = (event) => {
|
|
242
256
|
sessions.get(message.sessionId).channel = new ReliableArtifactChannel(event.channel);
|
|
243
|
-
wireChatChannel(event.channel, message.from);
|
|
257
|
+
wireChatChannel(event.channel, message.sessionId, message.from);
|
|
244
258
|
};
|
|
245
259
|
await peer.setRemoteDescription(message.signal);
|
|
246
260
|
await flushPendingIceCandidates(message.sessionId, peer);
|
|
247
261
|
const answer = await peer.createAnswer();
|
|
248
262
|
await peer.setLocalDescription(answer);
|
|
263
|
+
reportDiagnostic("answer-created", message.sessionId, peerState(peer));
|
|
249
264
|
send({
|
|
250
265
|
type: "p2p.answer",
|
|
251
266
|
taskId: message.taskId,
|
|
@@ -270,40 +285,10 @@ async function receiveSignal(message) {
|
|
|
270
285
|
closeSession(message.sessionId);
|
|
271
286
|
}
|
|
272
287
|
}
|
|
273
|
-
async function verifySignal(message) {
|
|
274
|
-
if (!message.signature || !message.publicKey || !message.fromDeviceId) return false;
|
|
275
|
-
try {
|
|
276
|
-
const der = pemToBytes(message.publicKey);
|
|
277
|
-
const key = await crypto.subtle.importKey("spki", der, { name: "Ed25519" }, false, ["verify"]);
|
|
278
|
-
const signalJSON = JSON.stringify(message.signal ?? null);
|
|
279
|
-
const digest2 = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(signalJSON));
|
|
280
|
-
const hash = [...new Uint8Array(digest2)].map((value) => value.toString(16).padStart(2, "0")).join("");
|
|
281
|
-
const payload = [
|
|
282
|
-
message.type,
|
|
283
|
-
message.taskId,
|
|
284
|
-
message.sessionId,
|
|
285
|
-
message.fromDeviceId,
|
|
286
|
-
message.toDeviceId,
|
|
287
|
-
message.expiresAt,
|
|
288
|
-
hash
|
|
289
|
-
].join("\n");
|
|
290
|
-
return crypto.subtle.verify(
|
|
291
|
-
"Ed25519",
|
|
292
|
-
key,
|
|
293
|
-
Uint8Array.from(atob(message.signature), (char) => char.charCodeAt(0)),
|
|
294
|
-
new TextEncoder().encode(payload)
|
|
295
|
-
);
|
|
296
|
-
} catch {
|
|
297
|
-
return false;
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
function pemToBytes(value) {
|
|
301
|
-
const base64 = value.replace(/-----[^-]+-----/g, "").replace(/\s/g, "");
|
|
302
|
-
return Uint8Array.from(atob(base64), (char) => char.charCodeAt(0)).buffer;
|
|
303
|
-
}
|
|
304
288
|
function createPeer(taskId, sessionId, toDeviceId, timeoutMs = P2P_CONNECT_TIMEOUT_MS) {
|
|
305
289
|
const peer = new RTCPeerConnection({ iceServers });
|
|
306
290
|
const timer = setTimeout(() => {
|
|
291
|
+
reportDiagnostic("connect-timeout", sessionId, peerState(peer));
|
|
307
292
|
const operationId = sessions.get(sessionId)?.operationId;
|
|
308
293
|
if (operationId) window.p2pApi.emitChatEvent({ type: "failed", operationId });
|
|
309
294
|
send({ type: "p2p.failed", taskId, sessionId, toDeviceId });
|
|
@@ -316,6 +301,7 @@ function createPeer(taskId, sessionId, toDeviceId, timeoutMs = P2P_CONNECT_TIMEO
|
|
|
316
301
|
}
|
|
317
302
|
};
|
|
318
303
|
peer.onconnectionstatechange = () => {
|
|
304
|
+
reportDiagnostic("connection-state", sessionId, peerState(peer));
|
|
319
305
|
if (peer.connectionState === "connected") {
|
|
320
306
|
clearTimeout(timer);
|
|
321
307
|
send({ type: "p2p.ready", taskId, sessionId, toDeviceId });
|
|
@@ -327,9 +313,10 @@ function createPeer(taskId, sessionId, toDeviceId, timeoutMs = P2P_CONNECT_TIMEO
|
|
|
327
313
|
};
|
|
328
314
|
return peer;
|
|
329
315
|
}
|
|
330
|
-
function wireChatChannel(channel, peerUsername, operationId, outgoingText) {
|
|
316
|
+
function wireChatChannel(channel, sessionId, peerUsername, operationId, outgoingText) {
|
|
331
317
|
if (operationId && outgoingText) {
|
|
332
318
|
channel.addEventListener("open", () => {
|
|
319
|
+
reportDiagnostic("channel-open", sessionId);
|
|
333
320
|
channel.send(JSON.stringify({ type: "chat.message", text: outgoingText }));
|
|
334
321
|
window.p2pApi.emitChatEvent({ type: "sent", operationId });
|
|
335
322
|
}, { once: true });
|
|
@@ -366,6 +353,16 @@ async function flushPendingIceCandidates(sessionId, peer) {
|
|
|
366
353
|
function send(message) {
|
|
367
354
|
window.p2pApi.sendSignal({ ...message, expiresAt: new Date(Date.now() + 5 * 6e4).toISOString() });
|
|
368
355
|
}
|
|
356
|
+
function peerState(peer) {
|
|
357
|
+
return {
|
|
358
|
+
connectionState: peer.connectionState,
|
|
359
|
+
iceConnectionState: peer.iceConnectionState,
|
|
360
|
+
iceGatheringState: peer.iceGatheringState
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
function reportDiagnostic(stage, sessionId, fields = {}) {
|
|
364
|
+
window.p2pApi.emitChatEvent({ type: "diagnostic", stage, sessionId, ...fields });
|
|
365
|
+
}
|
|
369
366
|
function parseIceServers() {
|
|
370
367
|
try {
|
|
371
368
|
const env = __vite_import_meta_env__;
|
package/out/renderer/index.html
CHANGED
package/out/renderer/p2p.html
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<!doctype html>
|
|
2
2
|
<html lang="en">
|
|
3
|
-
<head><meta charset="UTF-8" /><title>Desktop Pet P2P</title> <script type="module" crossorigin src="./assets/p2p-
|
|
3
|
+
<head><meta charset="UTF-8" /><title>Desktop Pet P2P</title> <script type="module" crossorigin src="./assets/p2p-LRPQ9Jzg.js"></script>
|
|
4
4
|
</head>
|
|
5
5
|
<body></body>
|
|
6
6
|
</html>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "desktop-pet-app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "AI desktop pet with MCP support and a self-hosted relay server",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|
|
@@ -34,7 +34,9 @@
|
|
|
34
34
|
"test": "vitest run",
|
|
35
35
|
"package:mac": "npm run build && electron-builder --mac pkg --publish never",
|
|
36
36
|
"pack:check": "npm pack --dry-run",
|
|
37
|
-
"prepack": "npm run build",
|
|
37
|
+
"prepack": "npm run build && node scripts/prepare-npm-package.mjs",
|
|
38
|
+
"postpack": "node scripts/prepare-npm-package.mjs --restore",
|
|
39
|
+
"recover:npm-package": "node scripts/prepare-npm-package.mjs --restore",
|
|
38
40
|
"prepublishOnly": "npm run typecheck && npm test && npm run pack:check"
|
|
39
41
|
},
|
|
40
42
|
"build": {
|
|
@@ -65,8 +67,8 @@
|
|
|
65
67
|
"vitest": "^3.2.7"
|
|
66
68
|
},
|
|
67
69
|
"dependencies": {
|
|
68
|
-
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
69
70
|
"electron": "^33.2.0",
|
|
71
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
70
72
|
"ws": "^8.21.3",
|
|
71
73
|
"zod": "^4.5.4"
|
|
72
74
|
}
|