desktop-pet-app 0.3.4 → 0.3.13

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.
@@ -1 +0,0 @@
1
- {"type":"module"}
@@ -1,122 +0,0 @@
1
- import { randomUUID } from 'crypto';
2
- import { hostname } from 'os';
3
- import { sendToPet } from './tools.js';
4
- const SERVER_URL = process.env.DESKTOP_PET_SERVER ?? 'ws://127.0.0.1:8080/ws';
5
- const USER_ID = process.env.DESKTOP_PET_USER ?? hostname();
6
- const SKIN = process.env.DESKTOP_PET_SKIN ?? '';
7
- const pendingQuestions = new Map();
8
- const pendingAnswers = new Map();
9
- let ws = null;
10
- let connectPromise = null;
11
- /** 惰性连接中转服务器并完成 register;已连接时直接返回 */
12
- export function ensureConnected() {
13
- if (ws && ws.readyState === WebSocket.OPEN)
14
- return Promise.resolve();
15
- if (connectPromise)
16
- return connectPromise;
17
- connectPromise = new Promise((resolve, reject) => {
18
- let settled = false;
19
- const sock = new WebSocket(SERVER_URL);
20
- ws = sock;
21
- sock.onopen = () => {
22
- sock.send(JSON.stringify({ type: 'register', userId: USER_ID, skin: SKIN }));
23
- };
24
- sock.onmessage = (e) => {
25
- let msg;
26
- try {
27
- msg = JSON.parse(String(e.data));
28
- }
29
- catch {
30
- return;
31
- }
32
- if (msg.type === 'registered') {
33
- if (!settled) {
34
- settled = true;
35
- resolve();
36
- }
37
- }
38
- else if (msg.type === 'ask') {
39
- void onAsk(msg);
40
- }
41
- else if (msg.type === 'answer') {
42
- onAnswer(msg);
43
- }
44
- else if (msg.type === 'error' && msg.questionId) {
45
- const pending = pendingAnswers.get(msg.questionId);
46
- if (pending) {
47
- pendingAnswers.delete(msg.questionId);
48
- clearTimeout(pending.timer);
49
- pending.resolve(msg.message ?? '对方不在线');
50
- }
51
- }
52
- };
53
- sock.onerror = () => {
54
- if (!settled) {
55
- settled = true;
56
- connectPromise = null;
57
- ws = null;
58
- reject(new Error('RELAY_UNREACHABLE'));
59
- }
60
- };
61
- sock.onclose = () => {
62
- ws = null;
63
- connectPromise = null;
64
- };
65
- });
66
- return connectPromise;
67
- }
68
- /** 向远程用户的宠物提问,阻塞等待回答;超时或对方不在线时返回提示文本 */
69
- export async function askRemote(to, question, timeoutMs = 120_000) {
70
- await ensureConnected();
71
- const questionId = randomUUID();
72
- return new Promise((resolve) => {
73
- const timer = setTimeout(() => {
74
- pendingAnswers.delete(questionId);
75
- resolve(`对方暂时没理我(${Math.round(timeoutMs / 1000)} 秒未回复)`);
76
- }, timeoutMs);
77
- pendingAnswers.set(questionId, {
78
- resolve: (text) => {
79
- clearTimeout(timer);
80
- resolve(text);
81
- },
82
- timer
83
- });
84
- ws.send(JSON.stringify({ type: 'ask', to, questionId, text: question }));
85
- });
86
- }
87
- export function listPendingQuestions() {
88
- return [...pendingQuestions.values()];
89
- }
90
- export async function answerQuestion(questionId, text) {
91
- await ensureConnected();
92
- if (!pendingQuestions.has(questionId))
93
- throw new Error('UNKNOWN_QUESTION');
94
- pendingQuestions.delete(questionId);
95
- ws.send(JSON.stringify({ type: 'answer', questionId, text }));
96
- }
97
- async function onAsk(msg) {
98
- pendingQuestions.set(msg.questionId, {
99
- questionId: msg.questionId,
100
- from: msg.from,
101
- text: msg.text
102
- });
103
- // 本地气泡展示问题;对方宠物以"访客"形象来到桌面
104
- try {
105
- await sendToPet({ type: 'bubble', text: `${msg.from} 的宠物问你:${msg.text}`, ttl: 15000 });
106
- }
107
- catch { }
108
- if (msg.skin && ['codex', 'claude', 'minecraft'].includes(msg.skin)) {
109
- try {
110
- await sendToPet({ type: 'invite', skin: msg.skin });
111
- }
112
- catch { }
113
- }
114
- }
115
- function onAnswer(msg) {
116
- const pending = pendingAnswers.get(msg.questionId);
117
- if (pending) {
118
- pendingAnswers.delete(msg.questionId);
119
- pending.resolve(msg.text);
120
- }
121
- sendToPet({ type: 'bubble', text: `${msg.from} 的宠物回答:${msg.text}`, ttl: 10000 }).catch(() => { });
122
- }
@@ -1,21 +0,0 @@
1
- import { readFileSync } from 'fs';
2
- import { homedir } from 'os';
3
- import { join } from 'path';
4
- const PORT_FILE = join(homedir(), '.desktop-pet-port');
5
- /** 把事件 POST 给正在运行的桌面宠物应用;宠物未启动时抛错 */
6
- export async function sendToPet(event) {
7
- let port;
8
- try {
9
- port = readFileSync(PORT_FILE, 'utf8').trim();
10
- }
11
- catch {
12
- throw new Error('PET_NOT_RUNNING');
13
- }
14
- const res = await fetch(`http://127.0.0.1:${port}/`, {
15
- method: 'POST',
16
- headers: { 'content-type': 'application/json' },
17
- body: JSON.stringify(event)
18
- });
19
- if (!res.ok)
20
- throw new Error('PET_REJECTED');
21
- }
@@ -1,38 +0,0 @@
1
- "use strict";
2
- const electron = require("electron");
3
- const PET_EVENT = "pet:event";
4
- const PET_MOVE = "pet:move";
5
- const PET_DRAG = "pet:drag";
6
- const PET_MENU = "pet:menu";
7
- const PET_MOUSE_PASSTHROUGH = "pet:mouse-passthrough";
8
- const P2P_SIGNAL_IN = "p2p:signal-in";
9
- const P2P_SIGNAL_OUT = "p2p:signal-out";
10
- const P2P_START = "p2p:start";
11
- const P2P_CONFIG = "p2p:config";
12
- const P2P_CHAT_SEND = "p2p:chat-send";
13
- const P2P_CHAT_EVENT = "p2p:chat-event";
14
- electron.contextBridge.exposeInMainWorld("petApi", {
15
- move: (dx, dy) => electron.ipcRenderer.send(PET_MOVE, dx, dy),
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),
18
- openMenu: () => electron.ipcRenderer.send(PET_MENU),
19
- onEvent: (cb) => {
20
- electron.ipcRenderer.on(PET_EVENT, (_e, ev) => cb(ev));
21
- }
22
- });
23
- electron.contextBridge.exposeInMainWorld("p2pApi", {
24
- sendSignal: (message) => electron.ipcRenderer.send(P2P_SIGNAL_OUT, message),
25
- onSignal: (cb) => {
26
- electron.ipcRenderer.on(P2P_SIGNAL_IN, (_event, message) => cb(message));
27
- },
28
- onStart: (cb) => {
29
- electron.ipcRenderer.on(P2P_START, (_event, message) => cb(message));
30
- },
31
- onConfig: (cb) => {
32
- electron.ipcRenderer.on(P2P_CONFIG, (_event, message) => cb(message));
33
- },
34
- onChatSend: (cb) => {
35
- electron.ipcRenderer.on(P2P_CHAT_SEND, (_event, message) => cb(message));
36
- },
37
- emitChatEvent: (message) => electron.ipcRenderer.send(P2P_CHAT_EVENT, message)
38
- });