@open-wa/wa-automate 5.4.0 → 5.5.1

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.
@@ -26,11 +26,12 @@ qrcode_terminal = __toESM(qrcode_terminal, 1);
26
26
  let node_path = require("node:path");
27
27
  let _open_wa_core = require("@open-wa/core");
28
28
  let _open_wa_client = require("@open-wa/client");
29
- let _open_wa_driver_lightpanda = require("@open-wa/driver-lightpanda");
30
- let _open_wa_driver_puppeteer = require("@open-wa/driver-puppeteer");
31
- let _open_wa_schema = require("@open-wa/schema");
32
29
  let _open_wa_config = require("@open-wa/config");
33
30
  let node_fs = require("node:fs");
31
+ let node_fs_promises = require("node:fs/promises");
32
+ let node_os = require("node:os");
33
+ let _open_wa_driver_interface = require("@open-wa/driver-interface");
34
+ let _open_wa_schema = require("@open-wa/schema");
34
35
  let node_url = require("node:url");
35
36
  let _open_wa_runtime_core = require("@open-wa/runtime-core");
36
37
  let _open_wa_runtime_node = require("@open-wa/runtime-node");
@@ -42,6 +43,30 @@ var WAServer = class extends _open_wa_api.ApiServer {
42
43
  };
43
44
  //#endregion
44
45
  //#region src/cli/output-sink.ts
46
+ async function promptChoice(question, choices) {
47
+ if (!process.stdin.isTTY || !process.stderr.isTTY) throw new Error("No browser is installed. Choose --use-chrome, --use-chromium or --use-lightpanda to download one without a prompt.");
48
+ const { createInterface } = await import("node:readline/promises");
49
+ const readline = createInterface({
50
+ input: process.stdin,
51
+ output: process.stderr
52
+ });
53
+ const abort = new AbortController();
54
+ readline.on("SIGINT", () => abort.abort());
55
+ readline.on("close", () => abort.abort());
56
+ try {
57
+ process.stderr.write(`\n${question}\n${choices.map((choice, index) => ` ${index + 1}. ${choice}`).join("\n")}\n`);
58
+ while (true) {
59
+ const answer = (await readline.question("Choose a browser [1]: ", { signal: abort.signal })).trim();
60
+ const choice = answer === "" ? 0 : Number(answer) - 1;
61
+ if (Number.isInteger(choice) && choice >= 0 && choice < choices.length) return choice;
62
+ process.stderr.write(`Enter a number from 1 to ${choices.length}.\n`);
63
+ }
64
+ } catch (cause) {
65
+ throw new Error("Browser selection cancelled.", { cause });
66
+ } finally {
67
+ readline.close();
68
+ }
69
+ }
45
70
  function writeToConsole(level, message) {
46
71
  if (level === "warn") {
47
72
  console.warn(message);
@@ -55,6 +80,7 @@ function writeToConsole(level, message) {
55
80
  }
56
81
  function createConsoleOutputSink() {
57
82
  return {
83
+ promptChoice,
58
84
  write(entry) {
59
85
  writeToConsole(entry.level, entry.message);
60
86
  },
@@ -77,6 +103,430 @@ function resetCliOutputSink() {
77
103
  activeSink = createConsoleOutputSink();
78
104
  }
79
105
  //#endregion
106
+ //#region src/browser-choice.ts
107
+ const choiceFile = (0, node_path.join)((0, node_os.homedir)(), ".cache", "open-wa", "browser-choice.json");
108
+ async function findSystemBrowser(kind) {
109
+ const app = kind === "chrome" ? "Google Chrome" : "Chromium";
110
+ const names = process.platform === "win32" ? ["chrome.exe"] : kind === "chrome" ? [
111
+ "google-chrome-stable",
112
+ "google-chrome",
113
+ "chrome"
114
+ ] : ["chromium", "chromium-browser"];
115
+ const candidates = process.platform === "darwin" ? ["/Applications", (0, node_path.join)((0, node_os.homedir)(), "Applications")].map((root) => (0, node_path.join)(root, `${app}.app`, "Contents", "MacOS", app)) : process.platform === "win32" ? [
116
+ process.env.LOCALAPPDATA,
117
+ process.env.PROGRAMFILES,
118
+ process.env["PROGRAMFILES(X86)"]
119
+ ].filter((root) => !!root).map((root) => kind === "chrome" ? (0, node_path.join)(root, "Google", "Chrome", "Application", "chrome.exe") : (0, node_path.join)(root, "Chromium", "Application", "chrome.exe")) : kind === "chrome" ? ["/opt/google/chrome/chrome"] : [];
120
+ candidates.push(...(process.env.PATH ?? "").split(node_path.delimiter).filter(Boolean).flatMap((root) => names.map((name) => (0, node_path.join)(root, name))));
121
+ for (const candidate of candidates) try {
122
+ if (!(await (0, node_fs_promises.stat)(candidate)).isFile()) continue;
123
+ await (0, node_fs_promises.access)(candidate, node_fs.constants.X_OK);
124
+ return candidate;
125
+ } catch {}
126
+ }
127
+ async function readBrowserChoice() {
128
+ const { Effect, Schema } = await import("effect");
129
+ const recordSchema = Schema.fromJsonString(Schema.Struct({
130
+ kind: Schema.Literals([
131
+ "chrome",
132
+ "chromium",
133
+ "lightpanda"
134
+ ]),
135
+ executablePath: Schema.String
136
+ }));
137
+ const io = (operation) => Effect.tryPromise({
138
+ try: operation,
139
+ catch: _open_wa_driver_interface.toPublicError
140
+ });
141
+ return Effect.runPromise(Effect.gen(function* () {
142
+ const record = yield* Schema.decodeUnknownEffect(recordSchema)(yield* io(() => (0, node_fs_promises.readFile)(choiceFile, "utf8")));
143
+ if (!(yield* io(() => (0, node_fs_promises.stat)(record.executablePath))).isFile()) return void 0;
144
+ yield* io(() => (0, node_fs_promises.access)(record.executablePath, node_fs.constants.X_OK));
145
+ return record;
146
+ }).pipe(Effect.catch(() => Effect.succeed(void 0))));
147
+ }
148
+ async function writeBrowserChoice(kind, executablePath) {
149
+ const { Effect } = await import("effect");
150
+ const io = (operation) => Effect.tryPromise({
151
+ try: operation,
152
+ catch: _open_wa_driver_interface.toPublicError
153
+ });
154
+ const program = Effect.gen(function* () {
155
+ const directory = (0, node_path.dirname)(choiceFile);
156
+ yield* io(() => (0, node_fs_promises.mkdir)(directory, { recursive: true }));
157
+ const staging = yield* Effect.acquireRelease(io(() => (0, node_fs_promises.mkdtemp)((0, node_path.join)(directory, ".browser-choice-"))), (path) => io(() => (0, node_fs_promises.rm)(path, {
158
+ recursive: true,
159
+ force: true
160
+ })).pipe(Effect.orDie));
161
+ const temporary = (0, node_path.join)(staging, "browser-choice.json");
162
+ yield* io(() => (0, node_fs_promises.writeFile)(temporary, JSON.stringify({
163
+ kind,
164
+ executablePath
165
+ }), {
166
+ encoding: "utf8",
167
+ flag: "wx"
168
+ }));
169
+ yield* io(() => (0, node_fs_promises.rename)(temporary, choiceFile));
170
+ });
171
+ await Effect.runPromise(Effect.scoped(Effect.uninterruptible(program))).catch((cause) => {
172
+ throw (0, _open_wa_driver_interface.toPublicError)(cause);
173
+ });
174
+ }
175
+ async function chooseBrowser() {
176
+ return [
177
+ "chrome",
178
+ "chromium",
179
+ "lightpanda"
180
+ ][await (getCliOutputSink().promptChoice ?? promptChoice)("Google Chrome was not found. Which browser would you like to download?", [
181
+ "Chrome for Testing — recommended; highest compatibility, including video and media.",
182
+ "Chromium — middle ground; proprietary video/audio codecs may be unavailable.",
183
+ "Lightpanda — lightweight basic automation; low compatibility, experimental WhatsApp support, no video or rendering."
184
+ ])];
185
+ }
186
+ //#endregion
187
+ //#region src/runtime-client.ts
188
+ function readChromePathCache(cacheFilePath) {
189
+ try {
190
+ if (!(0, node_fs.existsSync)(cacheFilePath)) return;
191
+ const raw = JSON.parse((0, node_fs.readFileSync)(cacheFilePath, "utf8"));
192
+ if (!raw || typeof raw !== "object") return;
193
+ return { executablePath: typeof raw.executablePath === "string" ? raw.executablePath : void 0 };
194
+ } catch {
195
+ return;
196
+ }
197
+ }
198
+ function writeChromePathCache(executablePath, cacheFilePath) {
199
+ (0, node_fs.mkdirSync)((0, node_path.dirname)(cacheFilePath), { recursive: true });
200
+ (0, node_fs.writeFileSync)(cacheFilePath, JSON.stringify({
201
+ executablePath,
202
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
203
+ }, null, 2), "utf8");
204
+ }
205
+ function clearChromePathCache(cacheFilePath) {
206
+ if ((0, node_fs.existsSync)(cacheFilePath)) (0, node_fs.rmSync)(cacheFilePath, { force: true });
207
+ }
208
+ function isUsableExecutablePath(executablePath) {
209
+ if (!executablePath) return false;
210
+ try {
211
+ if (!(0, node_fs.statSync)(executablePath).isFile()) return false;
212
+ (0, node_fs.accessSync)(executablePath, node_fs.constants.X_OK);
213
+ return true;
214
+ } catch {
215
+ return false;
216
+ }
217
+ }
218
+ function getExplicitUseChromePreference(rawConfigs) {
219
+ const explicitConfigSources = [
220
+ rawConfigs?.file,
221
+ rawConfigs?.env,
222
+ rawConfigs?.cli,
223
+ rawConfigs?.programmatic
224
+ ];
225
+ for (let index = explicitConfigSources.length - 1; index >= 0; index -= 1) {
226
+ const source = explicitConfigSources[index];
227
+ if (source?.useChrome !== void 0) return source.useChrome;
228
+ }
229
+ }
230
+ function shouldPreferLocalChrome(config, rawConfigs, defaultPreference = true) {
231
+ if (config.useLightpanda || config.useChromium || config.browser?.kind === "chromium") return false;
232
+ if (config.executablePath || process.env.PUPPETEER_EXECUTABLE_PATH !== void 0) return false;
233
+ const explicitUseChrome = getExplicitUseChromePreference(rawConfigs);
234
+ if (explicitUseChrome !== void 0) return explicitUseChrome;
235
+ return defaultPreference;
236
+ }
237
+ async function resolveExecutablePath(config, options) {
238
+ const configuredPath = config.executablePath ?? process.env.PUPPETEER_EXECUTABLE_PATH;
239
+ if (configuredPath !== void 0) return {
240
+ executablePath: configuredPath,
241
+ source: "config"
242
+ };
243
+ if (!(options.preferLocalChrome ?? config.useChrome)) return { source: "driver_default" };
244
+ const matchesKind = (path) => options.kind === "chromium" ? /chromium/i.test(path) : !/chromium/i.test(path);
245
+ const cachedPath = readChromePathCache(options.cacheFilePath)?.executablePath;
246
+ if (isUsableExecutablePath(cachedPath) && matchesKind(cachedPath)) return {
247
+ executablePath: cachedPath,
248
+ source: "cache"
249
+ };
250
+ if (cachedPath) clearChromePathCache(options.cacheFilePath);
251
+ const detectedPath = await findSystemBrowser(options.kind ?? "chrome");
252
+ if (detectedPath) {
253
+ writeChromePathCache(detectedPath, options.cacheFilePath);
254
+ return {
255
+ executablePath: detectedPath,
256
+ source: "chrome_installation"
257
+ };
258
+ }
259
+ clearChromePathCache(options.cacheFilePath);
260
+ return { source: "driver_default" };
261
+ }
262
+ function resolveLightpandaExecutablePath(config) {
263
+ const executablePath = config.lightpanda?.executablePath ?? config.executablePath ?? process.env.LIGHTPANDA_EXECUTABLE_PATH;
264
+ if (executablePath) return {
265
+ executablePath,
266
+ source: "lightpanda_config"
267
+ };
268
+ return { source: "lightpanda_managed" };
269
+ }
270
+ async function selectRuntimeDriver$1(config, options) {
271
+ if ([
272
+ config.useChrome,
273
+ config.useChromium,
274
+ config.useLightpanda
275
+ ].filter(Boolean).length > 1) throw new Error("Choose only one browser: useChrome, useChromium or useLightpanda.");
276
+ let kind = config.useLightpanda ? "lightpanda" : config.useChromium ? "chromium" : config.useChrome ? "chrome" : config.browser?.kind;
277
+ const explicitChoice = kind !== void 0 || getExplicitUseChromePreference(options.rawConfigs) !== void 0 || config.browser?.download === "auto";
278
+ const preferLocalChrome = shouldPreferLocalChrome(config, options.rawConfigs, options.defaultPreferLocalChrome ?? true);
279
+ let executableResolution = { source: "driver_default" };
280
+ if (kind !== "lightpanda") executableResolution = await resolveExecutablePath(config, {
281
+ preferLocalChrome: kind === "chromium" || preferLocalChrome,
282
+ cacheFilePath: kind === "chromium" ? `${options.cacheFilePath}.chromium` : options.cacheFilePath,
283
+ kind: kind === "chromium" ? "chromium" : "chrome"
284
+ });
285
+ else {
286
+ executableResolution = resolveLightpandaExecutablePath(config);
287
+ executableResolution.executablePath ??= config.executablePath;
288
+ }
289
+ if (!executableResolution.executablePath && (kind !== void 0 || options.promptForMissingBrowser) && !(kind === "lightpanda" && config.lightpanda?.experimentalWhatsApp !== false)) {
290
+ const remembered = await readBrowserChoice();
291
+ if (remembered && (kind === void 0 || remembered.kind === kind)) {
292
+ kind = remembered.kind;
293
+ executableResolution = {
294
+ source: "cache",
295
+ executablePath: remembered.executablePath
296
+ };
297
+ }
298
+ }
299
+ if (!kind && executableResolution.executablePath === void 0 && options.promptForMissingBrowser) {
300
+ const { ensureBrowser } = await import("@open-wa/driver-puppeteer");
301
+ try {
302
+ executableResolution = {
303
+ source: "managed_browser",
304
+ executablePath: await ensureBrowser({ browser: {
305
+ ...config.browser,
306
+ kind: "chrome",
307
+ download: "never"
308
+ } })
309
+ };
310
+ } catch (error) {
311
+ if (!(error instanceof Error) || !/missing from .* and browser downloads are disabled/.test(error.message)) throw error;
312
+ }
313
+ }
314
+ if (!kind && !executableResolution.executablePath && !explicitChoice && options.promptForMissingBrowser) {
315
+ if (config.browser?.download === "never") throw new Error("No browser is available and browser downloads are disabled. Configure executablePath or pre-provision a browser cache.");
316
+ if (options.nonInteractive || process.env.CI || process.env.NONINTERACTIVE) throw new Error("No browser is installed. Choose --use-chrome, --use-chromium or --use-lightpanda to download one without a prompt.");
317
+ kind = await chooseBrowser();
318
+ }
319
+ kind ??= "chrome";
320
+ const sink = getCliOutputSink();
321
+ const ctx = { logger: {
322
+ debug: (message) => sink.write({
323
+ level: "debug",
324
+ message
325
+ }),
326
+ info: (message) => sink.write({
327
+ level: "info",
328
+ message
329
+ }),
330
+ warn: (message) => sink.write({
331
+ level: "warn",
332
+ message
333
+ }),
334
+ error: (message) => sink.write({
335
+ level: "error",
336
+ message
337
+ })
338
+ } };
339
+ const browser = {
340
+ ...config.browser,
341
+ ...explicitChoice || options.promptForMissingBrowser ? { download: config.browser?.download ?? "auto" } : {},
342
+ ...kind !== "lightpanda" ? { kind } : {}
343
+ };
344
+ if (kind === "lightpanda") {
345
+ const { LightpandaDriver, ensureLightpanda } = await import("@open-wa/driver-lightpanda");
346
+ config.lightpanda = _open_wa_config.ConfigSchema.shape.lightpanda.parse({
347
+ ...config.lightpanda,
348
+ experimentalWhatsApp: config.lightpanda?.experimentalWhatsApp ?? true
349
+ });
350
+ config.browser = browser;
351
+ const executablePath = await ensureLightpanda({
352
+ executablePath: executableResolution.executablePath,
353
+ browser,
354
+ preferPinnedRelease: config.lightpanda?.experimentalWhatsApp ?? true
355
+ }, ctx);
356
+ config.useLightpanda = true;
357
+ config.useChrome = false;
358
+ config.useChromium = false;
359
+ sink.write({
360
+ level: "warn",
361
+ message: "Lightpanda has low compatibility and experimental WhatsApp support; video, screenshots and rendering are unavailable."
362
+ });
363
+ if (options.promptForMissingBrowser) await writeBrowserChoice(kind, executablePath).catch((error) => sink.write({
364
+ level: "warn",
365
+ message: `Could not remember the browser choice: ${error instanceof Error ? error.message : String(error)}`
366
+ }));
367
+ return {
368
+ driver: new LightpandaDriver(),
369
+ engineLabel: "Lightpanda",
370
+ executableResolution: {
371
+ source: "lightpanda_managed",
372
+ executablePath
373
+ },
374
+ preferLocalChrome: false
375
+ };
376
+ }
377
+ config.browser = browser;
378
+ const { PuppeteerDriver, ensureBrowser } = await import("@open-wa/driver-puppeteer");
379
+ const executablePath = await ensureBrowser({
380
+ executablePath: executableResolution.executablePath,
381
+ browser
382
+ }, ctx);
383
+ if (!executableResolution.executablePath) executableResolution = {
384
+ source: "managed_browser",
385
+ executablePath
386
+ };
387
+ if (options.promptForMissingBrowser) await writeBrowserChoice(kind, executablePath).catch((error) => sink.write({
388
+ level: "warn",
389
+ message: `Could not remember the browser choice: ${error instanceof Error ? error.message : String(error)}`
390
+ }));
391
+ return {
392
+ driver: new PuppeteerDriver(),
393
+ engineLabel: "Puppeteer",
394
+ executableResolution,
395
+ preferLocalChrome
396
+ };
397
+ }
398
+ /** Load the Node media runtime only when a call needs a source or sink. */
399
+ function makeDefaultCallMediaHost() {
400
+ return { async prepare(...args) {
401
+ const { makeNodeCallMediaHost } = await import("@open-wa/runtime-node");
402
+ return makeNodeCallMediaHost({ log: (message) => getCliOutputSink().write({
403
+ level: "info",
404
+ message
405
+ }) }).prepare(...args);
406
+ } };
407
+ }
408
+ function toCreateClientOptions(config, driverSelection, options = {}) {
409
+ return {
410
+ sessionId: config.sessionId,
411
+ eventMode: config.eventMode,
412
+ driver: driverSelection.driver,
413
+ deleteSessionDataOnLogout: config.deleteSessionDataOnLogout,
414
+ killClientOnLogout: config.killClientOnLogout,
415
+ sessionDataPath: config.sessionDataPath,
416
+ sessionData: config.sessionData,
417
+ skipSessionSave: config.skipSessionSave,
418
+ sessionDataBucketAuth: config.sessionDataBucketAuth,
419
+ sessionDataEncryptionKey: config.sessionDataEncryptionKey,
420
+ sessionDataEncryptionKeyFile: config.sessionDataEncryptionKeyFile,
421
+ s3Sync: config.s3Sync,
422
+ debug: options.debug ?? (config.logLevel === "debug" || config.logConsole),
423
+ headless: config.headless,
424
+ qrTimeoutMs: typeof config.qrTimeout === "number" ? config.qrTimeout * 1e3 : void 0,
425
+ authTimeoutMs: typeof config.authTimeout === "number" ? config.authTimeout * 1e3 : void 0,
426
+ executablePath: driverSelection.executableResolution.executablePath,
427
+ browserArgs: config.chromiumArgs,
428
+ browser: config.browser,
429
+ allowDangerousBrowserArgs: config.allowDangerousBrowserArgs,
430
+ userDataDir: config.userDataDir,
431
+ ephemeral: config.ephemeral,
432
+ linkCode: config.linkCode,
433
+ qrMax: config.qrMax,
434
+ oorTimeoutMs: config.oorTimeout * 1e3,
435
+ logConsole: config.logConsole,
436
+ logConsoleErrors: config.logConsoleErrors,
437
+ blockCrashLogs: config.blockCrashLogs,
438
+ blockAssets: config.blockAssets,
439
+ safeMode: config.safeMode,
440
+ lightpanda: config.useLightpanda ? {
441
+ experimentalWhatsApp: true,
442
+ ...config.lightpanda
443
+ } : void 0,
444
+ licenseKey: config.licenseKey,
445
+ calling: config.calling,
446
+ callMediaHost: makeDefaultCallMediaHost(),
447
+ licenseConfig: config.licenseConfig,
448
+ patchConfig: {
449
+ ghPatch: config.ghPatch,
450
+ cachedPatch: config.cachedPatch
451
+ }
452
+ };
453
+ }
454
+ /** Start WhatsApp, display QR login when needed, and return a ready messaging client. */
455
+ async function create(options = {}) {
456
+ const { createClient: createCoreClient } = await import("@open-wa/core");
457
+ const { Client } = await import("@open-wa/client");
458
+ const { driver, sessionData, sessionDataBucketAuth, s3Sync, calling, sessionDataEncryptionKey, sessionDataEncryptionKeyFile, ...configOverrides } = options;
459
+ const { config, rawConfigs } = await (0, _open_wa_config.resolveConfig)({
460
+ programmaticOverrides: {
461
+ qrTimeout: 0,
462
+ ...configOverrides
463
+ },
464
+ skipConfigFile: true,
465
+ skipEnv: true,
466
+ includeRawConfigs: true
467
+ });
468
+ const coreOptions = toCreateClientOptions(config, driver ? {
469
+ driver,
470
+ engineLabel: driver.name === "lightpanda" ? "Lightpanda" : "Puppeteer",
471
+ executableResolution: {
472
+ source: "config",
473
+ executablePath: config.executablePath
474
+ },
475
+ preferLocalChrome: false
476
+ } : await selectRuntimeDriver$1(config, {
477
+ rawConfigs,
478
+ cacheFilePath: (0, node_path.resolve)(process.cwd(), ".open-wa", "chrome-executable-path.json"),
479
+ defaultPreferLocalChrome: false
480
+ }));
481
+ if (calling) coreOptions.calling = calling;
482
+ if (driver) coreOptions.driver = driver;
483
+ coreOptions.sessionData = sessionData;
484
+ coreOptions.sessionDataBucketAuth = sessionDataBucketAuth;
485
+ coreOptions.sessionDataEncryptionKey = sessionDataEncryptionKey;
486
+ coreOptions.sessionDataEncryptionKeyFile = sessionDataEncryptionKeyFile;
487
+ coreOptions.s3Sync = s3Sync;
488
+ const core = await createCoreClient(coreOptions);
489
+ const sink = getCliOutputSink();
490
+ const onQr = (event) => {
491
+ if (config.qrLogSkip || !event.details?.qr) return;
492
+ sink.write({
493
+ level: "info",
494
+ message: "Open WhatsApp on your sending phone: Linked Devices → Link a Device, then scan this QR code."
495
+ });
496
+ sink.qr({
497
+ qr: event.details.qr,
498
+ sessionId: config.sessionId
499
+ });
500
+ };
501
+ const onLinkCode = (event) => {
502
+ if (event.details?.linkCode && !config.qrLogSkip) sink.write({
503
+ level: "info",
504
+ message: `Enter this link code on your sending phone: ${event.details.linkCode}`
505
+ });
506
+ };
507
+ core.events.on("launch.auth.qr.generated", onQr);
508
+ core.events.on("launch.auth.linkCode.generated", onLinkCode);
509
+ let client;
510
+ try {
511
+ client = new Client({
512
+ client: core,
513
+ transport: core.getTransport()
514
+ });
515
+ await client.start();
516
+ if (core.getState() !== "READY" || !core.getReadiness().exposureSafe) throw new Error("WhatsApp startup finished without a messaging-ready session.");
517
+ return client;
518
+ } catch (error) {
519
+ try {
520
+ if (client) await client.stop("startup-failure");
521
+ else await core.stop("startup-failure");
522
+ } catch {}
523
+ throw error;
524
+ } finally {
525
+ core.events.off("launch.auth.qr.generated", onQr);
526
+ core.events.off("launch.auth.linkCode.generated", onLinkCode);
527
+ }
528
+ }
529
+ //#endregion
80
530
  //#region src/cli-runtime.ts
81
531
  function bridgeRuntimeEvents(openwaClient, publishRuntimeEvent) {
82
532
  if (typeof openwaClient.events.onAny === "function" && typeof openwaClient.events.offAny === "function") {
@@ -84,7 +534,9 @@ function bridgeRuntimeEvents(openwaClient, publishRuntimeEvent) {
84
534
  return () => openwaClient.events.offAny(publishRuntimeEvent);
85
535
  }
86
536
  const unsubscribers = [];
87
- new Set(_open_wa_schema.eventRegistry.getAll().map((def) => def.meta.eventName)).forEach((eventName) => {
537
+ const bridgedEvents = new Set(_open_wa_schema.eventRegistry.getAll().map((def) => def.meta.eventName));
538
+ bridgedEvents.add("session.refresh.progress");
539
+ bridgedEvents.forEach((eventName) => {
88
540
  const handler = (payload) => publishRuntimeEvent(eventName, payload);
89
541
  openwaClient.events.on(eventName, handler);
90
542
  unsubscribers.push(() => openwaClient.events.off(eventName, handler));
@@ -271,51 +723,6 @@ const CHROME_CACHE_FILE = (0, node_path.resolve)(process.cwd(), ".open-wa", "chr
271
723
  function getChromeCacheFilePath() {
272
724
  return CHROME_CACHE_FILE;
273
725
  }
274
- function readChromePathCache(cacheFilePath = getChromeCacheFilePath()) {
275
- if (!(0, node_fs.existsSync)(cacheFilePath)) return;
276
- try {
277
- const raw = JSON.parse((0, node_fs.readFileSync)(cacheFilePath, "utf8"));
278
- if (!raw || typeof raw !== "object") return;
279
- return {
280
- executablePath: typeof raw.executablePath === "string" ? raw.executablePath : void 0,
281
- updatedAt: typeof raw.updatedAt === "string" ? raw.updatedAt : void 0
282
- };
283
- } catch {
284
- return;
285
- }
286
- }
287
- function writeChromePathCache(executablePath, cacheFilePath = getChromeCacheFilePath()) {
288
- (0, node_fs.mkdirSync)((0, node_path.dirname)(cacheFilePath), { recursive: true });
289
- (0, node_fs.writeFileSync)(cacheFilePath, JSON.stringify({
290
- executablePath,
291
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
292
- }, null, 2), "utf8");
293
- }
294
- function clearChromePathCache(cacheFilePath = getChromeCacheFilePath()) {
295
- if ((0, node_fs.existsSync)(cacheFilePath)) (0, node_fs.rmSync)(cacheFilePath, { force: true });
296
- }
297
- function isUsableExecutablePath(executablePath) {
298
- return !!executablePath && (0, node_fs.existsSync)(executablePath);
299
- }
300
- function getExplicitUseChromePreference(rawConfigs) {
301
- const explicitConfigSources = [
302
- rawConfigs?.file,
303
- rawConfigs?.env,
304
- rawConfigs?.cli,
305
- rawConfigs?.programmatic
306
- ];
307
- for (let index = explicitConfigSources.length - 1; index >= 0; index -= 1) {
308
- const source = explicitConfigSources[index];
309
- if (source?.useChrome !== void 0) return source.useChrome;
310
- }
311
- }
312
- function shouldPreferLocalChrome(config, rawConfigs) {
313
- if (config.useLightpanda) return false;
314
- if (config.executablePath) return false;
315
- const explicitUseChrome = getExplicitUseChromePreference(rawConfigs);
316
- if (explicitUseChrome !== void 0) return explicitUseChrome;
317
- return true;
318
- }
319
726
  function getVal(argv, flag) {
320
727
  const index = argv.findIndex((arg) => arg === flag);
321
728
  return index !== -1 ? argv[index + 1] : void 0;
@@ -358,8 +765,17 @@ function parseCliArgs(argv = process.argv.slice(2)) {
358
765
  if (argv.includes("--no-ezqr")) cliOverrides.ezqr = false;
359
766
  if (argv.includes("--headful")) cliOverrides.headless = false;
360
767
  if (argv.includes("--headless")) cliOverrides.headless = true;
361
- if (argv.includes("--use-chrome")) cliOverrides.useChrome = true;
362
- if (argv.includes("--use-lightpanda")) cliOverrides.useLightpanda = true;
768
+ const browserFlags = [
769
+ "--use-chrome",
770
+ "--use-chromium",
771
+ "--use-lightpanda"
772
+ ].filter((flag) => argv.includes(flag) || flag === "--use-lightpanda" && argv.includes("--lightpanda"));
773
+ if (browserFlags.length > 1) throw new Error("Choose only one of --use-chrome, --use-chromium or --use-lightpanda.");
774
+ if (browserFlags.length) {
775
+ cliOverrides.useChrome = browserFlags[0] === "--use-chrome";
776
+ cliOverrides.useChromium = browserFlags[0] === "--use-chromium";
777
+ cliOverrides.useLightpanda = browserFlags[0] === "--use-lightpanda";
778
+ }
363
779
  if (argv.includes("--log-console")) cliOverrides.logConsole = true;
364
780
  if (argv.includes("--aggressive-garbage-collection")) cliOverrides.aggressiveGarbageCollection = true;
365
781
  if (argv.includes("--no-dashboard")) cliOverrides.dashboard = false;
@@ -412,57 +828,13 @@ function parseCliArgs(argv = process.argv.slice(2)) {
412
828
  unsupportedWarnings
413
829
  };
414
830
  }
415
- async function resolveExecutablePath(config, options = {}) {
416
- if (config.executablePath) return {
417
- executablePath: config.executablePath,
418
- source: "config"
419
- };
420
- if (!(options.preferLocalChrome ?? config.useChrome)) return { source: "driver_default" };
421
- const cacheFilePath = options.cacheFilePath ?? getChromeCacheFilePath();
422
- const cachedPath = readChromePathCache(cacheFilePath)?.executablePath;
423
- if (isUsableExecutablePath(cachedPath)) return {
424
- executablePath: cachedPath,
425
- source: "cache"
426
- };
427
- if (cachedPath) clearChromePathCache(cacheFilePath);
428
- const { Launcher } = await import("chrome-launcher");
429
- const detectedPath = Launcher.getInstallations().find((installationPath) => isUsableExecutablePath(installationPath));
430
- if (detectedPath) {
431
- writeChromePathCache(detectedPath, cacheFilePath);
432
- return {
433
- executablePath: detectedPath,
434
- source: "chrome_installation"
435
- };
436
- }
437
- clearChromePathCache(cacheFilePath);
438
- return {
439
- source: "driver_default",
440
- warning: "Chrome resolution warning: no valid local Chrome installation was found. Falling back to Puppeteer/default driver browser resolution."
441
- };
442
- }
443
- function resolveLightpandaExecutablePath(config) {
444
- const executablePath = config.lightpanda?.executablePath;
445
- if (executablePath) return {
446
- executablePath,
447
- source: "lightpanda_config"
448
- };
449
- return { source: "lightpanda_sdk_default" };
450
- }
451
- async function selectRuntimeDriver(config, rawConfigs) {
452
- if (config.useLightpanda) return {
453
- driver: new _open_wa_driver_lightpanda.LightpandaDriver(),
454
- engineLabel: "Lightpanda",
455
- executableResolution: resolveLightpandaExecutablePath(config),
456
- preferLocalChrome: false
457
- };
458
- const preferLocalChrome = shouldPreferLocalChrome(config, rawConfigs);
459
- const executableResolution = await resolveExecutablePath(config, { preferLocalChrome });
460
- return {
461
- driver: new _open_wa_driver_puppeteer.PuppeteerDriver(),
462
- engineLabel: "Puppeteer",
463
- executableResolution,
464
- preferLocalChrome
465
- };
831
+ async function selectRuntimeDriver(config, rawConfigs, nonInteractive = false) {
832
+ return selectRuntimeDriver$1(config, {
833
+ rawConfigs,
834
+ cacheFilePath: getChromeCacheFilePath(),
835
+ promptForMissingBrowser: true,
836
+ nonInteractive
837
+ });
466
838
  }
467
839
  function printStartupSummary(config, resolution, preferLocalChrome, engineLabel) {
468
840
  const host = config.host.includes("http") ? config.host : `http://${config.host}`;
@@ -507,9 +879,9 @@ function printStartupSummary(config, resolution, preferLocalChrome, engineLabel)
507
879
  level: "info",
508
880
  message: `Browser executable: explicit Lightpanda override (${resolution.executablePath})`
509
881
  });
510
- else if (resolution.source === "lightpanda_sdk_default") sink.write({
882
+ else if (resolution.source === "lightpanda_managed") sink.write({
511
883
  level: "info",
512
- message: "Browser executable: Lightpanda SDK managed executable (shared cache/default resolution)"
884
+ message: `Browser executable: Lightpanda with experimental WhatsApp setup (${resolution.executablePath})`
513
885
  });
514
886
  else if (resolution.source === "config" && config.executablePath) sink.write({
515
887
  level: "info",
@@ -517,15 +889,15 @@ function printStartupSummary(config, resolution, preferLocalChrome, engineLabel)
517
889
  });
518
890
  else if (resolution.source === "cache") sink.write({
519
891
  level: "info",
520
- message: `Browser executable: local Chrome from cache (${resolution.executablePath})`
892
+ message: `Browser executable: cached browser (${resolution.executablePath})`
521
893
  });
522
894
  else if (resolution.source === "chrome_installation") sink.write({
523
895
  level: "info",
524
- message: `Browser executable: local Chrome detected (${resolution.executablePath})`
896
+ message: `Browser executable: installed ${config.browser?.kind === "chromium" ? "Chromium" : "Chrome"} (${resolution.executablePath})`
525
897
  });
526
- else if (preferLocalChrome) sink.write({
898
+ else if (resolution.source === "managed_browser") sink.write({
527
899
  level: "info",
528
- message: "Browser executable: driver default fallback (local Chrome unavailable)"
900
+ message: `Browser executable: managed ${config.browser?.kind === "chromium" ? "Chromium" : "Chrome"} (${resolution.executablePath})`
529
901
  });
530
902
  if (config.webhook) sink.write({
531
903
  level: "warn",
@@ -564,7 +936,7 @@ async function start(parsedArgs = parseCliArgs()) {
564
936
  level: "warn",
565
937
  message: `Compatibility warning: ${warning}`
566
938
  }));
567
- const { driver, engineLabel, executableResolution, preferLocalChrome } = await selectRuntimeDriver(config, rawConfigs);
939
+ const { driver, engineLabel, executableResolution, preferLocalChrome } = await selectRuntimeDriver(config, rawConfigs, parsedArgs.forwardedArgs.includes("--non-interactive"));
568
940
  if (executableResolution.warning) sink.write({
569
941
  level: "warn",
570
942
  message: executableResolution.warning
@@ -592,6 +964,7 @@ async function start(parsedArgs = parseCliArgs()) {
592
964
  const executionSandbox = sandboxPolicy ? (0, _open_wa_runtime_node.makeNodeExecutionSandbox)({ workspacePath: process.cwd() }) : void 0;
593
965
  const openwaClient = await (0, _open_wa_core.createClient)({
594
966
  sessionId: config.sessionId,
967
+ eventMode: config.eventMode,
595
968
  driver,
596
969
  deleteSessionDataOnLogout: config.deleteSessionDataOnLogout,
597
970
  killClientOnLogout: config.killClientOnLogout,
@@ -618,7 +991,14 @@ async function start(parsedArgs = parseCliArgs()) {
618
991
  blockCrashLogs: config.blockCrashLogs,
619
992
  blockAssets: config.blockAssets,
620
993
  safeMode: config.safeMode,
994
+ lightpanda: config.useLightpanda ? {
995
+ experimentalWhatsApp: true,
996
+ ...config.lightpanda
997
+ } : void 0,
621
998
  licenseKey: config.licenseKey,
999
+ licenseConfig: config.licenseConfig,
1000
+ calling: config.calling,
1001
+ callMediaHost: makeDefaultCallMediaHost(),
622
1002
  sandboxPolicy,
623
1003
  executionSandbox,
624
1004
  memoryObservation: (observability, getBrowserProcessId) => (0, _open_wa_runtime_node.observeBrowserProcessMemory)(observability, getBrowserProcessId, { attributes: { session: config.sessionId } })
@@ -628,6 +1008,8 @@ async function start(parsedArgs = parseCliArgs()) {
628
1008
  state: openwaClient.getState()
629
1009
  }));
630
1010
  const detachLaunchNarration = attachLaunchNarration(openwaClient, sink, config.sessionId);
1011
+ openwaClient.events.on("launch.auth.qr.expired", () => server.setQR(null));
1012
+ openwaClient.events.on("launch.auth.qr.scanned", () => server.setQR(null));
631
1013
  openwaClient.events.on("launch.auth.qr.generated", (event) => {
632
1014
  const qr = event.details?.qr;
633
1015
  if (!qr) return;
@@ -709,7 +1091,7 @@ async function start(parsedArgs = parseCliArgs()) {
709
1091
  });
710
1092
  sink.write({
711
1093
  level: "warn",
712
- message: "Session kept alive for debugging. Browser page is still open."
1094
+ message: "The API server is still running for inspection; the client failed to start."
713
1095
  });
714
1096
  sink.write({
715
1097
  level: "warn",
@@ -745,7 +1127,7 @@ async function start(parsedArgs = parseCliArgs()) {
745
1127
  }
746
1128
  async function main(argv = process.argv.slice(2)) {
747
1129
  if (argv[0] === "init") {
748
- const { initProject } = await Promise.resolve().then(() => require("./init-k_0bazOu.cjs"));
1130
+ const { initProject } = await Promise.resolve().then(() => require("./init-d6fE8kvv.cjs"));
749
1131
  await initProject(argv.slice(1));
750
1132
  return;
751
1133
  }
@@ -815,6 +1197,12 @@ Object.defineProperty(exports, "WAServer", {
815
1197
  return WAServer;
816
1198
  }
817
1199
  });
1200
+ Object.defineProperty(exports, "create", {
1201
+ enumerable: true,
1202
+ get: function() {
1203
+ return create;
1204
+ }
1205
+ });
818
1206
  Object.defineProperty(exports, "createConsoleOutputSink", {
819
1207
  enumerable: true,
820
1208
  get: function() {
@@ -858,4 +1246,4 @@ Object.defineProperty(exports, "start", {
858
1246
  }
859
1247
  });
860
1248
 
861
- //# sourceMappingURL=cli-runtime-BKwXzp9V.cjs.map
1249
+ //# sourceMappingURL=cli-runtime-DDEl6a1P.cjs.map