@leg3ndy/otto-bridge 0.6.5 → 0.6.7
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 +5 -5
- package/dist/executors/native_macos.js +1 -1
- package/dist/types.js +1 -1
- package/dist/whatsapp_background.js +108 -3
- 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.7.tgz
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
No `0.6.
|
|
39
|
+
No `0.6.7`, `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.7`:
|
|
110
110
|
|
|
111
111
|
```bash
|
|
112
112
|
otto-bridge extensions --install whatsappweb
|
|
@@ -116,10 +116,10 @@ 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.7`:
|
|
120
120
|
|
|
121
121
|
- `otto-bridge extensions --setup whatsappweb`: autentica a sessao uma vez
|
|
122
|
-
- `otto-bridge run`: mantem o browser persistente do WhatsApp vivo em background enquanto o runtime estiver ativo
|
|
122
|
+
- `otto-bridge run`: mantem o browser persistente do WhatsApp vivo em background enquanto o runtime estiver ativo, sem depender de uma aba aberta no Safari
|
|
123
123
|
- ao parar o `otto-bridge run`: o browser em background e desligado, mas a sessao local fica lembrada para o proximo boot
|
|
124
124
|
|
|
125
125
|
### Ver estado local
|
|
@@ -2164,7 +2164,7 @@ end repeat
|
|
|
2164
2164
|
if (!availability.ok) {
|
|
2165
2165
|
return null;
|
|
2166
2166
|
}
|
|
2167
|
-
const browser = new WhatsAppBackgroundBrowser({ headless: true });
|
|
2167
|
+
const browser = new WhatsAppBackgroundBrowser({ headless: false, background: true });
|
|
2168
2168
|
await browser.start();
|
|
2169
2169
|
this.whatsappBackgroundBrowser = browser;
|
|
2170
2170
|
return browser;
|
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.7";
|
|
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;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
1
2
|
import { mkdir } from "node:fs/promises";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
@@ -99,7 +100,7 @@ export class WhatsAppBackgroundBrowser {
|
|
|
99
100
|
const playwright = await loadPlaywrightModule();
|
|
100
101
|
await ensureWhatsAppBrowserUserDataDir();
|
|
101
102
|
this.context = await playwright.chromium.launchPersistentContext(getWhatsAppBrowserUserDataDir(), {
|
|
102
|
-
headless: this.options.headless
|
|
103
|
+
headless: this.options.headless === true,
|
|
103
104
|
viewport: { width: 1440, height: 960 },
|
|
104
105
|
locale: "pt-BR",
|
|
105
106
|
timezoneId: "America/Sao_Paulo",
|
|
@@ -113,6 +114,10 @@ export class WhatsAppBackgroundBrowser {
|
|
|
113
114
|
const pages = this.context.pages();
|
|
114
115
|
this.page = pages[0] || await this.context.newPage();
|
|
115
116
|
await this.ensureWhatsAppPage();
|
|
117
|
+
if (this.options.background) {
|
|
118
|
+
await this.moveWindowOffscreen();
|
|
119
|
+
await this.hideAppFromDock();
|
|
120
|
+
}
|
|
116
121
|
}
|
|
117
122
|
async close() {
|
|
118
123
|
const current = this.context;
|
|
@@ -126,6 +131,85 @@ export class WhatsAppBackgroundBrowser {
|
|
|
126
131
|
async waitForTimeout(timeoutMs) {
|
|
127
132
|
await this.page?.waitForTimeout(Math.max(0, Number(timeoutMs || 0)));
|
|
128
133
|
}
|
|
134
|
+
async moveWindowOffscreen() {
|
|
135
|
+
const context = this.context;
|
|
136
|
+
const page = this.page;
|
|
137
|
+
if (!context || !page || typeof context.newCDPSession !== "function") {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
try {
|
|
141
|
+
const session = await context.newCDPSession(page);
|
|
142
|
+
const windowInfo = await session.send("Browser.getWindowForTarget");
|
|
143
|
+
const windowId = Number(windowInfo.windowId || 0);
|
|
144
|
+
if (Number.isFinite(windowId) && windowId > 0) {
|
|
145
|
+
await session.send("Browser.setWindowBounds", {
|
|
146
|
+
windowId,
|
|
147
|
+
bounds: {
|
|
148
|
+
windowState: "normal",
|
|
149
|
+
left: -2200,
|
|
150
|
+
top: 80,
|
|
151
|
+
width: 1320,
|
|
152
|
+
height: 920,
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
await session.detach?.().catch(() => undefined);
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
// If background placement fails, keep the runtime alive anyway.
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
async hideAppFromDock() {
|
|
163
|
+
if (process.platform !== "darwin") {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const browserPid = await this.findBrowserProcessId();
|
|
167
|
+
if (!browserPid) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const script = [
|
|
171
|
+
'tell application "System Events"',
|
|
172
|
+
`set visible of (first application process whose unix id is ${browserPid}) to false`,
|
|
173
|
+
"end tell",
|
|
174
|
+
].join("\n");
|
|
175
|
+
await runCommand("osascript", ["-e", script]).catch(() => undefined);
|
|
176
|
+
}
|
|
177
|
+
async findBrowserProcessId() {
|
|
178
|
+
if (process.platform !== "darwin") {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
const userDataDir = getWhatsAppBrowserUserDataDir();
|
|
182
|
+
const result = await runCommand("pgrep", ["-af", userDataDir]).catch(() => null);
|
|
183
|
+
if (!result) {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
const candidates = result
|
|
187
|
+
.split("\n")
|
|
188
|
+
.map((line) => line.trim())
|
|
189
|
+
.filter(Boolean)
|
|
190
|
+
.map((line) => {
|
|
191
|
+
const match = line.match(/^(\d+)\s+(.*)$/);
|
|
192
|
+
if (!match) {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
const pid = Number(match[1]);
|
|
196
|
+
const command = match[2] || "";
|
|
197
|
+
if (!Number.isFinite(pid) || pid <= 0) {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
let score = 0;
|
|
201
|
+
if (!command.includes("--type="))
|
|
202
|
+
score += 100;
|
|
203
|
+
if (command.includes("Chromium.app") || command.includes("chrome"))
|
|
204
|
+
score += 40;
|
|
205
|
+
if (command.includes(userDataDir))
|
|
206
|
+
score += 20;
|
|
207
|
+
return { pid, score };
|
|
208
|
+
})
|
|
209
|
+
.filter((item) => item !== null)
|
|
210
|
+
.sort((left, right) => right.score - left.score || left.pid - right.pid);
|
|
211
|
+
return candidates[0]?.pid || null;
|
|
212
|
+
}
|
|
129
213
|
async getSessionState() {
|
|
130
214
|
const state = await this.withPage(async (page) => {
|
|
131
215
|
await this.ensureWhatsAppPage();
|
|
@@ -494,6 +578,27 @@ function clipText(text, maxLength) {
|
|
|
494
578
|
}
|
|
495
579
|
return `${value.slice(0, Math.max(0, maxLength - 1)).trimEnd()}…`;
|
|
496
580
|
}
|
|
581
|
+
function runCommand(command, args) {
|
|
582
|
+
return new Promise((resolve, reject) => {
|
|
583
|
+
const child = spawn(command, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
584
|
+
let stdout = "";
|
|
585
|
+
let stderr = "";
|
|
586
|
+
child.stdout.on("data", (chunk) => {
|
|
587
|
+
stdout += String(chunk);
|
|
588
|
+
});
|
|
589
|
+
child.stderr.on("data", (chunk) => {
|
|
590
|
+
stderr += String(chunk);
|
|
591
|
+
});
|
|
592
|
+
child.on("error", reject);
|
|
593
|
+
child.on("close", (code) => {
|
|
594
|
+
if (code === 0) {
|
|
595
|
+
resolve(stdout.trim());
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
reject(new Error(stderr.trim() || stdout.trim() || `${command} exited with code ${code}`));
|
|
599
|
+
});
|
|
600
|
+
});
|
|
601
|
+
}
|
|
497
602
|
export async function detectWhatsAppBackgroundStatus() {
|
|
498
603
|
const availability = await WhatsAppBackgroundBrowser.checkAvailability();
|
|
499
604
|
if (!availability.ok) {
|
|
@@ -502,7 +607,7 @@ export async function detectWhatsAppBackgroundStatus() {
|
|
|
502
607
|
notes: availability.reason || "Playwright nao esta disponivel para verificar o WhatsApp Web em background.",
|
|
503
608
|
};
|
|
504
609
|
}
|
|
505
|
-
const browser = new WhatsAppBackgroundBrowser({ headless: true });
|
|
610
|
+
const browser = new WhatsAppBackgroundBrowser({ headless: false, background: true });
|
|
506
611
|
try {
|
|
507
612
|
const state = await browser.waitForStableSessionState({
|
|
508
613
|
timeoutMs: DEFAULT_EXPECTED_CONNECTED_TIMEOUT_MS,
|
|
@@ -522,7 +627,7 @@ export async function detectWhatsAppBackgroundStatus() {
|
|
|
522
627
|
}
|
|
523
628
|
}
|
|
524
629
|
async function verifyWhatsAppBackgroundSessionPersistence(options) {
|
|
525
|
-
const browser = new WhatsAppBackgroundBrowser({ headless: true });
|
|
630
|
+
const browser = new WhatsAppBackgroundBrowser({ headless: false, background: true });
|
|
526
631
|
try {
|
|
527
632
|
options?.log?.("Confirmando se a sessao persistiu no browser em background do bridge...");
|
|
528
633
|
const state = await browser.waitForStableSessionState({
|