@walldock/agent 0.1.7 → 0.2.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.
- package/dist/main.js +48 -9
- package/dist/startup.js +6 -5
- package/dist/tray.d.ts +5 -0
- package/dist/tray.js +54 -0
- package/package.json +3 -2
package/dist/main.js
CHANGED
|
@@ -44,8 +44,10 @@ const sync_1 = require("./sync");
|
|
|
44
44
|
const storage = __importStar(require("./token-storage"));
|
|
45
45
|
const startup_1 = require("./startup");
|
|
46
46
|
const pid_1 = require("./pid");
|
|
47
|
+
const tray_1 = require("./tray");
|
|
47
48
|
// ─── Logging ─────────────────────────────────────────────────────────────────
|
|
48
49
|
const isDaemon = process.argv.includes('--daemon');
|
|
50
|
+
const isTray = process.argv.includes('--tray');
|
|
49
51
|
function logDir() {
|
|
50
52
|
if (process.platform === 'darwin')
|
|
51
53
|
return path.join(os.homedir(), 'Library', 'Logs');
|
|
@@ -61,7 +63,7 @@ async function openLog() {
|
|
|
61
63
|
}
|
|
62
64
|
function log(msg) {
|
|
63
65
|
const line = `[${new Date().toISOString()}] ${msg}\n`;
|
|
64
|
-
if (isDaemon && logStream) {
|
|
66
|
+
if ((isDaemon || isTray) && logStream) {
|
|
65
67
|
logStream.write(line).catch(() => undefined);
|
|
66
68
|
}
|
|
67
69
|
else {
|
|
@@ -95,7 +97,7 @@ async function main() {
|
|
|
95
97
|
}
|
|
96
98
|
return;
|
|
97
99
|
}
|
|
98
|
-
if (isDaemon)
|
|
100
|
+
if (isDaemon || isTray)
|
|
99
101
|
await openLog();
|
|
100
102
|
// Single-instance guard
|
|
101
103
|
try {
|
|
@@ -106,12 +108,17 @@ async function main() {
|
|
|
106
108
|
process.exit(1);
|
|
107
109
|
}
|
|
108
110
|
const api = new api_1.AgentApi(process.env['WALLDOCK_API_URL'] ?? undefined);
|
|
111
|
+
let trayHandle = null;
|
|
109
112
|
const sync = new sync_1.SyncService(api, {
|
|
110
113
|
onTick(status, lastSyncAt) {
|
|
111
|
-
if (status === 'error')
|
|
114
|
+
if (status === 'error') {
|
|
112
115
|
log('[sync] connection error');
|
|
113
|
-
|
|
116
|
+
trayHandle?.updateStatus('connection error');
|
|
117
|
+
}
|
|
118
|
+
else if (lastSyncAt) {
|
|
114
119
|
log(`[sync] ok at ${lastSyncAt}`);
|
|
120
|
+
trayHandle?.updateStatus('synced ' + new Date(lastSyncAt).toLocaleTimeString());
|
|
121
|
+
}
|
|
115
122
|
},
|
|
116
123
|
onLog: log,
|
|
117
124
|
onInvalidToken() {
|
|
@@ -119,8 +126,8 @@ async function main() {
|
|
|
119
126
|
log('Device token is no longer valid. Clearing credentials…');
|
|
120
127
|
await storage.deleteDeviceToken();
|
|
121
128
|
await storage.clearDeviceIdentity();
|
|
122
|
-
if (isDaemon) {
|
|
123
|
-
log('Run "walldock-agent"
|
|
129
|
+
if (isDaemon || isTray) {
|
|
130
|
+
log('Run "walldock-agent" to re-pair this device.');
|
|
124
131
|
await shutdown();
|
|
125
132
|
return;
|
|
126
133
|
}
|
|
@@ -147,6 +154,8 @@ async function main() {
|
|
|
147
154
|
if (stopping)
|
|
148
155
|
return;
|
|
149
156
|
stopping = true;
|
|
157
|
+
trayHandle?.stop();
|
|
158
|
+
trayHandle = null;
|
|
150
159
|
sync.stop();
|
|
151
160
|
await (0, pid_1.releaseLock)();
|
|
152
161
|
log('Walldock Agent stopped.');
|
|
@@ -155,14 +164,41 @@ async function main() {
|
|
|
155
164
|
}
|
|
156
165
|
process.on('SIGINT', () => { void shutdown(); });
|
|
157
166
|
process.on('SIGTERM', () => { void shutdown(); });
|
|
167
|
+
async function maybeStartTray() {
|
|
168
|
+
if (isDaemon)
|
|
169
|
+
return false;
|
|
170
|
+
if (isTray) {
|
|
171
|
+
// Already a detached background process — start the tray in-place
|
|
172
|
+
try {
|
|
173
|
+
trayHandle = (0, tray_1.startTray)(() => { void sync.syncNow(); }, () => { void shutdown(); });
|
|
174
|
+
}
|
|
175
|
+
catch { /* no display — run headlessly */ }
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
// Console-attached interactive session: re-spawn detached so closing the
|
|
179
|
+
// terminal doesn't kill the agent.
|
|
180
|
+
await (0, pid_1.releaseLock)();
|
|
181
|
+
const { spawn } = await Promise.resolve().then(() => __importStar(require('node:child_process')));
|
|
182
|
+
const child = spawn(process.execPath, [process.argv[1], '--tray'], {
|
|
183
|
+
detached: true,
|
|
184
|
+
stdio: 'ignore',
|
|
185
|
+
windowsHide: true,
|
|
186
|
+
});
|
|
187
|
+
child.unref();
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
158
190
|
// Try to resume from stored token
|
|
159
191
|
log('Walldock Agent starting…');
|
|
160
192
|
const existing = await (0, auth_1.validateStoredToken)(api);
|
|
161
193
|
if (existing) {
|
|
162
194
|
await storage.setDeviceIdentity({ deviceId: existing.deviceId, deviceName: existing.deviceName });
|
|
163
195
|
log(`Linked as "${existing.deviceName}". Starting sync…`);
|
|
196
|
+
if (await maybeStartTray()) {
|
|
197
|
+
console.log('Walldock Agent is running in the system tray.');
|
|
198
|
+
process.exit(0);
|
|
199
|
+
}
|
|
164
200
|
sync.start({ token: existing.token, deviceId: existing.deviceId });
|
|
165
|
-
return; // sync loop keeps process alive
|
|
201
|
+
return; // sync loop keeps process alive (isTray path)
|
|
166
202
|
}
|
|
167
203
|
// No valid token — need to pair
|
|
168
204
|
if (isDaemon) {
|
|
@@ -199,11 +235,14 @@ async function main() {
|
|
|
199
235
|
}
|
|
200
236
|
catch (err) {
|
|
201
237
|
console.warn(` Could not register for startup: ${err}`);
|
|
202
|
-
console.warn(' You can run "walldock-agent
|
|
238
|
+
console.warn(' You can run "walldock-agent" manually to start syncing.');
|
|
203
239
|
}
|
|
204
240
|
}
|
|
205
241
|
}
|
|
206
|
-
|
|
242
|
+
if (await maybeStartTray()) {
|
|
243
|
+
console.log('\nWalldock Agent is running in the system tray.\n');
|
|
244
|
+
process.exit(0);
|
|
245
|
+
}
|
|
207
246
|
sync.start({ token: result.token, deviceId: result.deviceId });
|
|
208
247
|
}
|
|
209
248
|
main().catch(err => {
|
package/dist/startup.js
CHANGED
|
@@ -47,7 +47,8 @@ const exec = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
|
47
47
|
async function resolveGlobalBin() {
|
|
48
48
|
let prefix;
|
|
49
49
|
try {
|
|
50
|
-
const
|
|
50
|
+
const npmBin = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
51
|
+
const { stdout } = await exec(npmBin, ['prefix', '-g']);
|
|
51
52
|
prefix = stdout.trim();
|
|
52
53
|
}
|
|
53
54
|
catch {
|
|
@@ -82,7 +83,7 @@ function plistContent(binPath) {
|
|
|
82
83
|
<key>ProgramArguments</key>
|
|
83
84
|
<array>
|
|
84
85
|
<string>${binPath}</string>
|
|
85
|
-
<string>--
|
|
86
|
+
<string>--tray</string>
|
|
86
87
|
</array>
|
|
87
88
|
<key>RunAtLoad</key>
|
|
88
89
|
<true/>
|
|
@@ -111,7 +112,7 @@ const REG_KEY = 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run';
|
|
|
111
112
|
const REG_VALUE = 'WalldockAgent';
|
|
112
113
|
async function registerWindows() {
|
|
113
114
|
const binPath = await resolveGlobalBin();
|
|
114
|
-
const cmd = `"${binPath}" --
|
|
115
|
+
const cmd = `"${binPath}" --tray`;
|
|
115
116
|
await exec('reg', ['add', REG_KEY, '/v', REG_VALUE, '/t', 'REG_SZ', '/d', cmd, '/f']);
|
|
116
117
|
}
|
|
117
118
|
async function unregisterWindows() {
|
|
@@ -129,7 +130,7 @@ After=network-online.target
|
|
|
129
130
|
Wants=network-online.target
|
|
130
131
|
|
|
131
132
|
[Service]
|
|
132
|
-
ExecStart=${binPath} --
|
|
133
|
+
ExecStart=${binPath} --tray
|
|
133
134
|
Restart=on-failure
|
|
134
135
|
RestartSec=10s
|
|
135
136
|
|
|
@@ -141,7 +142,7 @@ function desktopContent(binPath) {
|
|
|
141
142
|
return `[Desktop Entry]
|
|
142
143
|
Type=Application
|
|
143
144
|
Name=Walldock Agent
|
|
144
|
-
Exec=${binPath} --
|
|
145
|
+
Exec=${binPath} --tray
|
|
145
146
|
Hidden=false
|
|
146
147
|
NoDisplay=false
|
|
147
148
|
X-GNOME-Autostart-enabled=true
|
package/dist/tray.d.ts
ADDED
package/dist/tray.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.startTray = startTray;
|
|
7
|
+
const systray2_1 = __importDefault(require("systray2"));
|
|
8
|
+
// 32x32 PNG from apps/agent/src-tauri/icons/32x32.png (base64)
|
|
9
|
+
const ICON = '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';
|
|
10
|
+
const IDX_STATUS = 0;
|
|
11
|
+
const IDX_SYNC = 2;
|
|
12
|
+
const IDX_QUIT = 4;
|
|
13
|
+
function startTray(onSyncNow, onQuit) {
|
|
14
|
+
const systray = new systray2_1.default({
|
|
15
|
+
menu: {
|
|
16
|
+
icon: ICON,
|
|
17
|
+
title: '',
|
|
18
|
+
tooltip: 'Walldock Agent',
|
|
19
|
+
items: [
|
|
20
|
+
{ title: 'Walldock Agent', tooltip: '', checked: false, enabled: false },
|
|
21
|
+
systray2_1.default.separator,
|
|
22
|
+
{ title: 'Sync Now', tooltip: 'Fetch latest wallpapers', checked: false, enabled: true },
|
|
23
|
+
systray2_1.default.separator,
|
|
24
|
+
{ title: 'Quit', tooltip: 'Stop Walldock Agent', checked: false, enabled: true },
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
debug: false,
|
|
28
|
+
copyDir: true,
|
|
29
|
+
});
|
|
30
|
+
systray.onClick(action => {
|
|
31
|
+
if (action.seq_id === IDX_SYNC)
|
|
32
|
+
onSyncNow();
|
|
33
|
+
if (action.seq_id === IDX_QUIT)
|
|
34
|
+
onQuit();
|
|
35
|
+
});
|
|
36
|
+
return {
|
|
37
|
+
updateStatus(msg) {
|
|
38
|
+
try {
|
|
39
|
+
void systray.sendAction({
|
|
40
|
+
type: 'update-item',
|
|
41
|
+
item: { title: `Walldock — ${msg}`, tooltip: '', checked: false, enabled: false },
|
|
42
|
+
seq_id: IDX_STATUS,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
catch { /* tray process may have exited */ }
|
|
46
|
+
},
|
|
47
|
+
stop() {
|
|
48
|
+
try {
|
|
49
|
+
systray.kill(false);
|
|
50
|
+
}
|
|
51
|
+
catch { /* ignore */ }
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@walldock/agent",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Walldock desktop agent — sync wallpapers across all your screens",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/main.js",
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
"node": ">=18.0.0"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@napi-rs/keyring": "^1.1.5"
|
|
17
|
+
"@napi-rs/keyring": "^1.1.5",
|
|
18
|
+
"systray2": "^2.1.4"
|
|
18
19
|
},
|
|
19
20
|
"devDependencies": {
|
|
20
21
|
"@types/node": "^20.0.0",
|