desktop-pet-app 0.2.9 → 0.3.3
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 +115 -6
- package/package.json +1 -1
package/out/main/index.js
CHANGED
|
@@ -1717,6 +1717,12 @@ async function checkForUpdates(currentVersion = electron.app.getVersion()) {
|
|
|
1717
1717
|
status = { kind: "up-to-date", checkedAt: Date.now() };
|
|
1718
1718
|
return status;
|
|
1719
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
|
+
}
|
|
1720
1726
|
status = { kind: "available", update, checkedAt: Date.now() };
|
|
1721
1727
|
logger.info("update available", { currentVersion, version: update.version });
|
|
1722
1728
|
return status;
|
|
@@ -1743,7 +1749,7 @@ async function downloadAvailableUpdate() {
|
|
|
1743
1749
|
if (!available) return status;
|
|
1744
1750
|
status = { kind: "downloading", update: available, downloadedBytes: 0 };
|
|
1745
1751
|
const updateDirectory = node_path.join(electron.app.getPath("userData"), "updates");
|
|
1746
|
-
const artifactPath =
|
|
1752
|
+
const artifactPath = updateArtifactPath(available);
|
|
1747
1753
|
const temporaryPath = `${artifactPath}.${node_crypto.randomUUID()}.download`;
|
|
1748
1754
|
try {
|
|
1749
1755
|
await promises.mkdir(updateDirectory, { recursive: true });
|
|
@@ -1793,6 +1799,7 @@ async function installDownloadedUpdate() {
|
|
|
1793
1799
|
downloaded.artifactPath,
|
|
1794
1800
|
downloaded.update.sha256,
|
|
1795
1801
|
process.platform,
|
|
1802
|
+
downloaded.update.version,
|
|
1796
1803
|
process.execPath,
|
|
1797
1804
|
JSON.stringify(restartArgumentsAfterUpdate())
|
|
1798
1805
|
], {
|
|
@@ -1812,6 +1819,29 @@ function supportsInAppInstall() {
|
|
|
1812
1819
|
function restartArgumentsAfterUpdate() {
|
|
1813
1820
|
return !electron.app.isPackaged && process.argv[1] ? [process.argv[1]] : [];
|
|
1814
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;
|
|
1834
|
+
}
|
|
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
|
+
});
|
|
1844
|
+
}
|
|
1815
1845
|
async function saveAndHash(body, destination, onChunk) {
|
|
1816
1846
|
const hash = node_crypto.createHash("sha256");
|
|
1817
1847
|
const meter = new node_stream.Transform({
|
|
@@ -1861,7 +1891,7 @@ const { dirname, join } = require('node:path')
|
|
|
1861
1891
|
const { execFile, spawn } = require('node:child_process')
|
|
1862
1892
|
const { promisify } = require('node:util')
|
|
1863
1893
|
|
|
1864
|
-
const [, , parentPidText, sourcePath, expectedSHA256, platform, fallbackExecutable, fallbackArgsJSON] = process.argv
|
|
1894
|
+
const [, , parentPidText, sourcePath, expectedSHA256, platform, targetVersion, fallbackExecutable, fallbackArgsJSON] = process.argv
|
|
1865
1895
|
const parentPid = Number(parentPidText)
|
|
1866
1896
|
const fallbackArgs = JSON.parse(fallbackArgsJSON || '[]')
|
|
1867
1897
|
const run = promisify(execFile)
|
|
@@ -1884,11 +1914,25 @@ const restartFallback = () => {
|
|
|
1884
1914
|
child.on('error', () => undefined)
|
|
1885
1915
|
child.unref()
|
|
1886
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
|
+
}
|
|
1887
1930
|
const restartInstalledApp = async () => {
|
|
1888
|
-
// 安装器可能会保留已有应用的位置(开发构建尤其如此),不能假设它在 /Applications。
|
|
1889
|
-
// 让 LaunchServices 按 bundle ID 查找刚安装的实际应用位置。
|
|
1890
1931
|
if (platform === 'darwin') {
|
|
1891
|
-
|
|
1932
|
+
// 安装器可能把包重定位到已有开发构建,且机器上可能存在多个同 bundle ID 的应用。
|
|
1933
|
+
// 找到版本完全匹配的具体路径再启动,避免按 bundle ID 命中旧副本后静默失败。
|
|
1934
|
+
const installedApp = await findInstalledMacApp()
|
|
1935
|
+
await run('/usr/bin/open', ['-n', installedApp])
|
|
1892
1936
|
return
|
|
1893
1937
|
}
|
|
1894
1938
|
restartFallback()
|
|
@@ -3100,6 +3144,67 @@ async function dutyLoop(adapter) {
|
|
|
3100
3144
|
}
|
|
3101
3145
|
}
|
|
3102
3146
|
}
|
|
3147
|
+
const TAKEOVER_TIMEOUT_MS = 5e3;
|
|
3148
|
+
const TAKEOVER_POLL_MS = 100;
|
|
3149
|
+
function takeOverRunningInstance() {
|
|
3150
|
+
if (process.platform === "win32") return false;
|
|
3151
|
+
const pid = readLockOwnerPid();
|
|
3152
|
+
if (!pid) return false;
|
|
3153
|
+
if (!processIsDesktopPet(pid)) {
|
|
3154
|
+
logger.warn("single-instance lock held by an unrelated process, refusing to take over", { pid });
|
|
3155
|
+
return false;
|
|
3156
|
+
}
|
|
3157
|
+
logger.info("taking over running instance", { pid });
|
|
3158
|
+
try {
|
|
3159
|
+
process.kill(pid, "SIGTERM");
|
|
3160
|
+
} catch {
|
|
3161
|
+
return true;
|
|
3162
|
+
}
|
|
3163
|
+
if (waitForExit(pid, TAKEOVER_TIMEOUT_MS)) return true;
|
|
3164
|
+
logger.warn("instance did not exit after SIGTERM, forcing", { pid });
|
|
3165
|
+
try {
|
|
3166
|
+
process.kill(pid, "SIGKILL");
|
|
3167
|
+
} catch {
|
|
3168
|
+
return true;
|
|
3169
|
+
}
|
|
3170
|
+
return waitForExit(pid, 1e3);
|
|
3171
|
+
}
|
|
3172
|
+
function readLockOwnerPid() {
|
|
3173
|
+
let target;
|
|
3174
|
+
try {
|
|
3175
|
+
target = node_fs.readlinkSync(node_path.join(electron.app.getPath("userData"), "SingletonLock"));
|
|
3176
|
+
} catch {
|
|
3177
|
+
return void 0;
|
|
3178
|
+
}
|
|
3179
|
+
const hostPrefix = `${node_os.hostname()}-`;
|
|
3180
|
+
if (!target.startsWith(hostPrefix)) return void 0;
|
|
3181
|
+
const pid = Number(target.slice(hostPrefix.length));
|
|
3182
|
+
return Number.isSafeInteger(pid) && pid > 0 ? pid : void 0;
|
|
3183
|
+
}
|
|
3184
|
+
function processIsDesktopPet(pid) {
|
|
3185
|
+
try {
|
|
3186
|
+
const command = node_child_process.execFileSync("/bin/ps", ["-p", String(pid), "-o", "command="], { encoding: "utf8" });
|
|
3187
|
+
return /desktop[- ]pet/i.test(command);
|
|
3188
|
+
} catch {
|
|
3189
|
+
return false;
|
|
3190
|
+
}
|
|
3191
|
+
}
|
|
3192
|
+
function waitForExit(pid, timeoutMs) {
|
|
3193
|
+
const deadline = Date.now() + timeoutMs;
|
|
3194
|
+
while (Date.now() < deadline) {
|
|
3195
|
+
if (!processAlive(pid)) return true;
|
|
3196
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, TAKEOVER_POLL_MS);
|
|
3197
|
+
}
|
|
3198
|
+
return !processAlive(pid);
|
|
3199
|
+
}
|
|
3200
|
+
function processAlive(pid) {
|
|
3201
|
+
try {
|
|
3202
|
+
process.kill(pid, 0);
|
|
3203
|
+
return true;
|
|
3204
|
+
} catch {
|
|
3205
|
+
return false;
|
|
3206
|
+
}
|
|
3207
|
+
}
|
|
3103
3208
|
const trayIcon1xPath = path.join(__dirname, "./chunks/tray-iconTemplate-BIK86F-0.png");
|
|
3104
3209
|
const trayIcon2xPath = path.join(__dirname, "./chunks/tray-iconTemplate@2x-B19HpcaE.png");
|
|
3105
3210
|
let tray;
|
|
@@ -3138,8 +3243,12 @@ if (profileDirectory) {
|
|
|
3138
3243
|
electron.app.setPath("userData", node_path.resolve(profileDirectory));
|
|
3139
3244
|
}
|
|
3140
3245
|
registerAccountProtocol();
|
|
3141
|
-
|
|
3246
|
+
let singleInstance = electron.app.requestSingleInstanceLock();
|
|
3247
|
+
if (!singleInstance && takeOverRunningInstance()) {
|
|
3248
|
+
singleInstance = electron.app.requestSingleInstanceLock();
|
|
3249
|
+
}
|
|
3142
3250
|
if (!singleInstance) {
|
|
3251
|
+
logger.warn("another instance is running, quitting");
|
|
3143
3252
|
electron.app.quit();
|
|
3144
3253
|
} else {
|
|
3145
3254
|
electron.app.on("second-instance", (_event, argv) => {
|