@zhangferry-dev/tokendash 1.6.1 → 1.6.2

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.
Files changed (61) hide show
  1. package/README.md +146 -83
  2. package/dist/client/assets/index-Bw503sNp.css +1 -0
  3. package/dist/client/index.html +2 -2
  4. package/dist/daemon.cjs +3306 -0
  5. package/dist/daemon.cjs.map +7 -0
  6. package/dist/electron-server.cjs +1019 -28
  7. package/dist/electron-server.cjs.map +4 -4
  8. package/dist/server/ccusage.d.ts +7 -0
  9. package/dist/server/ccusage.js +69 -0
  10. package/dist/server/daemon.d.ts +12 -0
  11. package/dist/server/daemon.js +176 -0
  12. package/dist/server/index.js +22 -11
  13. package/dist/server/insightsCalculator.d.ts +15 -0
  14. package/dist/server/insightsCalculator.js +276 -0
  15. package/dist/server/quota/adapter.d.ts +47 -0
  16. package/dist/server/quota/adapter.js +41 -0
  17. package/dist/server/quota/adapters/claude.d.ts +2 -0
  18. package/dist/server/quota/adapters/claude.js +124 -0
  19. package/dist/server/quota/adapters/codex.d.ts +2 -0
  20. package/dist/server/quota/adapters/codex.js +188 -0
  21. package/dist/server/quota/adapters/glm.d.ts +2 -0
  22. package/dist/server/quota/adapters/glm.js +133 -0
  23. package/dist/server/quota/adapters/kimi.d.ts +2 -0
  24. package/dist/server/quota/adapters/kimi.js +184 -0
  25. package/dist/server/quota/adapters/minimax.d.ts +2 -0
  26. package/dist/server/quota/adapters/minimax.js +77 -0
  27. package/dist/server/quota/cache.d.ts +20 -0
  28. package/dist/server/quota/cache.js +44 -0
  29. package/dist/server/quota/credentialsFile.d.ts +13 -0
  30. package/dist/server/quota/credentialsFile.js +23 -0
  31. package/dist/server/quota/helpers.d.ts +39 -0
  32. package/dist/server/quota/helpers.js +93 -0
  33. package/dist/server/quota/index.d.ts +5 -0
  34. package/dist/server/quota/index.js +23 -0
  35. package/dist/server/quota/quotaService.d.ts +37 -0
  36. package/dist/server/quota/quotaService.js +141 -0
  37. package/dist/server/quota/schemas.d.ts +358 -0
  38. package/dist/server/quota/schemas.js +53 -0
  39. package/dist/server/quota/types.d.ts +65 -0
  40. package/dist/server/quota/types.js +10 -0
  41. package/dist/server/routes/api.js +15 -0
  42. package/dist/server/routes/insights.d.ts +2 -0
  43. package/dist/server/routes/insights.js +155 -0
  44. package/package.json +6 -10
  45. package/resources/entitlements.mac.plist +10 -0
  46. package/resources/icon-1024.png +0 -0
  47. package/resources/icon.icns +0 -0
  48. package/resources/icon.png +0 -0
  49. package/resources/product_menu.png +0 -0
  50. package/resources/readme-hero.png +0 -0
  51. package/dist/client/assets/index-_yA9tOzZ.css +0 -1
  52. package/electron/main.cjs +0 -516
  53. package/electron/npmSync.cjs +0 -62
  54. package/electron/preload.cjs +0 -36
  55. package/electron/serverReuse.cjs +0 -59
  56. package/electron/trayBadge.cjs +0 -27
  57. package/electron/trayHelper +0 -0
  58. package/electron/trayHelper.swift +0 -152
  59. package/electron/updateService.cjs +0 -220
  60. package/electron-builder.yml +0 -20
  61. /package/dist/client/assets/{index-CY4G_b0x.js → index-C913wKtU.js} +0 -0
@@ -1,62 +0,0 @@
1
- const { spawn } = require('node:child_process');
2
-
3
- function normalizeVersion(version) {
4
- return String(version || '').trim().replace(/^v/, '');
5
- }
6
-
7
- function shouldInstallPackage(installedVersion, targetVersion) {
8
- const installed = normalizeVersion(installedVersion);
9
- const target = normalizeVersion(targetVersion);
10
- return Boolean(target) && installed !== target;
11
- }
12
-
13
- function buildNpmInstallArgs(packageName, version) {
14
- return ['install', '-g', `${packageName}@${version}`];
15
- }
16
-
17
- function runCommand(command, args) {
18
- return new Promise((resolve) => {
19
- const child = spawn(command, args, { stdio: ['ignore', 'pipe', 'pipe'] });
20
- let stdout = '';
21
- let stderr = '';
22
-
23
- child.stdout.on('data', (chunk) => { stdout += chunk.toString(); });
24
- child.stderr.on('data', (chunk) => { stderr += chunk.toString(); });
25
- child.on('error', (error) => resolve({ ok: false, stdout, stderr, error }));
26
- child.on('close', (code) => resolve({ ok: code === 0, code, stdout, stderr }));
27
- });
28
- }
29
-
30
- async function getInstalledPackageVersion(packageName) {
31
- const result = await runCommand('npm', ['list', '-g', packageName, '--depth=0', '--json']);
32
- if (!result.ok) return null;
33
- try {
34
- const data = JSON.parse(result.stdout);
35
- return normalizeVersion(data && data.dependencies && data.dependencies[packageName] && data.dependencies[packageName].version);
36
- } catch (_) {
37
- return null;
38
- }
39
- }
40
-
41
- async function syncNpmPackageVersion(packageName, version) {
42
- const installedVersion = await getInstalledPackageVersion(packageName);
43
- if (!shouldInstallPackage(installedVersion, version)) {
44
- return { ok: true, installedVersion, targetVersion: version, changed: false };
45
- }
46
-
47
- const result = await runCommand('npm', buildNpmInstallArgs(packageName, version));
48
- return {
49
- ok: result.ok,
50
- installedVersion,
51
- targetVersion: version,
52
- changed: result.ok,
53
- error: result.ok ? null : (result.error ? result.error.message : result.stderr || `npm exited with ${result.code}`),
54
- };
55
- }
56
-
57
- module.exports = {
58
- buildNpmInstallArgs,
59
- getInstalledPackageVersion,
60
- shouldInstallPackage,
61
- syncNpmPackageVersion,
62
- };
@@ -1,36 +0,0 @@
1
- const { contextBridge, ipcRenderer } = require('electron');
2
-
3
- contextBridge.exposeInMainWorld('electronAPI', {
4
- openDashboard(url) {
5
- return ipcRenderer.invoke('tokendash:open-dashboard', url);
6
- },
7
- getAppInfo() {
8
- return ipcRenderer.invoke('tokendash:get-app-info');
9
- },
10
- setLaunchAtLogin(enabled) {
11
- return ipcRenderer.invoke('tokendash:set-launch-at-login', enabled);
12
- },
13
- checkForUpdates() {
14
- return ipcRenderer.invoke('tokendash:check-for-updates');
15
- },
16
- downloadUpdate(updateInfo) {
17
- return ipcRenderer.invoke('tokendash:download-update', updateInfo);
18
- },
19
- onUpdateDownloadProgress(callback) {
20
- if (typeof callback !== 'function') return function noop() {};
21
- const listener = (_event, progress) => callback(progress);
22
- ipcRenderer.on('tokendash:update-download-progress', listener);
23
- return function unsubscribe() {
24
- ipcRenderer.removeListener('tokendash:update-download-progress', listener);
25
- };
26
- },
27
- quitApp() {
28
- return ipcRenderer.invoke('tokendash:quit');
29
- },
30
- setSelectedAgents(agents) {
31
- return ipcRenderer.invoke('tokendash:set-selected-agents', agents);
32
- },
33
- updateTraySnapshot(snapshot) {
34
- return ipcRenderer.invoke('tokendash:update-tray-snapshot', snapshot);
35
- },
36
- });
@@ -1,59 +0,0 @@
1
- const http = require('node:http');
2
-
3
- function normalizePort(port) {
4
- const value = parseInt(String(port || ''), 10);
5
- return Number.isInteger(value) && value > 0 ? value : 3456;
6
- }
7
-
8
- function getDashboardUrl(port) {
9
- return `http://localhost:${normalizePort(port)}`;
10
- }
11
-
12
- function isCompatibleServerInfo(info, expectedVersion, expectedPackageName) {
13
- return Boolean(
14
- info &&
15
- info.packageName === expectedPackageName &&
16
- String(info.version || '').replace(/^v/, '') === String(expectedVersion || '').replace(/^v/, '')
17
- );
18
- }
19
-
20
- function fetchJson(url, timeoutMs = 1000) {
21
- return new Promise((resolve, reject) => {
22
- const req = http.get(url, (res) => {
23
- let data = '';
24
- res.on('data', (chunk) => { data += chunk; });
25
- res.on('end', () => {
26
- if (res.statusCode && res.statusCode >= 400) {
27
- reject(new Error(`HTTP ${res.statusCode}`));
28
- return;
29
- }
30
- try { resolve(JSON.parse(data)); }
31
- catch (error) { reject(error); }
32
- });
33
- });
34
-
35
- req.setTimeout(timeoutMs, () => {
36
- req.destroy(new Error('Request timed out'));
37
- });
38
- req.on('error', reject);
39
- });
40
- }
41
-
42
- async function findCompatibleServer(preferredPort, expectedVersion, expectedPackageName) {
43
- const port = normalizePort(preferredPort);
44
- try {
45
- const info = await fetchJson(`${getDashboardUrl(port)}/api/app-info`);
46
- if (isCompatibleServerInfo(info, expectedVersion, expectedPackageName)) {
47
- return { port, dashboardUrl: info.dashboardUrl || getDashboardUrl(port), info };
48
- }
49
- } catch (_) {}
50
- return null;
51
- }
52
-
53
- module.exports = {
54
- fetchJson,
55
- findCompatibleServer,
56
- getDashboardUrl,
57
- isCompatibleServerInfo,
58
- normalizePort,
59
- };
@@ -1,27 +0,0 @@
1
- // electron/trayBadge.cjs
2
-
3
- /**
4
- * Format token count as compact string for tray badge.
5
- * Examples: 1234 -> "1.2K", 567890 -> "567.9K", 1500000 -> "1.5M"
6
- */
7
- function formatTokens(tokens) {
8
- tokens = Number(tokens) || 0;
9
-
10
- if (tokens >= 1e6) return (tokens / 1e6).toFixed(1) + 'M';
11
- if (tokens >= 1e3) return (tokens / 1e3).toFixed(1) + 'K';
12
- if (tokens > 0) return String(tokens);
13
- return '0';
14
- }
15
-
16
- /**
17
- * Format cost as compact string for tray badge (max 5 chars).
18
- * Examples: 1.234 -> "$1.2", 12.5 -> "$12", 0.05 -> "$0.1", 123.4 -> "$123"
19
- */
20
- function formatCost(cost) {
21
- if (cost < 0.05) return '$0';
22
- if (cost < 10) return '$' + cost.toFixed(1);
23
- if (cost < 100) return '$' + Math.round(cost);
24
- return '$' + Math.round(cost);
25
- }
26
-
27
- module.exports = { formatCost, formatTokens };
Binary file
@@ -1,152 +0,0 @@
1
- import Cocoa
2
-
3
- // TokenDash Native Tray Helper for macOS 26+
4
- // Communicates with Electron main process via stdin/stdout
5
- // Protocol:
6
- // stdin commands: "title:<text>\n" "tooltip:<text>\n" "quit\n"
7
- // stdout events: "click:<screenX>,<screenY>\n"
8
-
9
- class AppDelegate: NSObject, NSApplicationDelegate {
10
- var statusItem: NSStatusItem!
11
- var readHandle: FileHandle?
12
- var currentTitle = "0"
13
-
14
- func applicationDidFinishLaunching(_ notification: Notification) {
15
- // Create status bar item
16
- statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
17
-
18
- statusItem.button?.image = renderCombinedImage(title: currentTitle)
19
- statusItem.button?.imagePosition = .imageOnly
20
- statusItem.button?.imageScaling = .scaleProportionallyDown
21
- statusItem.button?.isBordered = false
22
- statusItem.button?.title = ""
23
- statusItem.button?.toolTip = "TokenDash"
24
-
25
- // Set up click actions — both left and right click
26
- statusItem.button?.target = self
27
- statusItem.button?.action = #selector(handleClick(_:))
28
- statusItem.button?.sendAction(on: [.leftMouseUp, .rightMouseUp])
29
-
30
- // Read commands from stdin
31
- readHandle = FileHandle.standardInput
32
- NotificationCenter.default.addObserver(
33
- self,
34
- selector: #selector(handleStdin),
35
- name: .NSFileHandleDataAvailable,
36
- object: readHandle
37
- )
38
- readHandle?.waitForDataInBackgroundAndNotify()
39
-
40
- // Signal ready
41
- sendEvent("ready")
42
- }
43
-
44
- /// Render icon + title text into a single template image for the status bar.
45
- func renderCombinedImage(title: String) -> NSImage {
46
- let iconW: CGFloat = 18
47
- let iconH: CGFloat = 18
48
- let fontSize: CGFloat = 13
49
- let font = NSFont.monospacedSystemFont(ofSize: fontSize, weight: .medium)
50
- let textAttrs: [NSAttributedString.Key: Any] = [.font: font]
51
- let textWidth = (title as NSString).size(withAttributes: textAttrs).width
52
- let padding: CGFloat = 4 // gap between icon and text
53
-
54
- let totalWidth = iconW + padding + textWidth
55
- // Status bar height is ~22pt; center vertically
56
- let totalHeight: CGFloat = 20
57
-
58
- let image = NSImage(size: NSSize(width: totalWidth, height: totalHeight))
59
- image.lockFocus()
60
-
61
- // Draw icon centered vertically
62
- let icon = createTemplateIcon(size: NSSize(width: iconW, height: iconH))
63
- let iconY = (totalHeight - iconH) / 2.0
64
- icon.draw(in: NSRect(x: 0, y: iconY, width: iconW, height: iconH))
65
-
66
- // Draw text centered vertically (baseline-adjusted)
67
- let textY = (totalHeight - fontSize) / 2.0 - 1
68
- (title as NSString).draw(at: NSPoint(x: iconW + padding, y: textY), withAttributes: textAttrs)
69
-
70
- image.unlockFocus()
71
- image.isTemplate = true
72
- return image
73
- }
74
-
75
- @objc func handleClick(_ sender: Any?) {
76
- guard let _ = NSApp.currentEvent else { return }
77
- let loc = NSEvent.mouseLocation
78
- sendEvent("click:\(Int(loc.x)),\(Int(loc.y))")
79
- }
80
-
81
- @objc func handleStdin() {
82
- guard let data = readHandle?.availableData, data.count > 0 else {
83
- readHandle?.waitForDataInBackgroundAndNotify()
84
- return
85
- }
86
-
87
- if let line = String(data: data, encoding: .utf8) {
88
- for command in line.split(separator: "\n") {
89
- let cmd = command.trimmingCharacters(in: .whitespacesAndNewlines)
90
- if cmd.hasPrefix("title:") {
91
- let title = String(cmd.dropFirst(6))
92
- currentTitle = title
93
- statusItem.button?.image = renderCombinedImage(title: title)
94
- } else if cmd.hasPrefix("tooltip:") {
95
- let tooltip = String(cmd.dropFirst(8))
96
- statusItem.button?.toolTip = tooltip
97
- } else if cmd == "quit" {
98
- NSApp.terminate(nil)
99
- return
100
- }
101
- }
102
- }
103
-
104
- readHandle?.waitForDataInBackgroundAndNotify()
105
- }
106
-
107
- func sendEvent(_ event: String) {
108
- print(event)
109
- fflush(stdout)
110
- }
111
-
112
- func createTemplateIcon(size: NSSize) -> NSImage {
113
- let image = NSImage(size: size)
114
- image.lockFocus()
115
-
116
- let sx = size.width / 64.0
117
- let sy = size.height / 64.0
118
-
119
- let path = NSBezierPath()
120
- path.move(to: NSPoint(x: 6 * sx, y: (64 - 32) * sy))
121
- path.line(to: NSPoint(x: 18 * sx, y: (64 - 32) * sy))
122
- path.curve(to: NSPoint(x: 24.5 * sx, y: (64 - 39) * sy),
123
- controlPoint1: NSPoint(x: 21 * sx, y: (64 - 32) * sy),
124
- controlPoint2: NSPoint(x: 22.5 * sx, y: (64 - 34) * sy))
125
- path.curve(to: NSPoint(x: 34 * sx, y: (64 - 50) * sy),
126
- controlPoint1: NSPoint(x: 27 * sx, y: (64 - 45.5) * sy),
127
- controlPoint2: NSPoint(x: 30 * sx, y: (64 - 50) * sy))
128
- path.curve(to: NSPoint(x: 44 * sx, y: (64 - 22) * sy),
129
- controlPoint1: NSPoint(x: 38 * sx, y: (64 - 50) * sy),
130
- controlPoint2: NSPoint(x: 40.5 * sx, y: (64 - 42) * sy))
131
- path.curve(to: NSPoint(x: 52 * sx, y: (64 - 8) * sy),
132
- controlPoint1: NSPoint(x: 46 * sx, y: (64 - 11) * sy),
133
- controlPoint2: NSPoint(x: 49 * sx, y: (64 - 8) * sy))
134
- path.curve(to: NSPoint(x: 60 * sx, y: (64 - 22) * sy),
135
- controlPoint1: NSPoint(x: 55 * sx, y: (64 - 8) * sy),
136
- controlPoint2: NSPoint(x: 57.5 * sx, y: (64 - 13) * sy))
137
-
138
- path.lineWidth = 5 * sx
139
- path.lineCapStyle = .round
140
- path.lineJoinStyle = .round
141
- NSColor.black.setStroke()
142
- path.stroke()
143
-
144
- image.unlockFocus()
145
- image.isTemplate = true
146
- return image
147
- }
148
- }
149
-
150
- let delegate = AppDelegate()
151
- NSApplication.shared.delegate = delegate
152
- NSApp.run()
@@ -1,220 +0,0 @@
1
- const fs = require('node:fs');
2
- const https = require('node:https');
3
- const path = require('node:path');
4
-
5
- function fetchHttpsJson(url) {
6
- return new Promise((resolve, reject) => {
7
- const opts = new URL(url);
8
- const reqOpts = {
9
- hostname: opts.hostname,
10
- path: opts.pathname + opts.search,
11
- method: 'GET',
12
- headers: {
13
- Accept: 'application/vnd.github+json',
14
- 'User-Agent': 'TokenDash',
15
- },
16
- };
17
-
18
- https.get(reqOpts, (res) => {
19
- let data = '';
20
- res.on('data', (chunk) => { data += chunk; });
21
- res.on('end', () => {
22
- if (res.statusCode && res.statusCode >= 400) {
23
- reject(new Error(`HTTP ${res.statusCode}`));
24
- return;
25
- }
26
- try { resolve(JSON.parse(data)); }
27
- catch (e) { reject(e); }
28
- });
29
- }).on('error', reject);
30
- });
31
- }
32
-
33
- function fetchLatestReleaseUrl(repo) {
34
- const url = `https://github.com/${repo}/releases/latest`;
35
- return new Promise((resolve, reject) => {
36
- const opts = new URL(url);
37
- const reqOpts = {
38
- hostname: opts.hostname,
39
- path: opts.pathname + opts.search,
40
- method: 'HEAD',
41
- headers: {
42
- 'User-Agent': 'TokenDash',
43
- },
44
- };
45
-
46
- https.request(reqOpts, (res) => {
47
- if ([301, 302, 303, 307, 308].includes(res.statusCode) && res.headers.location) {
48
- resolve(new URL(res.headers.location, url).toString());
49
- return;
50
- }
51
-
52
- if (res.statusCode && res.statusCode >= 400) {
53
- reject(new Error(`HTTP ${res.statusCode}`));
54
- return;
55
- }
56
-
57
- resolve(url);
58
- }).on('error', reject).end();
59
- });
60
- }
61
-
62
- function compareVersions(a, b) {
63
- const aParts = String(a).replace(/^v/, '').split(/[.-]/).map((part) => parseInt(part, 10) || 0);
64
- const bParts = String(b).replace(/^v/, '').split(/[.-]/).map((part) => parseInt(part, 10) || 0);
65
- const maxLen = Math.max(aParts.length, bParts.length);
66
- for (let i = 0; i < maxLen; i++) {
67
- const delta = (aParts[i] || 0) - (bParts[i] || 0);
68
- if (delta !== 0) return delta;
69
- }
70
- return 0;
71
- }
72
-
73
- function isDmgAsset(asset) {
74
- return Boolean(asset && typeof asset.name === 'string' && /\.dmg$/i.test(asset.name));
75
- }
76
-
77
- function selectMacDmgAsset(assets, arch = process.arch) {
78
- const dmgAssets = (Array.isArray(assets) ? assets : []).filter(isDmgAsset);
79
- if (dmgAssets.length === 0) return null;
80
-
81
- const archNeedle = arch === 'arm64' ? 'arm64' : arch === 'x64' ? 'x64' : '';
82
- if (archNeedle) {
83
- const archMatch = dmgAssets.find((asset) => asset.name.toLowerCase().includes(archNeedle));
84
- if (archMatch) return archMatch;
85
- }
86
-
87
- const universal = dmgAssets.find((asset) => /universal/i.test(asset.name));
88
- return universal || dmgAssets[0];
89
- }
90
-
91
- function getReleaseUpdateInfo(release, currentVersion, arch = process.arch) {
92
- const tag = String((release && release.tag_name) || '').replace(/^v/, '');
93
- const latestVersion = tag || currentVersion;
94
- const asset = selectMacDmgAsset(release && release.assets, arch);
95
- const upToDate = compareVersions(currentVersion, latestVersion) >= 0;
96
-
97
- return {
98
- currentVersion,
99
- latestVersion,
100
- upToDate,
101
- releaseUrl: (release && release.html_url) || null,
102
- asset: asset ? {
103
- name: asset.name,
104
- size: Number(asset.size) || 0,
105
- url: asset.browser_download_url,
106
- } : null,
107
- };
108
- }
109
-
110
- function buildDmgAssetFromVersion(repo, tagName, arch = process.arch) {
111
- const version = String(tagName || '').replace(/^v/, '');
112
- const archSuffix = arch === 'arm64' ? 'arm64' : arch === 'x64' ? 'x64' : 'universal';
113
- const name = `TokenDash-${version}-${archSuffix}.dmg`;
114
-
115
- return {
116
- name,
117
- size: 0,
118
- url: `https://github.com/${repo}/releases/download/${tagName}/${name}`,
119
- };
120
- }
121
-
122
- function getRedirectReleaseUpdateInfo(repo, releaseUrl, currentVersion, arch = process.arch) {
123
- const parsedUrl = new URL(releaseUrl);
124
- const tagMatch = parsedUrl.pathname.match(/\/releases\/tag\/([^/]+)\/?$/);
125
- if (!tagMatch) throw new Error('Unable to determine the latest release tag.');
126
-
127
- const tagName = decodeURIComponent(tagMatch[1]);
128
- const latestVersion = tagName.replace(/^v/, '');
129
- const upToDate = compareVersions(currentVersion, latestVersion) >= 0;
130
-
131
- return {
132
- currentVersion,
133
- latestVersion,
134
- upToDate,
135
- releaseUrl: parsedUrl.toString(),
136
- asset: upToDate ? null : buildDmgAssetFromVersion(repo, tagName, arch),
137
- };
138
- }
139
-
140
- async function checkForUpdates({
141
- repo,
142
- currentVersion,
143
- arch = process.arch,
144
- fetchReleaseJson = fetchHttpsJson,
145
- fetchLatestReleaseUrl: fetchLatestReleaseUrlOverride = fetchLatestReleaseUrl,
146
- }) {
147
- try {
148
- const release = await fetchReleaseJson(`https://api.github.com/repos/${repo}/releases/latest`);
149
- return getReleaseUpdateInfo(release, currentVersion, arch);
150
- } catch (error) {
151
- const releaseUrl = await fetchLatestReleaseUrlOverride(repo);
152
- return getRedirectReleaseUpdateInfo(repo, releaseUrl, currentVersion, arch);
153
- }
154
- }
155
-
156
- function safeDownloadName(name) {
157
- return path.basename(String(name || 'TokenDash-update.dmg')).replace(/[^\w .()+@-]/g, '-');
158
- }
159
-
160
- function downloadFile(url, destination, onProgress) {
161
- return new Promise((resolve, reject) => {
162
- const file = fs.createWriteStream(destination);
163
- let received = 0;
164
-
165
- const request = https.get(url, { headers: { 'User-Agent': 'TokenDash' } }, (res) => {
166
- if ([301, 302, 303, 307, 308].includes(res.statusCode) && res.headers.location) {
167
- file.close(() => fs.rm(destination, { force: true }, () => {}));
168
- downloadFile(res.headers.location, destination, onProgress).then(resolve, reject);
169
- return;
170
- }
171
-
172
- if (res.statusCode && res.statusCode >= 400) {
173
- file.close(() => fs.rm(destination, { force: true }, () => {}));
174
- reject(new Error(`HTTP ${res.statusCode}`));
175
- return;
176
- }
177
-
178
- const total = Number(res.headers['content-length']) || 0;
179
- res.on('data', (chunk) => {
180
- received += chunk.length;
181
- if (typeof onProgress === 'function') {
182
- onProgress({ received, total, percent: total > 0 ? Math.round((received / total) * 100) : null });
183
- }
184
- });
185
- res.pipe(file);
186
- });
187
-
188
- request.on('error', (error) => {
189
- file.close(() => fs.rm(destination, { force: true }, () => {}));
190
- reject(error);
191
- });
192
-
193
- file.on('finish', () => {
194
- file.close(() => resolve(destination));
195
- });
196
-
197
- file.on('error', (error) => {
198
- file.close(() => fs.rm(destination, { force: true }, () => {}));
199
- reject(error);
200
- });
201
- });
202
- }
203
-
204
- async function downloadUpdateAsset(asset, downloadsDir, onProgress) {
205
- if (!asset || !asset.url) throw new Error('No downloadable macOS update asset was found.');
206
- fs.mkdirSync(downloadsDir, { recursive: true });
207
- const destination = path.join(downloadsDir, safeDownloadName(asset.name));
208
- await downloadFile(asset.url, destination, onProgress);
209
- return destination;
210
- }
211
-
212
- module.exports = {
213
- checkForUpdates,
214
- compareVersions,
215
- downloadUpdateAsset,
216
- fetchLatestReleaseUrl,
217
- getRedirectReleaseUpdateInfo,
218
- getReleaseUpdateInfo,
219
- selectMacDmgAsset,
220
- };
@@ -1,20 +0,0 @@
1
- appId: com.zhangferry-dev.tokendash
2
- productName: TokenDash
3
- directories:
4
- output: release
5
- files:
6
- - dist/**/*
7
- - bin/**/*
8
- - electron/**/*
9
- - package.json
10
- mac:
11
- target: dmg
12
- category: public.app-category.developer-tools
13
- icon: resources/icon.icns
14
- identity: null
15
- darkModeSupport: true
16
- minimumSystemVersion: '14.0'
17
- extendInfo:
18
- LSUIElement: true
19
- extraMetadata:
20
- main: electron/main.cjs