@hienlh/ppm 0.8.73 → 0.8.74

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/CHANGELOG.md CHANGED
@@ -1,9 +1,10 @@
1
1
  # Changelog
2
2
 
3
- ## [0.8.73] - 2026-04-01
3
+ ## [0.8.74] - 2026-04-01
4
4
 
5
5
  ### Fixed
6
- - **Cloud WS reconnect loop**: Stale WebSocket closure handlers from replaced connections no longer reset module state — prevents infinite reconnect cycle after upgrade/restart
6
+ - **Cloud WS reconnect loop**: Stale WebSocket closure handlers from replaced connections no longer reset module state
7
+ - **Cloud WS auth race**: Delay heartbeat/queue flush 500ms after auth to let server complete async DB auth — prevents 4002 rejection
7
8
 
8
9
  ## [0.8.72] - 2026-03-31
9
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hienlh/ppm",
3
- "version": "0.8.73",
3
+ "version": "0.8.74",
4
4
  "description": "Personal Project Manager — mobile-first web IDE with AI assistance",
5
5
  "author": "hienlh",
6
6
  "license": "MIT",
@@ -140,8 +140,10 @@ function doConnect(): void {
140
140
  sock.onopen = () => {
141
141
  if (ws !== sock) return; // stale — newer connection replaced us
142
142
  reconnecting = false;
143
+ reconnectAttempt = 0;
143
144
  log("INFO", "Cloud WS connected, sending auth");
144
145
 
146
+ // Send auth as first message — server must process this before any other msg
145
147
  sock.send(JSON.stringify({
146
148
  type: "auth",
147
149
  deviceId,
@@ -150,23 +152,28 @@ function doConnect(): void {
150
152
  version: 1,
151
153
  }));
152
154
 
153
- connected = true;
154
- reconnectAttempt = 0;
155
-
156
- // Flush queued messages
157
- while (outboundQueue.length > 0 && connected) {
158
- const msg = outboundQueue.shift()!;
159
- sock.send(JSON.stringify(msg));
160
- }
155
+ // Delay setting connected + sending heartbeat to let server process auth.
156
+ // Server's authenticateDevice() is async (DB lookup), so messages sent
157
+ // immediately after auth arrive before authenticated=true → 4002 reject.
158
+ setTimeout(() => {
159
+ if (ws !== sock) return; // replaced during delay
160
+ connected = true;
161
+
162
+ // Flush queued messages
163
+ while (outboundQueue.length > 0 && connected) {
164
+ const msg = outboundQueue.shift()!;
165
+ sock.send(JSON.stringify(msg));
166
+ }
161
167
 
162
- // Send immediate heartbeat
163
- if (getHeartbeatData) send(getHeartbeatData());
168
+ // Send immediate heartbeat
169
+ if (getHeartbeatData) send(getHeartbeatData());
164
170
 
165
- // Start periodic heartbeat
166
- if (heartbeatTimer) clearInterval(heartbeatTimer);
167
- heartbeatTimer = setInterval(() => {
168
- if (getHeartbeatData && connected) send(getHeartbeatData());
169
- }, HEARTBEAT_INTERVAL_MS);
171
+ // Start periodic heartbeat
172
+ if (heartbeatTimer) clearInterval(heartbeatTimer);
173
+ heartbeatTimer = setInterval(() => {
174
+ if (getHeartbeatData && connected) send(getHeartbeatData());
175
+ }, HEARTBEAT_INTERVAL_MS);
176
+ }, 500); // 500ms for DB auth round-trip
170
177
  };
171
178
 
172
179
  sock.onmessage = (event) => {