@leg3ndy/otto-bridge 0.6.4 → 0.6.6

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 CHANGED
@@ -33,10 +33,10 @@ Enquanto o pacote nao estiver publicado, voce pode gerar um tarball local:
33
33
 
34
34
  ```bash
35
35
  npm pack
36
- npm install -g ./leg3ndy-otto-bridge-0.6.4.tgz
36
+ npm install -g ./leg3ndy-otto-bridge-0.6.6.tgz
37
37
  ```
38
38
 
39
- No `0.6.4`, `playwright` deixa de ser opcional no `otto-bridge`. O primeiro `npm install -g @leg3ndy/otto-bridge` pode demorar mais porque instala o browser persistente usado pelo WhatsApp Web e pelos fluxos web em background do bridge.
39
+ No `0.6.6`, `playwright` deixa de ser opcional no `otto-bridge`. O primeiro `npm install -g @leg3ndy/otto-bridge` pode demorar mais porque instala o browser persistente usado pelo WhatsApp Web e pelos fluxos web em background do bridge.
40
40
 
41
41
  ## Publicacao
42
42
 
@@ -106,7 +106,7 @@ otto-bridge run --executor clawd-cursor --clawd-url http://127.0.0.1:3847
106
106
 
107
107
  ### WhatsApp Web em background
108
108
 
109
- Fluxo recomendado no `0.6.4`:
109
+ Fluxo recomendado no `0.6.6`:
110
110
 
111
111
  ```bash
112
112
  otto-bridge extensions --install whatsappweb
@@ -116,7 +116,7 @@ otto-bridge extensions --status whatsappweb
116
116
 
117
117
  O setup agora abre o login do WhatsApp Web em um browser persistente do proprio bridge. Depois do QR code, o Otto usa a sessao local em background, sem depender de aba visivel no Safari.
118
118
 
119
- Contrato do `0.6.4`:
119
+ Contrato do `0.6.6`:
120
120
 
121
121
  - `otto-bridge extensions --setup whatsappweb`: autentica a sessao uma vez
122
122
  - `otto-bridge run`: mantem o browser persistente do WhatsApp vivo em background enquanto o runtime estiver ativo
@@ -33,6 +33,8 @@ const KNOWN_SITES = [
33
33
  { label: "X", url: "https://x.com", patterns: [/\bx\.com\b/i, /\btwitter\b/i, /\bxis\b/i] },
34
34
  ];
35
35
  const WHATSAPP_WEB_EXTENSION_SLUG = "whatsappweb";
36
+ const WHATSAPP_EXPECTED_CONNECTED_TIMEOUT_MS = 45_000;
37
+ const WHATSAPP_EXPECTED_CONNECTED_QR_STABILITY_WINDOW_MS = 10_000;
36
38
  const FILE_SEARCH_SKIP_DIRS = new Set([
37
39
  ".git",
38
40
  "node_modules",
@@ -2162,7 +2164,7 @@ end repeat
2162
2164
  if (!availability.ok) {
2163
2165
  return null;
2164
2166
  }
2165
- const browser = new WhatsAppBackgroundBrowser({ headless: true });
2167
+ const browser = new WhatsAppBackgroundBrowser({ headless: false, background: true });
2166
2168
  await browser.start();
2167
2169
  this.whatsappBackgroundBrowser = browser;
2168
2170
  return browser;
@@ -2218,7 +2220,10 @@ return {
2218
2220
  const backgroundBrowser = await this.getWhatsAppBackgroundBrowser().catch(() => null);
2219
2221
  if (backgroundBrowser) {
2220
2222
  try {
2221
- const state = await backgroundBrowser.waitForStableSessionState();
2223
+ const state = await backgroundBrowser.waitForStableSessionState({
2224
+ timeoutMs: WHATSAPP_EXPECTED_CONNECTED_TIMEOUT_MS,
2225
+ qrStabilityWindowMs: WHATSAPP_EXPECTED_CONNECTED_QR_STABILITY_WINDOW_MS,
2226
+ });
2222
2227
  if (state.connected) {
2223
2228
  await this.syncWhatsAppExtensionState("connected", "Sessao local do WhatsApp Web pronta para uso no browser em background.", { runtimeAttached: true });
2224
2229
  return;
@@ -2317,7 +2322,10 @@ return {
2317
2322
  return;
2318
2323
  }
2319
2324
  try {
2320
- const state = await backgroundBrowser.waitForStableSessionState({ timeoutMs: 12_000 });
2325
+ const state = await backgroundBrowser.waitForStableSessionState({
2326
+ timeoutMs: WHATSAPP_EXPECTED_CONNECTED_TIMEOUT_MS,
2327
+ qrStabilityWindowMs: WHATSAPP_EXPECTED_CONNECTED_QR_STABILITY_WINDOW_MS,
2328
+ });
2321
2329
  if (state.connected) {
2322
2330
  await this.syncWhatsAppExtensionState("connected", "Sessao local do WhatsApp Web mantida em background enquanto `otto-bridge run` estiver ativo.", { runtimeAttached: true });
2323
2331
  return;
package/dist/types.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export const BRIDGE_CONFIG_VERSION = 1;
2
- export const BRIDGE_VERSION = "0.6.4";
2
+ export const BRIDGE_VERSION = "0.6.6";
3
3
  export const BRIDGE_PACKAGE_NAME = "@leg3ndy/otto-bridge";
4
4
  export const DEFAULT_API_BASE_URL = "http://localhost:8000";
5
5
  export const DEFAULT_POLL_INTERVAL_MS = 3000;
@@ -7,6 +7,13 @@ const DEFAULT_SETUP_TIMEOUT_MS = 5 * 60 * 1000;
7
7
  const DEFAULT_SESSION_SETTLE_TIMEOUT_MS = 15_000;
8
8
  const DEFAULT_SESSION_POLL_INTERVAL_MS = 1_000;
9
9
  const DEFAULT_QR_STABILITY_WINDOW_MS = 4_000;
10
+ const DEFAULT_EXPECTED_CONNECTED_TIMEOUT_MS = 45_000;
11
+ const DEFAULT_EXPECTED_CONNECTED_QR_STABILITY_WINDOW_MS = 10_000;
12
+ const DEFAULT_POST_LOGIN_STABILIZE_MS = 10_000;
13
+ const DEFAULT_REOPEN_VERIFY_DELAY_MS = 2_000;
14
+ function delay(ms) {
15
+ return new Promise((resolve) => setTimeout(resolve, ms));
16
+ }
10
17
  function moduleDir() {
11
18
  return path.dirname(fileURLToPath(import.meta.url));
12
19
  }
@@ -92,11 +99,12 @@ export class WhatsAppBackgroundBrowser {
92
99
  const playwright = await loadPlaywrightModule();
93
100
  await ensureWhatsAppBrowserUserDataDir();
94
101
  this.context = await playwright.chromium.launchPersistentContext(getWhatsAppBrowserUserDataDir(), {
95
- headless: this.options.headless !== false,
102
+ headless: this.options.headless === true,
96
103
  viewport: { width: 1440, height: 960 },
97
104
  locale: "pt-BR",
98
105
  timezoneId: "America/Sao_Paulo",
99
106
  args: [
107
+ ...(this.options.background ? ["--start-minimized"] : []),
100
108
  "--disable-backgrounding-occluded-windows",
101
109
  "--disable-renderer-backgrounding",
102
110
  "--disable-background-timer-throttling",
@@ -106,6 +114,9 @@ export class WhatsAppBackgroundBrowser {
106
114
  const pages = this.context.pages();
107
115
  this.page = pages[0] || await this.context.newPage();
108
116
  await this.ensureWhatsAppPage();
117
+ if (this.options.background) {
118
+ await this.minimizeWindow();
119
+ }
109
120
  }
110
121
  async close() {
111
122
  const current = this.context;
@@ -116,6 +127,31 @@ export class WhatsAppBackgroundBrowser {
116
127
  }
117
128
  await current.close().catch(() => undefined);
118
129
  }
130
+ async waitForTimeout(timeoutMs) {
131
+ await this.page?.waitForTimeout(Math.max(0, Number(timeoutMs || 0)));
132
+ }
133
+ async minimizeWindow() {
134
+ const context = this.context;
135
+ const page = this.page;
136
+ if (!context || !page || typeof context.newCDPSession !== "function") {
137
+ return;
138
+ }
139
+ try {
140
+ const session = await context.newCDPSession(page);
141
+ const windowInfo = await session.send("Browser.getWindowForTarget");
142
+ const windowId = Number(windowInfo.windowId || 0);
143
+ if (Number.isFinite(windowId) && windowId > 0) {
144
+ await session.send("Browser.setWindowBounds", {
145
+ windowId,
146
+ bounds: { windowState: "minimized" },
147
+ });
148
+ }
149
+ await session.detach?.().catch(() => undefined);
150
+ }
151
+ catch {
152
+ // If minimization fails, keep the runtime alive anyway.
153
+ }
154
+ }
119
155
  async getSessionState() {
120
156
  const state = await this.withPage(async (page) => {
121
157
  await this.ensureWhatsAppPage();
@@ -492,9 +528,12 @@ export async function detectWhatsAppBackgroundStatus() {
492
528
  notes: availability.reason || "Playwright nao esta disponivel para verificar o WhatsApp Web em background.",
493
529
  };
494
530
  }
495
- const browser = new WhatsAppBackgroundBrowser({ headless: true });
531
+ const browser = new WhatsAppBackgroundBrowser({ headless: false, background: true });
496
532
  try {
497
- const state = await browser.waitForStableSessionState();
533
+ const state = await browser.waitForStableSessionState({
534
+ timeoutMs: DEFAULT_EXPECTED_CONNECTED_TIMEOUT_MS,
535
+ qrStabilityWindowMs: DEFAULT_EXPECTED_CONNECTED_QR_STABILITY_WINDOW_MS,
536
+ });
498
537
  return sessionStateToStatus(state);
499
538
  }
500
539
  catch (error) {
@@ -508,6 +547,20 @@ export async function detectWhatsAppBackgroundStatus() {
508
547
  await browser.close();
509
548
  }
510
549
  }
550
+ async function verifyWhatsAppBackgroundSessionPersistence(options) {
551
+ const browser = new WhatsAppBackgroundBrowser({ headless: false, background: true });
552
+ try {
553
+ options?.log?.("Confirmando se a sessao persistiu no browser em background do bridge...");
554
+ const state = await browser.waitForStableSessionState({
555
+ timeoutMs: DEFAULT_EXPECTED_CONNECTED_TIMEOUT_MS,
556
+ qrStabilityWindowMs: DEFAULT_EXPECTED_CONNECTED_QR_STABILITY_WINDOW_MS,
557
+ });
558
+ return sessionStateToStatus(state);
559
+ }
560
+ finally {
561
+ await browser.close();
562
+ }
563
+ }
511
564
  export async function runWhatsAppBackgroundSetup(options) {
512
565
  const browser = new WhatsAppBackgroundBrowser({ headless: false });
513
566
  const log = options?.log;
@@ -530,9 +583,29 @@ export async function runWhatsAppBackgroundSetup(options) {
530
583
  notes: "O setup abriu o browser persistente, mas o login ainda nao foi concluido.",
531
584
  };
532
585
  }
533
- return detected;
586
+ log?.("Login detectado. Finalizando a sincronizacao local da sessao...");
587
+ await browser.waitForTimeout(DEFAULT_POST_LOGIN_STABILIZE_MS);
588
+ const settledState = await browser.waitForStableSessionState({
589
+ timeoutMs: 20_000,
590
+ qrStabilityWindowMs: DEFAULT_EXPECTED_CONNECTED_QR_STABILITY_WINDOW_MS,
591
+ });
592
+ if (!settledState.connected) {
593
+ return {
594
+ status: "waiting_login",
595
+ notes: "O QR foi autenticado, mas a sessao ainda nao terminou de sincronizar. Aguarde alguns segundos e rode `otto-bridge extensions --status whatsappweb`.",
596
+ };
597
+ }
534
598
  }
535
599
  finally {
536
- await browser.close();
600
+ await browser.close().catch(() => undefined);
601
+ }
602
+ await delay(DEFAULT_REOPEN_VERIFY_DELAY_MS);
603
+ const verified = await verifyWhatsAppBackgroundSessionPersistence({ log });
604
+ if (verified.status === "connected") {
605
+ return verified;
537
606
  }
607
+ return {
608
+ status: "waiting_login",
609
+ notes: "O login foi detectado, mas a sessao ainda nao ficou pronta para reabrir em background. Aguarde alguns segundos e rode `otto-bridge extensions --status whatsappweb`.",
610
+ };
538
611
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leg3ndy/otto-bridge",
3
- "version": "0.6.4",
3
+ "version": "0.6.6",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Local companion for Otto Bridge device pairing and WebSocket runtime.",