desktop-pet-app 0.2.4 → 0.2.7
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/README.md +1 -1
- package/bin/cli.js +2 -1
- package/out/main/index.js +80 -14
- package/package.json +1 -1
package/README.md
CHANGED
package/bin/cli.js
CHANGED
|
@@ -25,7 +25,8 @@ const appPath = path.join(__dirname, '..')
|
|
|
25
25
|
|
|
26
26
|
const child = spawn(electron, [appPath, ...process.argv.slice(2)], {
|
|
27
27
|
stdio: 'ignore',
|
|
28
|
-
detached: true
|
|
28
|
+
detached: true,
|
|
29
|
+
env: { ...process.env, DESKTOP_PET_NPM_DISTRIBUTION: '1' }
|
|
29
30
|
})
|
|
30
31
|
|
|
31
32
|
child.unref()
|
package/out/main/index.js
CHANGED
|
@@ -246,6 +246,9 @@ function sleep(ms) {
|
|
|
246
246
|
function isPetOnQuest(petId) {
|
|
247
247
|
return busy.has(petId);
|
|
248
248
|
}
|
|
249
|
+
function canSendAwayPet(identity) {
|
|
250
|
+
return identity.kind === "remote-friend";
|
|
251
|
+
}
|
|
249
252
|
function presentPetIdentity(identity, localUsername) {
|
|
250
253
|
if (identity.kind === "remote-friend") {
|
|
251
254
|
return {
|
|
@@ -274,6 +277,11 @@ class PetManager {
|
|
|
274
277
|
count() {
|
|
275
278
|
return this.pets.size;
|
|
276
279
|
}
|
|
280
|
+
hasPrimaryPet() {
|
|
281
|
+
return [...this.pets.values()].some(
|
|
282
|
+
(pet) => !pet.win.isDestroyed() && pet.identity.kind === "primary"
|
|
283
|
+
);
|
|
284
|
+
}
|
|
277
285
|
findRemoteFriend(username) {
|
|
278
286
|
const normalized = username.trim().toLowerCase();
|
|
279
287
|
return [...this.pets.values()].find(
|
|
@@ -1645,6 +1653,7 @@ async function fallbackChat(operationId) {
|
|
|
1645
1653
|
const REQUEST_TIMEOUT_MS = 6e3;
|
|
1646
1654
|
const DOWNLOAD_TIMEOUT_MS = 10 * 6e4;
|
|
1647
1655
|
const LEGACY_OFFICIAL_HTTP_HOST = "47.94.20.104";
|
|
1656
|
+
const MAC_INSTALLED_APP_EXECUTABLE = "/Applications/Desktop Pet.app/Contents/MacOS/Desktop Pet";
|
|
1648
1657
|
let status = { kind: "idle" };
|
|
1649
1658
|
function updateCheckURL() {
|
|
1650
1659
|
const override = process.env.DESKTOP_PET_UPDATE_API?.trim();
|
|
@@ -1661,6 +1670,7 @@ function getUpdateStatus() {
|
|
|
1661
1670
|
return status;
|
|
1662
1671
|
}
|
|
1663
1672
|
async function checkForUpdates(currentVersion = electron.app.getVersion()) {
|
|
1673
|
+
if (status.kind === "downloading") return status;
|
|
1664
1674
|
status = { kind: "checking" };
|
|
1665
1675
|
try {
|
|
1666
1676
|
const endpoint = new URL(updateCheckURL());
|
|
@@ -1705,7 +1715,7 @@ function updateCheckHeaders(endpoint) {
|
|
|
1705
1715
|
async function downloadAvailableUpdate() {
|
|
1706
1716
|
const available = status.kind === "available" ? status.update : void 0;
|
|
1707
1717
|
if (!available) return status;
|
|
1708
|
-
status = { kind: "downloading", update: available };
|
|
1718
|
+
status = { kind: "downloading", update: available, downloadedBytes: 0 };
|
|
1709
1719
|
const updateDirectory = node_path.join(electron.app.getPath("userData"), "updates");
|
|
1710
1720
|
const artifactPath = node_path.join(updateDirectory, `desktop-pet-${available.version}${installerExtension(available.releaseUrl)}`);
|
|
1711
1721
|
const temporaryPath = `${artifactPath}.${node_crypto.randomUUID()}.download`;
|
|
@@ -1722,7 +1732,13 @@ async function downloadAvailableUpdate() {
|
|
|
1722
1732
|
if (response.url && new URL(response.url).protocol !== "https:") {
|
|
1723
1733
|
throw new Error("更新下载地址必须使用 HTTPS");
|
|
1724
1734
|
}
|
|
1725
|
-
const
|
|
1735
|
+
const totalBytes = parseContentLength(response.headers.get("content-length"));
|
|
1736
|
+
let downloadedBytes = 0;
|
|
1737
|
+
status = { kind: "downloading", update: available, downloadedBytes, totalBytes };
|
|
1738
|
+
const digest = await saveAndHash(response.body, temporaryPath, (chunkBytes) => {
|
|
1739
|
+
downloadedBytes += chunkBytes;
|
|
1740
|
+
status = { kind: "downloading", update: available, downloadedBytes, totalBytes };
|
|
1741
|
+
});
|
|
1726
1742
|
if (digest !== available.sha256) throw new Error("更新包校验失败,文件未安装");
|
|
1727
1743
|
await promises.rm(artifactPath, { force: true });
|
|
1728
1744
|
await moveFile(temporaryPath, artifactPath);
|
|
@@ -1739,7 +1755,7 @@ async function downloadAvailableUpdate() {
|
|
|
1739
1755
|
}
|
|
1740
1756
|
async function installDownloadedUpdate() {
|
|
1741
1757
|
if (status.kind !== "downloaded") return void 0;
|
|
1742
|
-
if (!
|
|
1758
|
+
if (!supportsInAppInstall()) throw new Error("当前运行方式不支持应用内安装,请使用正式发布版");
|
|
1743
1759
|
if (process.platform !== "darwin" && process.platform !== "win32") {
|
|
1744
1760
|
throw new Error("当前平台暂不支持应用内安装,请下载对应安装包更新");
|
|
1745
1761
|
}
|
|
@@ -1751,6 +1767,7 @@ async function installDownloadedUpdate() {
|
|
|
1751
1767
|
downloaded.artifactPath,
|
|
1752
1768
|
downloaded.update.sha256,
|
|
1753
1769
|
process.platform,
|
|
1770
|
+
restartExecutableAfterUpdate(),
|
|
1754
1771
|
process.execPath
|
|
1755
1772
|
], {
|
|
1756
1773
|
detached: true,
|
|
@@ -1763,11 +1780,21 @@ async function installDownloadedUpdate() {
|
|
|
1763
1780
|
electron.app.quit();
|
|
1764
1781
|
return downloaded.update;
|
|
1765
1782
|
}
|
|
1766
|
-
|
|
1783
|
+
function supportsInAppInstall() {
|
|
1784
|
+
return electron.app.isPackaged || process.env.DESKTOP_PET_NPM_DISTRIBUTION === "1";
|
|
1785
|
+
}
|
|
1786
|
+
function restartExecutableAfterUpdate() {
|
|
1787
|
+
if (!electron.app.isPackaged && process.env.DESKTOP_PET_NPM_DISTRIBUTION === "1" && process.platform === "darwin") {
|
|
1788
|
+
return MAC_INSTALLED_APP_EXECUTABLE;
|
|
1789
|
+
}
|
|
1790
|
+
return process.execPath;
|
|
1791
|
+
}
|
|
1792
|
+
async function saveAndHash(body, destination, onChunk) {
|
|
1767
1793
|
const hash = node_crypto.createHash("sha256");
|
|
1768
1794
|
const meter = new node_stream.Transform({
|
|
1769
1795
|
transform(chunk, _encoding, done) {
|
|
1770
1796
|
hash.update(chunk);
|
|
1797
|
+
onChunk(chunk.length);
|
|
1771
1798
|
done(null, chunk);
|
|
1772
1799
|
}
|
|
1773
1800
|
});
|
|
@@ -1778,6 +1805,23 @@ async function saveAndHash(body, destination) {
|
|
|
1778
1805
|
);
|
|
1779
1806
|
return hash.digest("hex");
|
|
1780
1807
|
}
|
|
1808
|
+
function parseContentLength(value) {
|
|
1809
|
+
if (!value) return void 0;
|
|
1810
|
+
const bytes = Number(value);
|
|
1811
|
+
return Number.isSafeInteger(bytes) && bytes > 0 ? bytes : void 0;
|
|
1812
|
+
}
|
|
1813
|
+
function downloadProgressText(progress) {
|
|
1814
|
+
if (progress.totalBytes) {
|
|
1815
|
+
const percent = Math.min(100, Math.floor(progress.downloadedBytes / progress.totalBytes * 100));
|
|
1816
|
+
return `${percent}%`;
|
|
1817
|
+
}
|
|
1818
|
+
return formatBytes(progress.downloadedBytes);
|
|
1819
|
+
}
|
|
1820
|
+
function formatBytes(bytes) {
|
|
1821
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
1822
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
1823
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
1824
|
+
}
|
|
1781
1825
|
async function moveFile(source, destination) {
|
|
1782
1826
|
const { rename } = await import("node:fs/promises");
|
|
1783
1827
|
await rename(source, destination);
|
|
@@ -1794,7 +1838,7 @@ const { dirname, join } = require('node:path')
|
|
|
1794
1838
|
const { execFile, spawn } = require('node:child_process')
|
|
1795
1839
|
const { promisify } = require('node:util')
|
|
1796
1840
|
|
|
1797
|
-
const [, , parentPidText, sourcePath, expectedSHA256, platform,
|
|
1841
|
+
const [, , parentPidText, sourcePath, expectedSHA256, platform, installedAppExecutable, fallbackExecutable] = process.argv
|
|
1798
1842
|
const parentPid = Number(parentPidText)
|
|
1799
1843
|
const run = promisify(execFile)
|
|
1800
1844
|
|
|
@@ -1809,7 +1853,11 @@ const sha256 = (path) => new Promise((resolve, reject) => {
|
|
|
1809
1853
|
input.on('error', reject)
|
|
1810
1854
|
input.on('end', () => resolve(hash.digest('hex')))
|
|
1811
1855
|
})
|
|
1812
|
-
const restart = () =>
|
|
1856
|
+
const restart = (executable) => {
|
|
1857
|
+
const env = { ...process.env }
|
|
1858
|
+
delete env.ELECTRON_RUN_AS_NODE
|
|
1859
|
+
spawn(executable, [], { detached: true, stdio: 'ignore', env }).unref()
|
|
1860
|
+
}
|
|
1813
1861
|
const shellQuote = (value) => "'" + value.replace(/'/g, "'\\''") + "'"
|
|
1814
1862
|
|
|
1815
1863
|
async function install() {
|
|
@@ -1829,17 +1877,19 @@ async function install() {
|
|
|
1829
1877
|
async function main() {
|
|
1830
1878
|
while (parentIsRunning()) await sleep(200)
|
|
1831
1879
|
|
|
1880
|
+
let installed = false
|
|
1832
1881
|
try {
|
|
1833
1882
|
if (await sha256(sourcePath) !== expectedSHA256) throw new Error('downloaded update hash changed')
|
|
1834
1883
|
await install()
|
|
1884
|
+
installed = true
|
|
1835
1885
|
} catch (error) {
|
|
1836
1886
|
await fs.writeFile(join(dirname(sourcePath), 'last-install-error.log'), String(error)).catch(() => undefined)
|
|
1837
1887
|
} finally {
|
|
1838
|
-
restart()
|
|
1888
|
+
restart(installed ? installedAppExecutable : fallbackExecutable)
|
|
1839
1889
|
}
|
|
1840
1890
|
}
|
|
1841
1891
|
|
|
1842
|
-
main().catch(() => restart())
|
|
1892
|
+
main().catch(() => restart(fallbackExecutable))
|
|
1843
1893
|
`;
|
|
1844
1894
|
function installerExtension(releaseUrl) {
|
|
1845
1895
|
const pathname = new URL(releaseUrl).pathname.toLowerCase();
|
|
@@ -2087,7 +2137,7 @@ function createPetWindow(skin, options) {
|
|
|
2087
2137
|
submenu: updateMenuItems(id)
|
|
2088
2138
|
},
|
|
2089
2139
|
{ type: "separator" },
|
|
2090
|
-
{ label: "送走这只宠物", click: () => win.close() },
|
|
2140
|
+
...canSendAwayPet(identity) ? [{ label: "送走这只宠物", click: () => win.close() }] : [],
|
|
2091
2141
|
{ label: "退出", click: () => electron.app.quit() }
|
|
2092
2142
|
]);
|
|
2093
2143
|
menu.popup({ window: win });
|
|
@@ -2107,7 +2157,9 @@ function createPetWindow(skin, options) {
|
|
|
2107
2157
|
function updateMenuLabel() {
|
|
2108
2158
|
const status2 = getUpdateStatus();
|
|
2109
2159
|
if (status2.kind === "available") return `软件更新(v${status2.update.version} 可下载)`;
|
|
2110
|
-
if (status2.kind === "downloading")
|
|
2160
|
+
if (status2.kind === "downloading") {
|
|
2161
|
+
return `软件更新(v${status2.update.version} 下载 ${downloadProgressText(status2)})`;
|
|
2162
|
+
}
|
|
2111
2163
|
if (status2.kind === "downloaded") return `软件更新(v${status2.update.version} 已下载)`;
|
|
2112
2164
|
return "软件更新";
|
|
2113
2165
|
}
|
|
@@ -2123,6 +2175,12 @@ function updateMenuItems(petId) {
|
|
|
2123
2175
|
dispatch({ type: "bubble", text: `发现 v${result.update.version},可从“软件更新”安装`, ttl: 6e3 }, petId);
|
|
2124
2176
|
} else if (result.kind === "up-to-date") {
|
|
2125
2177
|
dispatch({ type: "bubble", text: "已经是最新版本啦", ttl: 4500 }, petId);
|
|
2178
|
+
} else if (result.kind === "downloading") {
|
|
2179
|
+
dispatch({
|
|
2180
|
+
type: "bubble",
|
|
2181
|
+
text: `v${result.update.version} 正在下载:${downloadProgressText(result)}`,
|
|
2182
|
+
ttl: 4500
|
|
2183
|
+
}, petId);
|
|
2126
2184
|
} else if (result.kind === "error") {
|
|
2127
2185
|
dispatch({ type: "bubble", text: result.message, ttl: 7e3 }, petId);
|
|
2128
2186
|
}
|
|
@@ -2146,7 +2204,10 @@ function updateMenuItems(petId) {
|
|
|
2146
2204
|
});
|
|
2147
2205
|
}
|
|
2148
2206
|
if (status2.kind === "downloading") {
|
|
2149
|
-
items.splice(1, 0, {
|
|
2207
|
+
items.splice(1, 0, {
|
|
2208
|
+
label: `正在下载 v${status2.update.version} · ${downloadProgressText(status2)}`,
|
|
2209
|
+
enabled: false
|
|
2210
|
+
});
|
|
2150
2211
|
}
|
|
2151
2212
|
if (status2.kind === "downloaded") {
|
|
2152
2213
|
items.splice(1, 0, {
|
|
@@ -2997,6 +3058,13 @@ if (!singleInstance) {
|
|
|
2997
3058
|
electron.app.on("second-instance", (_event, argv) => {
|
|
2998
3059
|
const callback = argv.find((value) => value.startsWith("desktop-pet://"));
|
|
2999
3060
|
if (callback) handleAccountCallback(callback);
|
|
3061
|
+
ensurePrimaryPet();
|
|
3062
|
+
});
|
|
3063
|
+
}
|
|
3064
|
+
function ensurePrimaryPet() {
|
|
3065
|
+
if (petManager.hasPrimaryPet()) return null;
|
|
3066
|
+
return createPetWindow(process.env.DESKTOP_PET_SKIN || "codex", {
|
|
3067
|
+
identity: { kind: "primary" }
|
|
3000
3068
|
});
|
|
3001
3069
|
}
|
|
3002
3070
|
electron.app.whenReady().then(async () => {
|
|
@@ -3009,9 +3077,7 @@ electron.app.whenReady().then(async () => {
|
|
|
3009
3077
|
if (!activated) startRelay();
|
|
3010
3078
|
if (process.env.DESKTOP_PET_P2P !== "0") startP2PWindow(getDeviceIceServers());
|
|
3011
3079
|
void startRunnerManager();
|
|
3012
|
-
const id =
|
|
3013
|
-
identity: { kind: "primary" }
|
|
3014
|
-
});
|
|
3080
|
+
const id = ensurePrimaryPet();
|
|
3015
3081
|
setTimeout(() => {
|
|
3016
3082
|
void checkForUpdates().then((result) => {
|
|
3017
3083
|
if (id && result.kind === "available") {
|