@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 +4 -4
- package/dist/executors/native_macos.js +11 -3
- package/dist/types.js +1 -1
- package/dist/whatsapp_background.js +78 -5
- package/package.json +1 -1
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.
|
|
36
|
+
npm install -g ./leg3ndy-otto-bridge-0.6.6.tgz
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
No `0.6.
|
|
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.
|
|
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.
|
|
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({
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
}
|