pi-antigravity-rotator 2.3.1 → 2.3.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.
package/src/login.ts CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  import { createInterface } from "node:readline";
8
8
  import { addAccountToConfig, ensurePiAuthConfig, ensurePiModelsConfig, loadOrCreateAccountsConfig } from "./account-store.js";
9
- import { buildAuthUrl, discoverProject, exchangeAuthorizationCode, generatePkce, getOAuthClientConfig, getUserEmail } from "./oauth.js";
9
+ import { buildAuthUrl, discoverProject, exchangeAuthorizationCode, generatePkce, generateState, getOAuthClientConfig, getUserEmail } from "./oauth.js";
10
10
  import type { AccountConfig } from "./types.js";
11
11
  import { getAccountsPath } from "./paths.js";
12
12
 
@@ -42,7 +42,8 @@ export async function runLogin(): Promise<void> {
42
42
 
43
43
  const oauth = getOAuthClientConfig();
44
44
  const { verifier, challenge } = generatePkce();
45
- const authUrl = buildAuthUrl(verifier, challenge);
45
+ const state = generateState();
46
+ const authUrl = buildAuthUrl(state, challenge);
46
47
 
47
48
  console.log("1. Open this URL in your browser:");
48
49
  console.log();
@@ -66,7 +67,7 @@ export async function runLogin(): Promise<void> {
66
67
  process.exit(1);
67
68
  }
68
69
 
69
- if (parsed.state && parsed.state !== verifier) {
70
+ if (parsed.state !== state) {
70
71
  console.error("State mismatch - the URL does not match this login session.");
71
72
  process.exit(1);
72
73
  }
@@ -48,6 +48,7 @@ function getVersion(): string {
48
48
 
49
49
  // ── Cached notifications ─────────────────────────────────────────────
50
50
  let _notifications: AdminNotification[] = [];
51
+ let _initialPollTimer: ReturnType<typeof setTimeout> | null = null;
51
52
  let _pollTimer: ReturnType<typeof setInterval> | null = null;
52
53
 
53
54
  /**
@@ -100,18 +101,26 @@ export function startNotificationPoller(): void {
100
101
  }
101
102
 
102
103
  // Initial delayed fetch
103
- setTimeout(() => {
104
- void fetchNotifications();
105
- }, 15_000);
104
+ if (!_initialPollTimer) {
105
+ _initialPollTimer = setTimeout(() => {
106
+ _initialPollTimer = null;
107
+ void fetchNotifications();
108
+ }, 15_000);
109
+ if (_initialPollTimer.unref) {
110
+ _initialPollTimer.unref();
111
+ }
112
+ }
106
113
 
107
114
  // Periodic poll
108
- _pollTimer = setInterval(() => {
109
- void fetchNotifications();
110
- }, POLL_INTERVAL_MS);
111
-
112
- // Don't prevent process exit
113
- if (_pollTimer.unref) {
114
- _pollTimer.unref();
115
+ if (!_pollTimer) {
116
+ _pollTimer = setInterval(() => {
117
+ void fetchNotifications();
118
+ }, POLL_INTERVAL_MS);
119
+
120
+ // Don't prevent process exit
121
+ if (_pollTimer.unref) {
122
+ _pollTimer.unref();
123
+ }
115
124
  }
116
125
  }
117
126
 
@@ -119,6 +128,10 @@ export function startNotificationPoller(): void {
119
128
  * Stop notification polling.
120
129
  */
121
130
  export function stopNotificationPoller(): void {
131
+ if (_initialPollTimer) {
132
+ clearTimeout(_initialPollTimer);
133
+ _initialPollTimer = null;
134
+ }
122
135
  if (_pollTimer) {
123
136
  clearInterval(_pollTimer);
124
137
  _pollTimer = null;
package/src/onboarding.ts CHANGED
@@ -203,6 +203,7 @@ export function startHostedLogin(
203
203
  interface CliLoginSession {
204
204
  verifier: string;
205
205
  challenge: string;
206
+ oauthState: string;
206
207
  authUrl: string;
207
208
  createdAt: number;
208
209
  }
@@ -221,11 +222,13 @@ function pruneCliSessions(): void {
221
222
  export function serveCliLogin(res: ServerResponse): void {
222
223
  pruneCliSessions();
223
224
  const { verifier, challenge } = generatePkce();
224
- const authUrl = buildAuthUrl(verifier, challenge);
225
+ const oauthState = generateState();
226
+ const authUrl = buildAuthUrl(oauthState, challenge);
225
227
  const sessionId = generateState();
226
228
  cliLoginSessions.set(sessionId, {
227
229
  verifier,
228
230
  challenge,
231
+ oauthState,
229
232
  authUrl,
230
233
  createdAt: Date.now(),
231
234
  });
@@ -348,9 +351,11 @@ export async function handleCliLoginApi(
348
351
 
349
352
  // Parse the redirect URL to extract code
350
353
  let code: string | undefined;
354
+ let state: string | null;
351
355
  try {
352
356
  const url = new URL(redirectUrl.trim());
353
357
  code = url.searchParams.get("code") ?? undefined;
358
+ state = url.searchParams.get("state");
354
359
  } catch {
355
360
  res.writeHead(400, { "Content-Type": "application/json" });
356
361
  res.end(
@@ -373,6 +378,16 @@ export async function handleCliLoginApi(
373
378
  );
374
379
  return;
375
380
  }
381
+ if (state !== session.oauthState) {
382
+ res.writeHead(400, { "Content-Type": "application/json" });
383
+ res.end(
384
+ JSON.stringify({
385
+ ok: false,
386
+ error: "State mismatch - reload the login page and try again.",
387
+ }),
388
+ );
389
+ return;
390
+ }
376
391
 
377
392
  cliLoginSessions.delete(sessionId);
378
393