desktop-pet-app 0.2.7 → 0.3.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.
|
Binary file
|
|
Binary file
|
package/out/main/index.js
CHANGED
|
@@ -23,11 +23,11 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
23
|
));
|
|
24
24
|
const electron = require("electron");
|
|
25
25
|
const node_path = require("node:path");
|
|
26
|
+
const node_fs = require("node:fs");
|
|
26
27
|
const path = require("path");
|
|
27
28
|
const os = require("os");
|
|
28
29
|
const node_crypto = require("node:crypto");
|
|
29
30
|
const node_http = require("node:http");
|
|
30
|
-
const node_fs = require("node:fs");
|
|
31
31
|
const node_os = require("node:os");
|
|
32
32
|
const crypto = require("crypto");
|
|
33
33
|
const WebSocket = require("ws");
|
|
@@ -66,7 +66,20 @@ function levelFromEnv() {
|
|
|
66
66
|
const v = (process.env.DESKTOP_PET_LOG_LEVEL ?? "info").toLowerCase();
|
|
67
67
|
return v === "debug" || v === "warn" || v === "error" ? v : "info";
|
|
68
68
|
}
|
|
69
|
-
|
|
69
|
+
function mainLogDirectory() {
|
|
70
|
+
return node_path.join(electron.app.getPath("userData"), "logs");
|
|
71
|
+
}
|
|
72
|
+
function writeMainLog(line) {
|
|
73
|
+
console.log(line);
|
|
74
|
+
try {
|
|
75
|
+
const directory = mainLogDirectory();
|
|
76
|
+
node_fs.mkdirSync(directory, { recursive: true });
|
|
77
|
+
node_fs.appendFileSync(node_path.join(directory, "main.log"), `${line}
|
|
78
|
+
`, "utf8");
|
|
79
|
+
} catch {
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const logger = new Logger(levelFromEnv(), writeMainLog);
|
|
70
83
|
const PORT_FILE = process.env.DESKTOP_PET_PORT_FILE?.trim() || path.join(os.homedir(), ".desktop-pet-port");
|
|
71
84
|
const LOCAL_PET_PROTOCOL_VERSION = "1.0";
|
|
72
85
|
const LOCAL_PET_MAX_BODY_BYTES = 64 * 1024;
|
|
@@ -268,9 +281,11 @@ class PetManager {
|
|
|
268
281
|
pets = /* @__PURE__ */ new Map();
|
|
269
282
|
lastGreetAt = /* @__PURE__ */ new Map();
|
|
270
283
|
timer;
|
|
284
|
+
petsVisible = true;
|
|
271
285
|
register(id, win, skin, identity) {
|
|
272
286
|
this.pets.set(id, { id, win, skin, identity });
|
|
273
287
|
win.on("closed", () => this.pets.delete(id));
|
|
288
|
+
if (!this.petsVisible) win.hide();
|
|
274
289
|
if (!this.timer) this.timer = setInterval(() => this.checkProximity(), PROXIMITY_SCAN_MS);
|
|
275
290
|
logger.info("pet registered", { id, skin, total: this.pets.size });
|
|
276
291
|
}
|
|
@@ -282,6 +297,17 @@ class PetManager {
|
|
|
282
297
|
(pet) => !pet.win.isDestroyed() && pet.identity.kind === "primary"
|
|
283
298
|
);
|
|
284
299
|
}
|
|
300
|
+
arePetsVisible() {
|
|
301
|
+
return this.petsVisible;
|
|
302
|
+
}
|
|
303
|
+
setPetsVisible(visible) {
|
|
304
|
+
this.petsVisible = visible;
|
|
305
|
+
for (const pet of this.pets.values()) {
|
|
306
|
+
if (pet.win.isDestroyed()) continue;
|
|
307
|
+
if (visible) pet.win.showInactive();
|
|
308
|
+
else pet.win.hide();
|
|
309
|
+
}
|
|
310
|
+
}
|
|
285
311
|
findRemoteFriend(username) {
|
|
286
312
|
const normalized = username.trim().toLowerCase();
|
|
287
313
|
return [...this.pets.values()].find(
|
|
@@ -1653,7 +1679,7 @@ async function fallbackChat(operationId) {
|
|
|
1653
1679
|
const REQUEST_TIMEOUT_MS = 6e3;
|
|
1654
1680
|
const DOWNLOAD_TIMEOUT_MS = 10 * 6e4;
|
|
1655
1681
|
const LEGACY_OFFICIAL_HTTP_HOST = "47.94.20.104";
|
|
1656
|
-
const
|
|
1682
|
+
const MAC_BUNDLE_ID = "com.xinshu.desktoppet";
|
|
1657
1683
|
let status = { kind: "idle" };
|
|
1658
1684
|
function updateCheckURL() {
|
|
1659
1685
|
const override = process.env.DESKTOP_PET_UPDATE_API?.trim();
|
|
@@ -1691,6 +1717,12 @@ async function checkForUpdates(currentVersion = electron.app.getVersion()) {
|
|
|
1691
1717
|
status = { kind: "up-to-date", checkedAt: Date.now() };
|
|
1692
1718
|
return status;
|
|
1693
1719
|
}
|
|
1720
|
+
const cachedArtifactPath = await verifiedCachedArtifact(update);
|
|
1721
|
+
if (cachedArtifactPath) {
|
|
1722
|
+
status = { kind: "downloaded", update, artifactPath: cachedArtifactPath, downloadedAt: Date.now() };
|
|
1723
|
+
logger.info("using cached verified update", { version: update.version });
|
|
1724
|
+
return status;
|
|
1725
|
+
}
|
|
1694
1726
|
status = { kind: "available", update, checkedAt: Date.now() };
|
|
1695
1727
|
logger.info("update available", { currentVersion, version: update.version });
|
|
1696
1728
|
return status;
|
|
@@ -1717,7 +1749,7 @@ async function downloadAvailableUpdate() {
|
|
|
1717
1749
|
if (!available) return status;
|
|
1718
1750
|
status = { kind: "downloading", update: available, downloadedBytes: 0 };
|
|
1719
1751
|
const updateDirectory = node_path.join(electron.app.getPath("userData"), "updates");
|
|
1720
|
-
const artifactPath =
|
|
1752
|
+
const artifactPath = updateArtifactPath(available);
|
|
1721
1753
|
const temporaryPath = `${artifactPath}.${node_crypto.randomUUID()}.download`;
|
|
1722
1754
|
try {
|
|
1723
1755
|
await promises.mkdir(updateDirectory, { recursive: true });
|
|
@@ -1767,8 +1799,9 @@ async function installDownloadedUpdate() {
|
|
|
1767
1799
|
downloaded.artifactPath,
|
|
1768
1800
|
downloaded.update.sha256,
|
|
1769
1801
|
process.platform,
|
|
1770
|
-
|
|
1771
|
-
process.execPath
|
|
1802
|
+
downloaded.update.version,
|
|
1803
|
+
process.execPath,
|
|
1804
|
+
JSON.stringify(restartArgumentsAfterUpdate())
|
|
1772
1805
|
], {
|
|
1773
1806
|
detached: true,
|
|
1774
1807
|
stdio: "ignore",
|
|
@@ -1783,11 +1816,31 @@ async function installDownloadedUpdate() {
|
|
|
1783
1816
|
function supportsInAppInstall() {
|
|
1784
1817
|
return electron.app.isPackaged || process.env.DESKTOP_PET_NPM_DISTRIBUTION === "1";
|
|
1785
1818
|
}
|
|
1786
|
-
function
|
|
1787
|
-
|
|
1788
|
-
|
|
1819
|
+
function restartArgumentsAfterUpdate() {
|
|
1820
|
+
return !electron.app.isPackaged && process.argv[1] ? [process.argv[1]] : [];
|
|
1821
|
+
}
|
|
1822
|
+
function updateArtifactPath(update) {
|
|
1823
|
+
return node_path.join(electron.app.getPath("userData"), "updates", `desktop-pet-${update.version}${installerExtension(update.releaseUrl)}`);
|
|
1824
|
+
}
|
|
1825
|
+
async function verifiedCachedArtifact(update) {
|
|
1826
|
+
const artifactPath = updateArtifactPath(update);
|
|
1827
|
+
try {
|
|
1828
|
+
return await sha256File(artifactPath) === update.sha256 ? artifactPath : void 0;
|
|
1829
|
+
} catch (error) {
|
|
1830
|
+
if (error.code !== "ENOENT") {
|
|
1831
|
+
logger.warn("could not verify cached update", { version: update.version, error: String(error) });
|
|
1832
|
+
}
|
|
1833
|
+
return void 0;
|
|
1789
1834
|
}
|
|
1790
|
-
|
|
1835
|
+
}
|
|
1836
|
+
function sha256File(path2) {
|
|
1837
|
+
return new Promise((resolve, reject) => {
|
|
1838
|
+
const hash = node_crypto.createHash("sha256");
|
|
1839
|
+
const input = node_fs.createReadStream(path2);
|
|
1840
|
+
input.on("data", (chunk) => hash.update(chunk));
|
|
1841
|
+
input.on("error", reject);
|
|
1842
|
+
input.on("end", () => resolve(hash.digest("hex")));
|
|
1843
|
+
});
|
|
1791
1844
|
}
|
|
1792
1845
|
async function saveAndHash(body, destination, onChunk) {
|
|
1793
1846
|
const hash = node_crypto.createHash("sha256");
|
|
@@ -1838,8 +1891,9 @@ const { dirname, join } = require('node:path')
|
|
|
1838
1891
|
const { execFile, spawn } = require('node:child_process')
|
|
1839
1892
|
const { promisify } = require('node:util')
|
|
1840
1893
|
|
|
1841
|
-
const [, , parentPidText, sourcePath, expectedSHA256, platform,
|
|
1894
|
+
const [, , parentPidText, sourcePath, expectedSHA256, platform, targetVersion, fallbackExecutable, fallbackArgsJSON] = process.argv
|
|
1842
1895
|
const parentPid = Number(parentPidText)
|
|
1896
|
+
const fallbackArgs = JSON.parse(fallbackArgsJSON || '[]')
|
|
1843
1897
|
const run = promisify(execFile)
|
|
1844
1898
|
|
|
1845
1899
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
|
|
@@ -1853,10 +1907,35 @@ const sha256 = (path) => new Promise((resolve, reject) => {
|
|
|
1853
1907
|
input.on('error', reject)
|
|
1854
1908
|
input.on('end', () => resolve(hash.digest('hex')))
|
|
1855
1909
|
})
|
|
1856
|
-
const
|
|
1910
|
+
const restartFallback = () => {
|
|
1857
1911
|
const env = { ...process.env }
|
|
1858
1912
|
delete env.ELECTRON_RUN_AS_NODE
|
|
1859
|
-
spawn(
|
|
1913
|
+
const child = spawn(fallbackExecutable, fallbackArgs, { detached: true, stdio: 'ignore', env })
|
|
1914
|
+
child.on('error', () => undefined)
|
|
1915
|
+
child.unref()
|
|
1916
|
+
}
|
|
1917
|
+
const findInstalledMacApp = async () => {
|
|
1918
|
+
const query = 'kMDItemCFBundleIdentifier == "${MAC_BUNDLE_ID}"'
|
|
1919
|
+
const { stdout } = await run('/usr/bin/mdfind', [query])
|
|
1920
|
+
const candidates = stdout.split('\n').map((value) => value.trim()).filter((value) => value.endsWith('.app'))
|
|
1921
|
+
for (const candidate of candidates) {
|
|
1922
|
+
try {
|
|
1923
|
+
const plist = join(candidate, 'Contents', 'Info.plist')
|
|
1924
|
+
const result = await run('/usr/libexec/PlistBuddy', ['-c', 'Print :CFBundleShortVersionString', plist])
|
|
1925
|
+
if (result.stdout.trim() === targetVersion) return candidate
|
|
1926
|
+
} catch {}
|
|
1927
|
+
}
|
|
1928
|
+
throw new Error('installed app for target version was not found')
|
|
1929
|
+
}
|
|
1930
|
+
const restartInstalledApp = async () => {
|
|
1931
|
+
if (platform === 'darwin') {
|
|
1932
|
+
// 安装器可能把包重定位到已有开发构建,且机器上可能存在多个同 bundle ID 的应用。
|
|
1933
|
+
// 找到版本完全匹配的具体路径再启动,避免按 bundle ID 命中旧副本后静默失败。
|
|
1934
|
+
const installedApp = await findInstalledMacApp()
|
|
1935
|
+
await run('/usr/bin/open', ['-n', installedApp])
|
|
1936
|
+
return
|
|
1937
|
+
}
|
|
1938
|
+
restartFallback()
|
|
1860
1939
|
}
|
|
1861
1940
|
const shellQuote = (value) => "'" + value.replace(/'/g, "'\\''") + "'"
|
|
1862
1941
|
|
|
@@ -1885,11 +1964,20 @@ async function main() {
|
|
|
1885
1964
|
} catch (error) {
|
|
1886
1965
|
await fs.writeFile(join(dirname(sourcePath), 'last-install-error.log'), String(error)).catch(() => undefined)
|
|
1887
1966
|
} finally {
|
|
1888
|
-
|
|
1967
|
+
if (installed) {
|
|
1968
|
+
try {
|
|
1969
|
+
await restartInstalledApp()
|
|
1970
|
+
} catch (error) {
|
|
1971
|
+
await fs.writeFile(join(dirname(sourcePath), 'last-restart-error.log'), String(error)).catch(() => undefined)
|
|
1972
|
+
restartFallback()
|
|
1973
|
+
}
|
|
1974
|
+
} else {
|
|
1975
|
+
restartFallback()
|
|
1976
|
+
}
|
|
1889
1977
|
}
|
|
1890
1978
|
}
|
|
1891
1979
|
|
|
1892
|
-
main().catch(() =>
|
|
1980
|
+
main().catch(() => restartFallback())
|
|
1893
1981
|
`;
|
|
1894
1982
|
function installerExtension(releaseUrl) {
|
|
1895
1983
|
const pathname = new URL(releaseUrl).pathname.toLowerCase();
|
|
@@ -1999,6 +2087,7 @@ function createPetWindow(skin, options) {
|
|
|
1999
2087
|
const x = options.near?.x ?? workArea.x + workArea.width - W - 120;
|
|
2000
2088
|
const y = options.near?.y ?? workArea.y + workArea.height - H - 60;
|
|
2001
2089
|
const win = new electron.BrowserWindow({
|
|
2090
|
+
show: petManager.arePetsVisible(),
|
|
2002
2091
|
width: W,
|
|
2003
2092
|
height: H,
|
|
2004
2093
|
x,
|
|
@@ -2132,11 +2221,20 @@ function createPetWindow(skin, options) {
|
|
|
2132
2221
|
dispatch({ type: "bubble", text: result.message, ttl: 6e3 }, id);
|
|
2133
2222
|
}
|
|
2134
2223
|
},
|
|
2224
|
+
{
|
|
2225
|
+
label: "打开日志文件夹",
|
|
2226
|
+
click: () => {
|
|
2227
|
+
void electron.shell.openPath(mainLogDirectory()).then((error) => {
|
|
2228
|
+
if (error) dispatch({ type: "bubble", text: "无法打开日志文件夹", ttl: 6e3 }, id);
|
|
2229
|
+
});
|
|
2230
|
+
}
|
|
2231
|
+
},
|
|
2135
2232
|
{
|
|
2136
2233
|
label: updateMenuLabel(),
|
|
2137
2234
|
submenu: updateMenuItems(id)
|
|
2138
2235
|
},
|
|
2139
2236
|
{ type: "separator" },
|
|
2237
|
+
{ label: "隐藏所有桌面宠物", click: () => petManager.setPetsVisible(false) },
|
|
2140
2238
|
...canSendAwayPet(identity) ? [{ label: "送走这只宠物", click: () => win.close() }] : [],
|
|
2141
2239
|
{ label: "退出", click: () => electron.app.quit() }
|
|
2142
2240
|
]);
|
|
@@ -3046,6 +3144,39 @@ async function dutyLoop(adapter) {
|
|
|
3046
3144
|
}
|
|
3047
3145
|
}
|
|
3048
3146
|
}
|
|
3147
|
+
const trayIcon1xPath = path.join(__dirname, "./chunks/tray-iconTemplate-BIK86F-0.png");
|
|
3148
|
+
const trayIcon2xPath = path.join(__dirname, "./chunks/tray-iconTemplate@2x-B19HpcaE.png");
|
|
3149
|
+
let tray;
|
|
3150
|
+
function createMenuBar() {
|
|
3151
|
+
if (process.platform !== "darwin" || tray) return;
|
|
3152
|
+
tray = new electron.Tray(createPetTemplateImage());
|
|
3153
|
+
tray.setToolTip("Desktop Pet");
|
|
3154
|
+
tray.on("click", showMenu);
|
|
3155
|
+
tray.on("right-click", showMenu);
|
|
3156
|
+
}
|
|
3157
|
+
function destroyMenuBar() {
|
|
3158
|
+
tray?.destroy();
|
|
3159
|
+
tray = void 0;
|
|
3160
|
+
}
|
|
3161
|
+
function showMenu() {
|
|
3162
|
+
tray?.popUpContextMenu(electron.Menu.buildFromTemplate([
|
|
3163
|
+
{ label: `Desktop Pet v${electron.app.getVersion()}`, enabled: false },
|
|
3164
|
+
{ type: "separator" },
|
|
3165
|
+
{
|
|
3166
|
+
label: petManager.arePetsVisible() ? "隐藏所有桌面宠物" : "显示所有桌面宠物",
|
|
3167
|
+
click: () => petManager.setPetsVisible(!petManager.arePetsVisible())
|
|
3168
|
+
},
|
|
3169
|
+
{ type: "separator" },
|
|
3170
|
+
{ label: "退出 Desktop Pet", click: () => electron.app.quit() }
|
|
3171
|
+
]));
|
|
3172
|
+
}
|
|
3173
|
+
function createPetTemplateImage() {
|
|
3174
|
+
let image = electron.nativeImage.createFromBuffer(node_fs.readFileSync(trayIcon2xPath), { scaleFactor: 2 });
|
|
3175
|
+
if (image.isEmpty()) image = electron.nativeImage.createFromPath(trayIcon1xPath);
|
|
3176
|
+
if (image.isEmpty()) throw new Error("无法加载 macOS 菜单栏图标");
|
|
3177
|
+
image.setTemplateImage(true);
|
|
3178
|
+
return image;
|
|
3179
|
+
}
|
|
3049
3180
|
const profileDirectory = process.env.DESKTOP_PET_PROFILE?.trim();
|
|
3050
3181
|
if (profileDirectory) {
|
|
3051
3182
|
electron.app.setPath("userData", node_path.resolve(profileDirectory));
|
|
@@ -3058,6 +3189,7 @@ if (!singleInstance) {
|
|
|
3058
3189
|
electron.app.on("second-instance", (_event, argv) => {
|
|
3059
3190
|
const callback = argv.find((value) => value.startsWith("desktop-pet://"));
|
|
3060
3191
|
if (callback) handleAccountCallback(callback);
|
|
3192
|
+
petManager.setPetsVisible(true);
|
|
3061
3193
|
ensurePrimaryPet();
|
|
3062
3194
|
});
|
|
3063
3195
|
}
|
|
@@ -3068,6 +3200,7 @@ function ensurePrimaryPet() {
|
|
|
3068
3200
|
});
|
|
3069
3201
|
}
|
|
3070
3202
|
electron.app.whenReady().then(async () => {
|
|
3203
|
+
createMenuBar();
|
|
3071
3204
|
await startAccountCallbackRouter();
|
|
3072
3205
|
startEventServer();
|
|
3073
3206
|
const activated = await activateStoredDevice().catch((err) => {
|
|
@@ -3092,9 +3225,10 @@ electron.app.whenReady().then(async () => {
|
|
|
3092
3225
|
logger.info("app started", { firstPet: id });
|
|
3093
3226
|
});
|
|
3094
3227
|
electron.app.on("window-all-closed", () => {
|
|
3095
|
-
electron.app.quit();
|
|
3228
|
+
if (process.platform !== "darwin") electron.app.quit();
|
|
3096
3229
|
});
|
|
3097
3230
|
electron.app.on("quit", () => {
|
|
3231
|
+
destroyMenuBar();
|
|
3098
3232
|
stopAccountCallbackRouter();
|
|
3099
3233
|
stopRelay();
|
|
3100
3234
|
stopP2PWindow();
|