@leg3ndy/otto-bridge 0.8.2 → 0.8.3

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.
@@ -7,6 +7,7 @@ import { JobCancelledError } from "./shared.js";
7
7
  import { loadManagedBridgeExtensionState, saveManagedBridgeExtensionState, } from "../extensions.js";
8
8
  import { postDeviceJson, uploadDeviceJobArtifact } from "../http.js";
9
9
  import { WHATSAPP_WEB_URL, WhatsAppBackgroundBrowser, } from "../whatsapp_background.js";
10
+ import { verifyExpectedWhatsAppMessage } from "../whatsapp_verification.js";
10
11
  const KNOWN_APPS = [
11
12
  { canonical: "Safari", patterns: [/\bsafari\b/i] },
12
13
  { canonical: "Google Chrome", patterns: [/\bgoogle chrome\b/i, /\bchrome\b/i] },
@@ -1344,10 +1345,6 @@ export class NativeMacOSJobExecutor {
1344
1345
  await reporter.progress(progressPercent, `Enviando a mensagem para ${action.contact} no WhatsApp`);
1345
1346
  await this.sendWhatsAppMessage(action.text);
1346
1347
  await delay(900);
1347
- const verification = await this.verifyWhatsAppLastMessageAgainstBaseline(action.text, beforeSend.messages);
1348
- if (!verification.ok) {
1349
- throw new Error(verification.reason || `Nao consegui confirmar o envio da mensagem para ${action.contact} no WhatsApp.`);
1350
- }
1351
1348
  const afterSend = await this.readWhatsAppVisibleConversation(action.contact, Math.max(12, beforeSend.messages.length + 4)).catch(() => null);
1352
1349
  resultPayload.whatsapp = {
1353
1350
  action: "send_message",
@@ -1356,6 +1353,12 @@ export class NativeMacOSJobExecutor {
1356
1353
  messages: afterSend?.messages || [],
1357
1354
  summary: afterSend?.summary || "",
1358
1355
  };
1356
+ const verification = await this.verifyWhatsAppLastMessageAgainstBaseline(action.text, beforeSend.messages);
1357
+ if (!verification.ok) {
1358
+ resultPayload.summary = verification.reason || `Nao consegui confirmar o envio da mensagem para ${action.contact} no WhatsApp.`;
1359
+ await reporter.failed(verification.reason || `Nao consegui confirmar o envio da mensagem para ${action.contact} no WhatsApp.`, resultPayload);
1360
+ return;
1361
+ }
1359
1362
  completionNotes.push(`Enviei no WhatsApp para ${action.contact}: ${clipText(action.text, 180)}`);
1360
1363
  continue;
1361
1364
  }
@@ -3164,42 +3167,7 @@ return { messages: messages.slice(-maxMessages) };
3164
3167
  }
3165
3168
  const baseline = Array.isArray(previousMessages) ? previousMessages : [];
3166
3169
  const chat = await this.readWhatsAppVisibleConversation("Contato", Math.max(8, baseline.length + 2));
3167
- if (!chat.messages.length) {
3168
- return {
3169
- ok: false,
3170
- reason: "Nao consegui ler as mensagens visiveis apos o envio no WhatsApp.",
3171
- };
3172
- }
3173
- const normalizedExpected = normalizeText(expectedText).slice(0, 120);
3174
- const normalizeMessage = (item) => `${normalizeText(item.author)}|${normalizeText(item.text)}`;
3175
- const beforeSignature = baseline.map(normalizeMessage).join("\n");
3176
- const afterSignature = chat.messages.map(normalizeMessage).join("\n");
3177
- const changed = beforeSignature !== afterSignature;
3178
- const beforeMatches = baseline.filter((item) => normalizeText(item.text).includes(normalizedExpected)).length;
3179
- const afterMatches = chat.messages.filter((item) => normalizeText(item.text).includes(normalizedExpected)).length;
3180
- const latest = chat.messages[chat.messages.length - 1] || null;
3181
- const latestAuthor = normalizeText(latest?.author || "");
3182
- const latestText = normalizeText(latest?.text || "");
3183
- const latestMatches = latestText.includes(normalizedExpected) && (latestAuthor === "voce" || latestAuthor === "você");
3184
- if ((changed && latestMatches) || (changed && afterMatches > beforeMatches)) {
3185
- return { ok: true, reason: "" };
3186
- }
3187
- if (!changed) {
3188
- return {
3189
- ok: false,
3190
- reason: "O WhatsApp nao mostrou mudanca visivel na conversa depois da tentativa de envio.",
3191
- };
3192
- }
3193
- if (afterMatches <= beforeMatches) {
3194
- return {
3195
- ok: false,
3196
- reason: "A conversa mudou, mas nao apareceu uma nova mensagem com o texto esperado no WhatsApp.",
3197
- };
3198
- }
3199
- return {
3200
- ok: false,
3201
- reason: "Nao consegui confirmar visualmente a nova mensagem enviada no WhatsApp.",
3202
- };
3170
+ return verifyExpectedWhatsAppMessage(expectedText, baseline, chat.messages);
3203
3171
  }
3204
3172
  async takeScreenshot(targetPath) {
3205
3173
  const artifactsDir = path.join(os.homedir(), ".otto-bridge", "artifacts");
@@ -5,16 +5,10 @@ import path from "node:path";
5
5
  import process from "node:process";
6
6
  import { getBridgeHomeDir } from "./config.js";
7
7
  import { MACOS_WHATSAPP_HELPER_SWIFT_SOURCE } from "./macos_whatsapp_helper_source.js";
8
+ import { verifyExpectedWhatsAppMessage } from "./whatsapp_verification.js";
8
9
  function delay(ms) {
9
10
  return new Promise((resolve) => setTimeout(resolve, ms));
10
11
  }
11
- function normalizeText(value) {
12
- return String(value || "")
13
- .normalize("NFD")
14
- .replace(/[\u0300-\u036f]/g, "")
15
- .toLowerCase()
16
- .trim();
17
- }
18
12
  async function runHelperCommand(command, args) {
19
13
  return await new Promise((resolve, reject) => {
20
14
  const child = spawn(command, args, { stdio: ["ignore", "pipe", "pipe"] });
@@ -743,42 +737,7 @@ export class MacOSWhatsAppHelperRuntime {
743
737
  async verifyLastMessage(expectedText, previousMessages) {
744
738
  const baseline = Array.isArray(previousMessages) ? previousMessages : [];
745
739
  const chat = await this.readVisibleConversation(Math.max(8, baseline.length + 2));
746
- if (!chat.messages.length) {
747
- return {
748
- ok: false,
749
- reason: "Nao consegui ler as mensagens visiveis apos o envio no WhatsApp.",
750
- };
751
- }
752
- const normalizedExpected = normalizeText(expectedText).slice(0, 120);
753
- const normalizeMessage = (item) => `${normalizeText(item.author)}|${normalizeText(item.text)}`;
754
- const beforeSignature = baseline.map(normalizeMessage).join("\n");
755
- const afterSignature = chat.messages.map(normalizeMessage).join("\n");
756
- const changed = beforeSignature !== afterSignature;
757
- const beforeMatches = baseline.filter((item) => normalizeText(item.text).includes(normalizedExpected)).length;
758
- const afterMatches = chat.messages.filter((item) => normalizeText(item.text).includes(normalizedExpected)).length;
759
- const latest = chat.messages[chat.messages.length - 1] || null;
760
- const latestAuthor = normalizeText(latest?.author || "");
761
- const latestText = normalizeText(latest?.text || "");
762
- const latestMatches = latestText.includes(normalizedExpected) && (latestAuthor === "voce" || latestAuthor === "você");
763
- if ((changed && latestMatches) || (changed && afterMatches > beforeMatches)) {
764
- return { ok: true, reason: "" };
765
- }
766
- if (!changed) {
767
- return {
768
- ok: false,
769
- reason: "O WhatsApp nao mostrou mudanca visivel na conversa depois da tentativa de envio.",
770
- };
771
- }
772
- if (afterMatches <= beforeMatches) {
773
- return {
774
- ok: false,
775
- reason: "A conversa mudou, mas nao apareceu uma nova mensagem com o texto esperado no WhatsApp.",
776
- };
777
- }
778
- return {
779
- ok: false,
780
- reason: "Nao consegui confirmar na conversa do WhatsApp se a nova mensagem foi enviada.",
781
- };
740
+ return verifyExpectedWhatsAppMessage(expectedText, baseline, chat.messages);
782
741
  }
783
742
  handleStdout(chunk) {
784
743
  this.stdoutBuffer += chunk;
package/dist/types.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export const BRIDGE_CONFIG_VERSION = 1;
2
- export const BRIDGE_VERSION = "0.8.2";
2
+ export const BRIDGE_VERSION = "0.8.3";
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;
@@ -5,6 +5,7 @@ import process from "node:process";
5
5
  import { fileURLToPath, pathToFileURL } from "node:url";
6
6
  import { getBridgeHomeDir } from "./config.js";
7
7
  import { checkMacOSWhatsAppHelperAvailability, MacOSWhatsAppHelperRuntime, } from "./macos_whatsapp_helper.js";
8
+ import { verifyExpectedWhatsAppMessage } from "./whatsapp_verification.js";
8
9
  import { getConfiguredWhatsAppRuntimeProvider } from "./whatsapp_runtime_provider.js";
9
10
  export const WHATSAPP_WEB_URL = "https://web.whatsapp.com";
10
11
  const DEFAULT_SETUP_TIMEOUT_MS = 5 * 60 * 1000;
@@ -44,13 +45,6 @@ async function loadEmbeddedPlaywrightModule() {
44
45
  }
45
46
  throw new Error(`Playwright nao esta disponivel para o Otto Bridge. Reinstale o pacote \`@leg3ndy/otto-bridge\` para garantir o browser persistente do bridge. ${errors.length ? `Detalhes: ${errors.join(" | ")}` : ""}`.trim());
46
47
  }
47
- function normalizeText(value) {
48
- return String(value || "")
49
- .normalize("NFD")
50
- .replace(/[\u0300-\u036f]/g, "")
51
- .toLowerCase()
52
- .trim();
53
- }
54
48
  export function getWhatsAppBrowserUserDataDir() {
55
49
  return path.join(getBridgeHomeDir(), "extensions", "whatsappweb-profile");
56
50
  }
@@ -806,42 +800,7 @@ export class WhatsAppBackgroundBrowser {
806
800
  }
807
801
  const baseline = Array.isArray(previousMessages) ? previousMessages : [];
808
802
  const chat = await this.readVisibleConversation(Math.max(8, baseline.length + 2));
809
- if (!chat.messages.length) {
810
- return {
811
- ok: false,
812
- reason: "Nao consegui ler as mensagens visiveis apos o envio no WhatsApp.",
813
- };
814
- }
815
- const normalizedExpected = normalizeText(expectedText).slice(0, 120);
816
- const normalizeMessage = (item) => `${normalizeText(item.author)}|${normalizeText(item.text)}`;
817
- const beforeSignature = baseline.map(normalizeMessage).join("\n");
818
- const afterSignature = chat.messages.map(normalizeMessage).join("\n");
819
- const changed = beforeSignature !== afterSignature;
820
- const beforeMatches = baseline.filter((item) => normalizeText(item.text).includes(normalizedExpected)).length;
821
- const afterMatches = chat.messages.filter((item) => normalizeText(item.text).includes(normalizedExpected)).length;
822
- const latest = chat.messages[chat.messages.length - 1] || null;
823
- const latestAuthor = normalizeText(latest?.author || "");
824
- const latestText = normalizeText(latest?.text || "");
825
- const latestMatches = latestText.includes(normalizedExpected) && (latestAuthor === "voce" || latestAuthor === "você");
826
- if ((changed && latestMatches) || (changed && afterMatches > beforeMatches)) {
827
- return { ok: true, reason: "" };
828
- }
829
- if (!changed) {
830
- return {
831
- ok: false,
832
- reason: "O WhatsApp nao mostrou mudanca visivel na conversa depois da tentativa de envio.",
833
- };
834
- }
835
- if (afterMatches <= beforeMatches) {
836
- return {
837
- ok: false,
838
- reason: "A conversa mudou, mas nao apareceu uma nova mensagem com o texto esperado no WhatsApp.",
839
- };
840
- }
841
- return {
842
- ok: false,
843
- reason: "Nao consegui confirmar na conversa do WhatsApp se a nova mensagem foi enviada.",
844
- };
803
+ return verifyExpectedWhatsAppMessage(expectedText, baseline, chat.messages);
845
804
  }
846
805
  async ensureWhatsAppPage() {
847
806
  const page = this.page;
@@ -0,0 +1,66 @@
1
+ function normalizeVerificationText(value) {
2
+ return String(value || "")
3
+ .normalize("NFD")
4
+ .replace(/[\u0300-\u036f]/g, "")
5
+ .replace(/\s+/g, " ")
6
+ .toLowerCase()
7
+ .trim();
8
+ }
9
+ function isOutboundAuthor(author) {
10
+ const normalized = normalizeVerificationText(author);
11
+ return normalized === "voce" || normalized === "you";
12
+ }
13
+ function messageSignature(messages) {
14
+ return messages
15
+ .map((item) => `${normalizeVerificationText(item.author)}|${normalizeVerificationText(item.text)}`)
16
+ .join("\n");
17
+ }
18
+ function countMatches(messages, normalizedExpected) {
19
+ return messages.filter((item) => normalizeVerificationText(item.text).includes(normalizedExpected)).length;
20
+ }
21
+ function countOutboundMatches(messages, normalizedExpected) {
22
+ return messages.filter((item) => {
23
+ if (!isOutboundAuthor(item.author)) {
24
+ return false;
25
+ }
26
+ return normalizeVerificationText(item.text).includes(normalizedExpected);
27
+ }).length;
28
+ }
29
+ export function verifyExpectedWhatsAppMessage(expectedText, previousMessages, observedMessages) {
30
+ if (!observedMessages.length) {
31
+ return {
32
+ ok: false,
33
+ reason: "Nao consegui ler as mensagens visiveis apos o envio no WhatsApp.",
34
+ };
35
+ }
36
+ const baseline = Array.isArray(previousMessages) ? previousMessages : [];
37
+ const normalizedExpected = normalizeVerificationText(expectedText).slice(0, 200);
38
+ const changed = messageSignature(baseline) !== messageSignature(observedMessages);
39
+ const beforeMatches = countMatches(baseline, normalizedExpected);
40
+ const afterMatches = countMatches(observedMessages, normalizedExpected);
41
+ const beforeOutboundMatches = countOutboundMatches(baseline, normalizedExpected);
42
+ const afterOutboundMatches = countOutboundMatches(observedMessages, normalizedExpected);
43
+ const latestOutbound = [...observedMessages].reverse().find((item) => isOutboundAuthor(item.author)) || null;
44
+ const latestOutboundMatches = latestOutbound
45
+ ? normalizeVerificationText(latestOutbound.text).includes(normalizedExpected)
46
+ : false;
47
+ if ((changed && latestOutboundMatches) || (changed && afterOutboundMatches > beforeOutboundMatches) || (changed && afterMatches > beforeMatches)) {
48
+ return { ok: true, reason: "" };
49
+ }
50
+ if (!changed) {
51
+ return {
52
+ ok: false,
53
+ reason: "O WhatsApp nao mostrou mudanca visivel na conversa depois da tentativa de envio.",
54
+ };
55
+ }
56
+ if (afterMatches <= beforeMatches) {
57
+ return {
58
+ ok: false,
59
+ reason: "A conversa mudou, mas nao apareceu uma nova mensagem com o texto esperado no WhatsApp.",
60
+ };
61
+ }
62
+ return {
63
+ ok: false,
64
+ reason: "Nao consegui confirmar na conversa do WhatsApp se a nova mensagem foi enviada.",
65
+ };
66
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leg3ndy/otto-bridge",
3
- "version": "0.8.2",
3
+ "version": "0.8.3",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Local companion for Otto Bridge device pairing and WebSocket runtime.",