@leg3ndy/otto-bridge 0.6.1 → 0.6.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/README.md +9 -3
- package/dist/executors/native_macos.js +77 -7
- package/dist/extensions.js +2 -0
- package/dist/main.js +42 -1
- package/dist/runtime.js +45 -1
- package/dist/types.js +1 -1
- package/dist/whatsapp_background.js +32 -2
- 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.2.tgz
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
No `0.6.
|
|
39
|
+
No `0.6.2`, `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.2`:
|
|
110
110
|
|
|
111
111
|
```bash
|
|
112
112
|
otto-bridge extensions --install whatsappweb
|
|
@@ -116,6 +116,12 @@ 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.2`:
|
|
120
|
+
|
|
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
|
|
123
|
+
- ao parar o `otto-bridge run`: o browser em background e desligado, mas a sessao local fica lembrada para o proximo boot
|
|
124
|
+
|
|
119
125
|
### Ver estado local
|
|
120
126
|
|
|
121
127
|
```bash
|
|
@@ -970,9 +970,13 @@ export class NativeMacOSJobExecutor {
|
|
|
970
970
|
lastVisualTargetDescription = null;
|
|
971
971
|
lastVisualTargetApp = null;
|
|
972
972
|
whatsappBackgroundBrowser = null;
|
|
973
|
+
whatsappRuntimeMonitor = null;
|
|
973
974
|
constructor(bridgeConfig) {
|
|
974
975
|
this.bridgeConfig = bridgeConfig;
|
|
975
976
|
}
|
|
977
|
+
async start() {
|
|
978
|
+
await this.ensureManagedBackgroundServices();
|
|
979
|
+
}
|
|
976
980
|
async run(job, reporter) {
|
|
977
981
|
if (process.platform !== "darwin") {
|
|
978
982
|
throw new Error("The native-macos executor only runs on macOS");
|
|
@@ -1481,7 +1485,6 @@ export class NativeMacOSJobExecutor {
|
|
|
1481
1485
|
await reporter.completed(resultPayload);
|
|
1482
1486
|
}
|
|
1483
1487
|
finally {
|
|
1484
|
-
await this.closeWhatsAppBackgroundBrowser();
|
|
1485
1488
|
this.cancelledJobs.delete(job.job_id);
|
|
1486
1489
|
}
|
|
1487
1490
|
}
|
|
@@ -1492,6 +1495,14 @@ export class NativeMacOSJobExecutor {
|
|
|
1492
1495
|
this.activeChild = null;
|
|
1493
1496
|
}
|
|
1494
1497
|
}
|
|
1498
|
+
async close() {
|
|
1499
|
+
if (this.whatsappRuntimeMonitor) {
|
|
1500
|
+
clearInterval(this.whatsappRuntimeMonitor);
|
|
1501
|
+
this.whatsappRuntimeMonitor = null;
|
|
1502
|
+
}
|
|
1503
|
+
await this.syncWhatsAppRuntimeDetached();
|
|
1504
|
+
await this.closeWhatsAppBackgroundBrowser();
|
|
1505
|
+
}
|
|
1495
1506
|
assertNotCancelled(jobId) {
|
|
1496
1507
|
if (this.cancelledJobs.has(jobId)) {
|
|
1497
1508
|
throw new JobCancelledError(jobId);
|
|
@@ -2115,16 +2126,32 @@ end repeat
|
|
|
2115
2126
|
activate,
|
|
2116
2127
|
};
|
|
2117
2128
|
}
|
|
2118
|
-
async syncWhatsAppExtensionState(status, notes) {
|
|
2129
|
+
async syncWhatsAppExtensionState(status, notes, options) {
|
|
2119
2130
|
const current = await loadManagedBridgeExtensionState(WHATSAPP_WEB_EXTENSION_SLUG).catch(() => null);
|
|
2120
2131
|
if (!current) {
|
|
2121
2132
|
return;
|
|
2122
2133
|
}
|
|
2134
|
+
const nowIso = new Date().toISOString();
|
|
2123
2135
|
await saveManagedBridgeExtensionState(WHATSAPP_WEB_EXTENSION_SLUG, {
|
|
2124
2136
|
...current,
|
|
2125
2137
|
status,
|
|
2126
2138
|
notes,
|
|
2127
|
-
|
|
2139
|
+
runtimeAttached: options?.runtimeAttached ?? current.runtimeAttached,
|
|
2140
|
+
lastRuntimeHeartbeatAt: options?.runtimeAttached !== undefined
|
|
2141
|
+
? nowIso
|
|
2142
|
+
: current.lastRuntimeHeartbeatAt,
|
|
2143
|
+
lastStatusCheckAt: nowIso,
|
|
2144
|
+
}).catch(() => undefined);
|
|
2145
|
+
}
|
|
2146
|
+
async syncWhatsAppRuntimeDetached() {
|
|
2147
|
+
const current = await loadManagedBridgeExtensionState(WHATSAPP_WEB_EXTENSION_SLUG).catch(() => null);
|
|
2148
|
+
if (!current) {
|
|
2149
|
+
return;
|
|
2150
|
+
}
|
|
2151
|
+
await saveManagedBridgeExtensionState(WHATSAPP_WEB_EXTENSION_SLUG, {
|
|
2152
|
+
...current,
|
|
2153
|
+
runtimeAttached: false,
|
|
2154
|
+
lastRuntimeHeartbeatAt: new Date().toISOString(),
|
|
2128
2155
|
}).catch(() => undefined);
|
|
2129
2156
|
}
|
|
2130
2157
|
async getWhatsAppBackgroundBrowser() {
|
|
@@ -2191,9 +2218,9 @@ return {
|
|
|
2191
2218
|
const backgroundBrowser = await this.getWhatsAppBackgroundBrowser().catch(() => null);
|
|
2192
2219
|
if (backgroundBrowser) {
|
|
2193
2220
|
try {
|
|
2194
|
-
const state = await backgroundBrowser.
|
|
2221
|
+
const state = await backgroundBrowser.waitForStableSessionState();
|
|
2195
2222
|
if (state.connected) {
|
|
2196
|
-
await this.syncWhatsAppExtensionState("connected", "Sessao local do WhatsApp Web pronta para uso no browser em background.");
|
|
2223
|
+
await this.syncWhatsAppExtensionState("connected", "Sessao local do WhatsApp Web pronta para uso no browser em background.", { runtimeAttached: true });
|
|
2197
2224
|
return;
|
|
2198
2225
|
}
|
|
2199
2226
|
const disconnectedStatus = currentState.status === "connected" || currentState.status === "session_expired"
|
|
@@ -2201,7 +2228,8 @@ return {
|
|
|
2201
2228
|
: "waiting_login";
|
|
2202
2229
|
await this.syncWhatsAppExtensionState(disconnectedStatus, state.qrVisible
|
|
2203
2230
|
? "QR code visivel no browser persistente. Escaneie com o celular para concluir o login."
|
|
2204
|
-
: "Browser persistente do WhatsApp Web aberto, mas a sessao ainda nao esta pronta.");
|
|
2231
|
+
: "Browser persistente do WhatsApp Web aberto, mas a sessao ainda nao esta pronta.", { runtimeAttached: false });
|
|
2232
|
+
await this.closeWhatsAppBackgroundBrowser();
|
|
2205
2233
|
if (disconnectedStatus === "session_expired") {
|
|
2206
2234
|
throw new Error("A sessao do WhatsApp Web expirou nesta maquina. Rode `otto-bridge extensions --setup whatsappweb` para fazer login de novo e depois `otto-bridge extensions --status whatsappweb`.");
|
|
2207
2235
|
}
|
|
@@ -2261,7 +2289,49 @@ return {
|
|
|
2261
2289
|
}
|
|
2262
2290
|
throw new Error("WhatsApp Web ainda nao esta conectado nesta maquina. Rode `otto-bridge extensions --setup whatsappweb`, escaneie o QR code e depois `otto-bridge extensions --status whatsappweb`.");
|
|
2263
2291
|
}
|
|
2264
|
-
await this.syncWhatsAppExtensionState("connected", "Sessao local do WhatsApp Web pronta para uso.");
|
|
2292
|
+
await this.syncWhatsAppExtensionState("connected", "Sessao local do WhatsApp Web pronta para uso.", { runtimeAttached: false });
|
|
2293
|
+
}
|
|
2294
|
+
async ensureManagedBackgroundServices() {
|
|
2295
|
+
if (!this.hasInstalledBridgeExtension(WHATSAPP_WEB_EXTENSION_SLUG)) {
|
|
2296
|
+
return;
|
|
2297
|
+
}
|
|
2298
|
+
if (!this.whatsappRuntimeMonitor) {
|
|
2299
|
+
this.whatsappRuntimeMonitor = setInterval(() => {
|
|
2300
|
+
void this.refreshWhatsAppBackgroundRuntime();
|
|
2301
|
+
}, 60_000);
|
|
2302
|
+
}
|
|
2303
|
+
await this.refreshWhatsAppBackgroundRuntime();
|
|
2304
|
+
}
|
|
2305
|
+
async refreshWhatsAppBackgroundRuntime() {
|
|
2306
|
+
const currentState = await loadManagedBridgeExtensionState(WHATSAPP_WEB_EXTENSION_SLUG).catch(() => null);
|
|
2307
|
+
if (!currentState) {
|
|
2308
|
+
return;
|
|
2309
|
+
}
|
|
2310
|
+
if (currentState.status !== "connected") {
|
|
2311
|
+
await this.syncWhatsAppRuntimeDetached();
|
|
2312
|
+
await this.closeWhatsAppBackgroundBrowser();
|
|
2313
|
+
return;
|
|
2314
|
+
}
|
|
2315
|
+
const backgroundBrowser = await this.getWhatsAppBackgroundBrowser().catch(() => null);
|
|
2316
|
+
if (!backgroundBrowser) {
|
|
2317
|
+
return;
|
|
2318
|
+
}
|
|
2319
|
+
try {
|
|
2320
|
+
const state = await backgroundBrowser.waitForStableSessionState({ timeoutMs: 12_000 });
|
|
2321
|
+
if (state.connected) {
|
|
2322
|
+
await this.syncWhatsAppExtensionState("connected", "Sessao local do WhatsApp Web mantida em background enquanto `otto-bridge run` estiver ativo.", { runtimeAttached: true });
|
|
2323
|
+
return;
|
|
2324
|
+
}
|
|
2325
|
+
await this.syncWhatsAppExtensionState("session_expired", state.qrVisible
|
|
2326
|
+
? "A sessao do WhatsApp Web expirou durante o runtime. Rode `otto-bridge extensions --setup whatsappweb` para abrir o QR code novamente."
|
|
2327
|
+
: "O browser persistente do WhatsApp Web nao confirmou uma sessao pronta durante o runtime.", { runtimeAttached: false });
|
|
2328
|
+
await this.closeWhatsAppBackgroundBrowser();
|
|
2329
|
+
}
|
|
2330
|
+
catch (error) {
|
|
2331
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
2332
|
+
await this.syncWhatsAppExtensionState("session_expired", detail || "Nao consegui manter a sessao do WhatsApp Web no runtime em background.", { runtimeAttached: false });
|
|
2333
|
+
await this.closeWhatsAppBackgroundBrowser();
|
|
2334
|
+
}
|
|
2265
2335
|
}
|
|
2266
2336
|
async selectWhatsAppConversation(contact) {
|
|
2267
2337
|
const backgroundBrowser = await this.getWhatsAppBackgroundBrowser().catch(() => null);
|
package/dist/extensions.js
CHANGED
|
@@ -58,6 +58,8 @@ export async function buildInstalledManagedExtensionState(slug) {
|
|
|
58
58
|
installedAt: existing?.installedAt || new Date().toISOString(),
|
|
59
59
|
lastSetupAt: existing?.lastSetupAt,
|
|
60
60
|
lastStatusCheckAt: existing?.lastStatusCheckAt,
|
|
61
|
+
runtimeAttached: existing?.runtimeAttached,
|
|
62
|
+
lastRuntimeHeartbeatAt: existing?.lastRuntimeHeartbeatAt,
|
|
61
63
|
notes: existing?.notes,
|
|
62
64
|
};
|
|
63
65
|
}
|
package/dist/main.js
CHANGED
|
@@ -7,6 +7,7 @@ import { pairDevice } from "./pairing.js";
|
|
|
7
7
|
import { BridgeRuntime } from "./runtime.js";
|
|
8
8
|
import { detectWhatsAppBackgroundStatus, runWhatsAppBackgroundSetup, } from "./whatsapp_background.js";
|
|
9
9
|
import { BRIDGE_PACKAGE_NAME, BRIDGE_VERSION, DEFAULT_PAIR_TIMEOUT_SECONDS, DEFAULT_POLL_INTERVAL_MS, } from "./types.js";
|
|
10
|
+
const RUNTIME_STATUS_FRESHNESS_MS = 90_000;
|
|
10
11
|
function parseArgs(argv) {
|
|
11
12
|
const [maybeCommand, ...rest] = argv;
|
|
12
13
|
if (maybeCommand === "--help" || maybeCommand === "-h") {
|
|
@@ -46,6 +47,13 @@ function numberOption(args, name) {
|
|
|
46
47
|
const parsed = Number(value);
|
|
47
48
|
return Number.isFinite(parsed) ? parsed : undefined;
|
|
48
49
|
}
|
|
50
|
+
function hasFreshRuntimeAttachment(state) {
|
|
51
|
+
if (!state?.runtimeAttached || !state.lastRuntimeHeartbeatAt) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
const lastHeartbeatAt = Date.parse(state.lastRuntimeHeartbeatAt);
|
|
55
|
+
return Number.isFinite(lastHeartbeatAt) && Date.now() - lastHeartbeatAt <= RUNTIME_STATUS_FRESHNESS_MS;
|
|
56
|
+
}
|
|
49
57
|
function resolveExecutorOverrides(args, current) {
|
|
50
58
|
return resolveExecutorConfig({
|
|
51
59
|
type: option(args, "executor") || process.env.OTTO_BRIDGE_EXECUTOR,
|
|
@@ -112,6 +120,12 @@ async function detectManagedWhatsAppWebStatus() {
|
|
|
112
120
|
}
|
|
113
121
|
async function detectManagedExtensionStatus(slug, currentState) {
|
|
114
122
|
if (slug === "whatsappweb") {
|
|
123
|
+
if (hasFreshRuntimeAttachment(currentState)) {
|
|
124
|
+
return {
|
|
125
|
+
status: "connected",
|
|
126
|
+
notes: currentState?.notes || "Sessao local do WhatsApp Web mantida em background enquanto `otto-bridge run` estiver ativo.",
|
|
127
|
+
};
|
|
128
|
+
}
|
|
115
129
|
const detected = await detectManagedWhatsAppWebStatus();
|
|
116
130
|
const shouldMarkExpired = (detected.status === "waiting_login"
|
|
117
131
|
&& (currentState?.status === "connected" || currentState?.status === "session_expired"));
|
|
@@ -161,7 +175,31 @@ async function runRuntimeCommand(args) {
|
|
|
161
175
|
executor: resolveExecutorOverrides(args, config.executor),
|
|
162
176
|
};
|
|
163
177
|
const runtime = new BridgeRuntime(runtimeConfig);
|
|
164
|
-
|
|
178
|
+
let stopping = false;
|
|
179
|
+
const shutdown = async (signal) => {
|
|
180
|
+
if (stopping) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
stopping = true;
|
|
184
|
+
console.log(`[otto-bridge] shutting down runtime after ${signal}`);
|
|
185
|
+
await runtime.stop().catch(() => undefined);
|
|
186
|
+
};
|
|
187
|
+
const handleSigint = () => {
|
|
188
|
+
void shutdown("SIGINT");
|
|
189
|
+
};
|
|
190
|
+
const handleSigterm = () => {
|
|
191
|
+
void shutdown("SIGTERM");
|
|
192
|
+
};
|
|
193
|
+
process.once("SIGINT", handleSigint);
|
|
194
|
+
process.once("SIGTERM", handleSigterm);
|
|
195
|
+
try {
|
|
196
|
+
await runtime.start();
|
|
197
|
+
}
|
|
198
|
+
finally {
|
|
199
|
+
process.off("SIGINT", handleSigint);
|
|
200
|
+
process.off("SIGTERM", handleSigterm);
|
|
201
|
+
await runtime.stop().catch(() => undefined);
|
|
202
|
+
}
|
|
165
203
|
}
|
|
166
204
|
async function runStatusCommand() {
|
|
167
205
|
const config = await loadBridgeConfig();
|
|
@@ -252,6 +290,7 @@ async function runExtensionsCommand(args) {
|
|
|
252
290
|
};
|
|
253
291
|
await saveManagedBridgeExtensionState(slug, {
|
|
254
292
|
...baseState,
|
|
293
|
+
runtimeAttached: false,
|
|
255
294
|
notes: `Setup iniciado para ${currentState.displayName}. Aguarde o login no browser persistente e depois rode \`otto-bridge extensions --status ${slug}\`.`,
|
|
256
295
|
});
|
|
257
296
|
if (slug === "whatsappweb") {
|
|
@@ -263,6 +302,7 @@ async function runExtensionsCommand(args) {
|
|
|
263
302
|
status: detected.status,
|
|
264
303
|
notes: detected.notes,
|
|
265
304
|
lastStatusCheckAt: new Date().toISOString(),
|
|
305
|
+
runtimeAttached: false,
|
|
266
306
|
});
|
|
267
307
|
console.log(`[otto-bridge] ${slug}: ${formatManagedBridgeExtensionStatus(detected.status)}`);
|
|
268
308
|
if (detected.notes) {
|
|
@@ -293,6 +333,7 @@ async function runExtensionsCommand(args) {
|
|
|
293
333
|
...currentState,
|
|
294
334
|
status: detected.status,
|
|
295
335
|
lastStatusCheckAt: new Date().toISOString(),
|
|
336
|
+
runtimeAttached: hasFreshRuntimeAttachment(currentState) && detected.status === "connected",
|
|
296
337
|
notes: detected.notes || currentState.notes,
|
|
297
338
|
};
|
|
298
339
|
await saveManagedBridgeExtensionState(slug, nextState);
|
package/dist/runtime.js
CHANGED
|
@@ -73,6 +73,9 @@ export class BridgeRuntime {
|
|
|
73
73
|
reconnectDelayMs = DEFAULT_RECONNECT_BASE_DELAY_MS;
|
|
74
74
|
executor;
|
|
75
75
|
lastBridgeReleaseNoticeKey = null;
|
|
76
|
+
activeSocket = null;
|
|
77
|
+
stopped = false;
|
|
78
|
+
started = false;
|
|
76
79
|
pendingConfirmations = new Map();
|
|
77
80
|
activeCancels = new Map();
|
|
78
81
|
constructor(config, executor) {
|
|
@@ -136,23 +139,62 @@ export class BridgeRuntime {
|
|
|
136
139
|
}));
|
|
137
140
|
}
|
|
138
141
|
async start() {
|
|
142
|
+
if (!this.started) {
|
|
143
|
+
this.started = true;
|
|
144
|
+
if (typeof this.executor.start === "function") {
|
|
145
|
+
await this.executor.start();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
139
148
|
console.log(`[otto-bridge] runtime start device=${this.config.deviceId}`);
|
|
140
|
-
while (
|
|
149
|
+
while (!this.stopped) {
|
|
141
150
|
try {
|
|
142
151
|
await this.connectOnce();
|
|
143
152
|
this.reconnectDelayMs = DEFAULT_RECONNECT_BASE_DELAY_MS;
|
|
144
153
|
}
|
|
145
154
|
catch (error) {
|
|
155
|
+
if (this.stopped) {
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
146
158
|
const message = error instanceof Error ? error.message : String(error);
|
|
147
159
|
console.error(`[otto-bridge] socket error: ${message}`);
|
|
148
160
|
}
|
|
161
|
+
if (this.stopped) {
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
149
164
|
console.log(`[otto-bridge] reconnecting in ${this.reconnectDelayMs}ms`);
|
|
150
165
|
await delay(this.reconnectDelayMs);
|
|
151
166
|
this.reconnectDelayMs = Math.min(this.reconnectDelayMs * 2, DEFAULT_RECONNECT_MAX_DELAY_MS);
|
|
152
167
|
}
|
|
153
168
|
}
|
|
169
|
+
async stop() {
|
|
170
|
+
this.stopped = true;
|
|
171
|
+
for (const [jobId, cancel] of this.activeCancels.entries()) {
|
|
172
|
+
try {
|
|
173
|
+
await cancel();
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
// ignore shutdown cancellation errors
|
|
177
|
+
}
|
|
178
|
+
finally {
|
|
179
|
+
this.activeCancels.delete(jobId);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
try {
|
|
183
|
+
this.activeSocket?.close();
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
// ignore socket close errors during shutdown
|
|
187
|
+
}
|
|
188
|
+
finally {
|
|
189
|
+
this.activeSocket = null;
|
|
190
|
+
}
|
|
191
|
+
if (typeof this.executor.close === "function") {
|
|
192
|
+
await this.executor.close();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
154
195
|
async connectOnce() {
|
|
155
196
|
const socket = new WebSocket(this.config.wsUrl, ["device", this.config.deviceToken]);
|
|
197
|
+
this.activeSocket = socket;
|
|
156
198
|
let heartbeatTimer = null;
|
|
157
199
|
const stopHeartbeat = () => {
|
|
158
200
|
if (heartbeatTimer) {
|
|
@@ -196,12 +238,14 @@ export class BridgeRuntime {
|
|
|
196
238
|
socket.addEventListener("close", (event) => {
|
|
197
239
|
stopHeartbeat();
|
|
198
240
|
rejectPendingConfirmations(new Error("WebSocket closed while awaiting confirmation"));
|
|
241
|
+
this.activeSocket = null;
|
|
199
242
|
console.log(`[otto-bridge] socket closed code=${event.code}`);
|
|
200
243
|
resolve();
|
|
201
244
|
});
|
|
202
245
|
socket.addEventListener("error", () => {
|
|
203
246
|
stopHeartbeat();
|
|
204
247
|
rejectPendingConfirmations(new Error("WebSocket failed while awaiting confirmation"));
|
|
248
|
+
this.activeSocket = null;
|
|
205
249
|
try {
|
|
206
250
|
socket.close();
|
|
207
251
|
}
|
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.2";
|
|
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;
|
|
@@ -4,6 +4,9 @@ import { fileURLToPath, pathToFileURL } from "node:url";
|
|
|
4
4
|
import { getBridgeHomeDir } from "./config.js";
|
|
5
5
|
export const WHATSAPP_WEB_URL = "https://web.whatsapp.com";
|
|
6
6
|
const DEFAULT_SETUP_TIMEOUT_MS = 5 * 60 * 1000;
|
|
7
|
+
const DEFAULT_SESSION_SETTLE_TIMEOUT_MS = 15_000;
|
|
8
|
+
const DEFAULT_SESSION_POLL_INTERVAL_MS = 1_000;
|
|
9
|
+
const DEFAULT_QR_STABILITY_WINDOW_MS = 4_000;
|
|
7
10
|
function moduleDir() {
|
|
8
11
|
return path.dirname(fileURLToPath(import.meta.url));
|
|
9
12
|
}
|
|
@@ -158,7 +161,7 @@ export class WhatsAppBackgroundBrowser {
|
|
|
158
161
|
};
|
|
159
162
|
}
|
|
160
163
|
async ensureReady() {
|
|
161
|
-
const state = await this.
|
|
164
|
+
const state = await this.waitForStableSessionState();
|
|
162
165
|
if (state.connected) {
|
|
163
166
|
return state;
|
|
164
167
|
}
|
|
@@ -179,6 +182,33 @@ export class WhatsAppBackgroundBrowser {
|
|
|
179
182
|
}
|
|
180
183
|
return lastState || await this.getSessionState();
|
|
181
184
|
}
|
|
185
|
+
async waitForStableSessionState(options) {
|
|
186
|
+
const timeoutMs = Math.max(2_000, Number(options?.timeoutMs || DEFAULT_SESSION_SETTLE_TIMEOUT_MS));
|
|
187
|
+
const pollIntervalMs = Math.max(250, Number(options?.pollIntervalMs || DEFAULT_SESSION_POLL_INTERVAL_MS));
|
|
188
|
+
const qrStabilityWindowMs = Math.max(pollIntervalMs, Number(options?.qrStabilityWindowMs || DEFAULT_QR_STABILITY_WINDOW_MS));
|
|
189
|
+
const deadline = Date.now() + timeoutMs;
|
|
190
|
+
let lastState = null;
|
|
191
|
+
let qrVisibleSince = null;
|
|
192
|
+
while (Date.now() < deadline) {
|
|
193
|
+
lastState = await this.getSessionState();
|
|
194
|
+
if (lastState.connected) {
|
|
195
|
+
return lastState;
|
|
196
|
+
}
|
|
197
|
+
if (lastState.qrVisible) {
|
|
198
|
+
if (qrVisibleSince === null) {
|
|
199
|
+
qrVisibleSince = Date.now();
|
|
200
|
+
}
|
|
201
|
+
else if (Date.now() - qrVisibleSince >= qrStabilityWindowMs) {
|
|
202
|
+
return lastState;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
qrVisibleSince = null;
|
|
207
|
+
}
|
|
208
|
+
await this.page?.waitForTimeout(pollIntervalMs);
|
|
209
|
+
}
|
|
210
|
+
return lastState || await this.getSessionState();
|
|
211
|
+
}
|
|
182
212
|
async selectConversation(contact) {
|
|
183
213
|
await this.ensureReady();
|
|
184
214
|
const prepared = await this.withPage((page) => page.evaluate((query) => {
|
|
@@ -464,7 +494,7 @@ export async function detectWhatsAppBackgroundStatus() {
|
|
|
464
494
|
}
|
|
465
495
|
const browser = new WhatsAppBackgroundBrowser({ headless: true });
|
|
466
496
|
try {
|
|
467
|
-
const state = await browser.
|
|
497
|
+
const state = await browser.waitForStableSessionState();
|
|
468
498
|
return sessionStateToStatus(state);
|
|
469
499
|
}
|
|
470
500
|
catch (error) {
|