@xbibzlibrary/whatsbibz 1.3.0
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/CHANGELOG.md +93 -0
- package/LICENSE +22 -0
- package/LICENSE.upstream +21 -0
- package/README.md +280 -0
- package/VERIFIKASI-IDENTITAS-2026-09-03.md +138 -0
- package/WAProto/GenerateStatics.sh +3 -0
- package/WAProto/WAProto.proto +6035 -0
- package/WAProto/fix-imports.js +81 -0
- package/WAProto/index.d.ts +15314 -0
- package/WAProto/index.js +132326 -0
- package/assets/logo/README.md +21 -0
- package/assets/logo/png/banner-1200x360.png +0 -0
- package/assets/logo/png/icon-1024.png +0 -0
- package/assets/logo/png/icon-128.png +0 -0
- package/assets/logo/png/icon-16.png +0 -0
- package/assets/logo/png/icon-24.png +0 -0
- package/assets/logo/png/icon-256.png +0 -0
- package/assets/logo/png/icon-32.png +0 -0
- package/assets/logo/png/icon-48.png +0 -0
- package/assets/logo/png/icon-512.png +0 -0
- package/assets/logo/png/icon-64.png +0 -0
- package/assets/logo/png/mark-512.png +0 -0
- package/assets/logo/preview.png +0 -0
- package/assets/logo/whatsbibz.ico +0 -0
- package/lib/BibzWhats/client.d.ts +95 -0
- package/lib/BibzWhats/client.js +501 -0
- package/lib/BibzWhats/device-identity.d.ts +60 -0
- package/lib/BibzWhats/device-identity.js +234 -0
- package/lib/BibzWhats/extract.d.ts +29 -0
- package/lib/BibzWhats/extract.js +115 -0
- package/lib/BibzWhats/index.d.ts +7 -0
- package/lib/BibzWhats/index.js +8 -0
- package/lib/BibzWhats/jid.d.ts +22 -0
- package/lib/BibzWhats/jid.js +98 -0
- package/lib/BibzWhats/pairing.d.ts +35 -0
- package/lib/BibzWhats/pairing.js +186 -0
- package/lib/BibzWhats/send.d.ts +9 -0
- package/lib/BibzWhats/send.js +107 -0
- package/lib/BibzWhats/version.d.ts +6 -0
- package/lib/BibzWhats/version.js +7 -0
- package/lib/Defaults/index.d.ts +73 -0
- package/lib/Defaults/index.js +141 -0
- package/lib/Modded/message_builder.d.ts +15 -0
- package/lib/Modded/message_builder.js +2330 -0
- package/lib/Signal/Group/ciphertext-message.d.ts +9 -0
- package/lib/Signal/Group/ciphertext-message.js +11 -0
- package/lib/Signal/Group/group-session-builder.d.ts +14 -0
- package/lib/Signal/Group/group-session-builder.js +29 -0
- package/lib/Signal/Group/group_cipher.d.ts +16 -0
- package/lib/Signal/Group/group_cipher.js +81 -0
- package/lib/Signal/Group/index.d.ts +11 -0
- package/lib/Signal/Group/index.js +11 -0
- package/lib/Signal/Group/keyhelper.d.ts +10 -0
- package/lib/Signal/Group/keyhelper.js +17 -0
- package/lib/Signal/Group/sender-chain-key.d.ts +13 -0
- package/lib/Signal/Group/sender-chain-key.js +25 -0
- package/lib/Signal/Group/sender-key-distribution-message.d.ts +16 -0
- package/lib/Signal/Group/sender-key-distribution-message.js +62 -0
- package/lib/Signal/Group/sender-key-message.d.ts +18 -0
- package/lib/Signal/Group/sender-key-message.js +65 -0
- package/lib/Signal/Group/sender-key-name.d.ts +17 -0
- package/lib/Signal/Group/sender-key-name.js +47 -0
- package/lib/Signal/Group/sender-key-record.d.ts +30 -0
- package/lib/Signal/Group/sender-key-record.js +40 -0
- package/lib/Signal/Group/sender-key-state.d.ts +38 -0
- package/lib/Signal/Group/sender-key-state.js +83 -0
- package/lib/Signal/Group/sender-message-key.d.ts +11 -0
- package/lib/Signal/Group/sender-message-key.js +25 -0
- package/lib/Signal/libsignal.d.ts +4 -0
- package/lib/Signal/libsignal.js +406 -0
- package/lib/Signal/lid-mapping.d.ts +18 -0
- package/lib/Signal/lid-mapping.js +276 -0
- package/lib/Socket/Client/index.d.ts +2 -0
- package/lib/Socket/Client/index.js +2 -0
- package/lib/Socket/Client/types.d.ts +15 -0
- package/lib/Socket/Client/types.js +10 -0
- package/lib/Socket/Client/websocket.d.ts +12 -0
- package/lib/Socket/Client/websocket.js +53 -0
- package/lib/Socket/business.d.ts +537 -0
- package/lib/Socket/business.js +421 -0
- package/lib/Socket/chats.d.ts +110 -0
- package/lib/Socket/chats.js +1006 -0
- package/lib/Socket/communities.d.ts +646 -0
- package/lib/Socket/communities.js +430 -0
- package/lib/Socket/engine.d.ts +129 -0
- package/lib/Socket/engine.js +767 -0
- package/lib/Socket/groups.d.ts +149 -0
- package/lib/Socket/groups.js +326 -0
- package/lib/Socket/index.d.ts +637 -0
- package/lib/Socket/index.js +14 -0
- package/lib/Socket/messages-recv.d.ts +239 -0
- package/lib/Socket/messages-recv.js +1307 -0
- package/lib/Socket/messages-send.d.ts +580 -0
- package/lib/Socket/messages-send.js +1639 -0
- package/lib/Socket/mex.d.ts +2 -0
- package/lib/Socket/mex.js +47 -0
- package/lib/Socket/newsletter.d.ts +176 -0
- package/lib/Socket/newsletter.js +456 -0
- package/lib/Socket/socket.d.ts +56 -0
- package/lib/Socket/socket.js +998 -0
- package/lib/Types/Auth.d.ts +115 -0
- package/lib/Types/Auth.js +1 -0
- package/lib/Types/Bussines.d.ts +36 -0
- package/lib/Types/Bussines.js +1 -0
- package/lib/Types/Call.d.ts +14 -0
- package/lib/Types/Call.js +1 -0
- package/lib/Types/Chat.d.ts +124 -0
- package/lib/Types/Chat.js +7 -0
- package/lib/Types/Contact.d.ts +23 -0
- package/lib/Types/Contact.js +1 -0
- package/lib/Types/Events.d.ts +241 -0
- package/lib/Types/Events.js +1 -0
- package/lib/Types/GroupMetadata.d.ts +67 -0
- package/lib/Types/GroupMetadata.js +1 -0
- package/lib/Types/Label.d.ts +46 -0
- package/lib/Types/Label.js +24 -0
- package/lib/Types/LabelAssociation.d.ts +29 -0
- package/lib/Types/LabelAssociation.js +6 -0
- package/lib/Types/Message.d.ts +427 -0
- package/lib/Types/Message.js +10 -0
- package/lib/Types/Newsletter.d.ts +134 -0
- package/lib/Types/Newsletter.js +30 -0
- package/lib/Types/Product.d.ts +78 -0
- package/lib/Types/Product.js +1 -0
- package/lib/Types/Signal.d.ts +77 -0
- package/lib/Types/Signal.js +1 -0
- package/lib/Types/Socket.d.ts +173 -0
- package/lib/Types/Socket.js +2 -0
- package/lib/Types/State.d.ts +38 -0
- package/lib/Types/State.js +12 -0
- package/lib/Types/USync.d.ts +25 -0
- package/lib/Types/USync.js +1 -0
- package/lib/Types/index.d.ts +75 -0
- package/lib/Types/index.js +32 -0
- package/lib/Utils/auth-utils.d.ts +18 -0
- package/lib/Utils/auth-utils.js +289 -0
- package/lib/Utils/browser-utils.d.ts +3 -0
- package/lib/Utils/browser-utils.js +47 -0
- package/lib/Utils/business.d.ts +22 -0
- package/lib/Utils/business.js +230 -0
- package/lib/Utils/chat-utils.d.ts +69 -0
- package/lib/Utils/chat-utils.js +811 -0
- package/lib/Utils/companion-reg-client-utils.d.ts +20 -0
- package/lib/Utils/companion-reg-client-utils.js +66 -0
- package/lib/Utils/crypto.d.ts +41 -0
- package/lib/Utils/crypto.js +117 -0
- package/lib/Utils/decode-wa-message.d.ts +47 -0
- package/lib/Utils/decode-wa-message.js +282 -0
- package/lib/Utils/event-buffer.d.ts +35 -0
- package/lib/Utils/event-buffer.js +590 -0
- package/lib/Utils/generics.d.ts +90 -0
- package/lib/Utils/generics.js +384 -0
- package/lib/Utils/history.d.ts +21 -0
- package/lib/Utils/history.js +130 -0
- package/lib/Utils/identity-change-handler.d.ts +36 -0
- package/lib/Utils/identity-change-handler.js +48 -0
- package/lib/Utils/index.d.ts +25 -0
- package/lib/Utils/index.js +24 -0
- package/lib/Utils/link-preview.d.ts +20 -0
- package/lib/Utils/link-preview.js +84 -0
- package/lib/Utils/logger.d.ts +12 -0
- package/lib/Utils/logger.js +2 -0
- package/lib/Utils/lt-hash.d.ts +10 -0
- package/lib/Utils/lt-hash.js +7 -0
- package/lib/Utils/make-mutex.d.ts +8 -0
- package/lib/Utils/make-mutex.js +32 -0
- package/lib/Utils/message-retry-manager.d.ts +109 -0
- package/lib/Utils/message-retry-manager.js +224 -0
- package/lib/Utils/messages-media.d.ts +129 -0
- package/lib/Utils/messages-media.js +784 -0
- package/lib/Utils/messages.d.ts +90 -0
- package/lib/Utils/messages.js +1240 -0
- package/lib/Utils/noise-handler.d.ts +19 -0
- package/lib/Utils/noise-handler.js +200 -0
- package/lib/Utils/offline-node-processor.d.ts +16 -0
- package/lib/Utils/offline-node-processor.js +39 -0
- package/lib/Utils/platform-identity.d.ts +14 -0
- package/lib/Utils/platform-identity.js +148 -0
- package/lib/Utils/pre-key-manager.d.ts +27 -0
- package/lib/Utils/pre-key-manager.js +105 -0
- package/lib/Utils/process-message.d.ts +59 -0
- package/lib/Utils/process-message.js +544 -0
- package/lib/Utils/reporting-utils.d.ts +10 -0
- package/lib/Utils/reporting-utils.js +257 -0
- package/lib/Utils/rich-messages.d.ts +276 -0
- package/lib/Utils/rich-messages.js +1143 -0
- package/lib/Utils/signal.d.ts +33 -0
- package/lib/Utils/signal.js +158 -0
- package/lib/Utils/stanza-ack.d.ts +10 -0
- package/lib/Utils/stanza-ack.js +37 -0
- package/lib/Utils/sticker-pack.d.ts +10 -0
- package/lib/Utils/sticker-pack.js +111 -0
- package/lib/Utils/sync-action-utils.d.ts +18 -0
- package/lib/Utils/sync-action-utils.js +47 -0
- package/lib/Utils/tc-token-utils.d.ts +8 -0
- package/lib/Utils/tc-token-utils.js +45 -0
- package/lib/Utils/use-multi-file-auth-state.d.ts +12 -0
- package/lib/Utils/use-multi-file-auth-state.js +120 -0
- package/lib/Utils/validate-connection.d.ts +10 -0
- package/lib/Utils/validate-connection.js +222 -0
- package/lib/VoIP/audio-feeder.d.ts +15 -0
- package/lib/VoIP/audio-feeder.js +136 -0
- package/lib/VoIP/index.d.ts +12 -0
- package/lib/VoIP/index.js +282 -0
- package/lib/VoIP/relay-transport.d.ts +43 -0
- package/lib/VoIP/relay-transport.js +559 -0
- package/lib/VoIP/signaling.js +603 -0
- package/lib/VoIP/types.d.ts +69 -0
- package/lib/VoIP/types.js +17 -0
- package/lib/VoIP/wasm-engine.d.ts +103 -0
- package/lib/VoIP/wasm-engine.js +1214 -0
- package/lib/VoIP/worker-bootstrap.js +1042 -0
- package/lib/WABinary/constants.d.ts +27 -0
- package/lib/WABinary/constants.js +1300 -0
- package/lib/WABinary/decode.d.ts +6 -0
- package/lib/WABinary/decode.js +261 -0
- package/lib/WABinary/encode.d.ts +2 -0
- package/lib/WABinary/encode.js +219 -0
- package/lib/WABinary/generic-utils.d.ts +16 -0
- package/lib/WABinary/generic-utils.js +190 -0
- package/lib/WABinary/index.d.ts +5 -0
- package/lib/WABinary/index.js +5 -0
- package/lib/WABinary/jid-utils.d.ts +47 -0
- package/lib/WABinary/jid-utils.js +95 -0
- package/lib/WABinary/types.d.ts +18 -0
- package/lib/WABinary/types.js +1 -0
- package/lib/WAM/BinaryInfo.d.ts +8 -0
- package/lib/WAM/BinaryInfo.js +9 -0
- package/lib/WAM/constants.d.ts +39 -0
- package/lib/WAM/constants.js +22852 -0
- package/lib/WAM/encode.d.ts +2 -0
- package/lib/WAM/encode.js +149 -0
- package/lib/WAM/index.d.ts +3 -0
- package/lib/WAM/index.js +3 -0
- package/lib/WAUSync/Protocols/USyncContactProtocol.d.ts +9 -0
- package/lib/WAUSync/Protocols/USyncContactProtocol.js +28 -0
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.d.ts +22 -0
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +53 -0
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.d.ts +12 -0
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +26 -0
- package/lib/WAUSync/Protocols/USyncStatusProtocol.d.ts +12 -0
- package/lib/WAUSync/Protocols/USyncStatusProtocol.js +37 -0
- package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.d.ts +25 -0
- package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +50 -0
- package/lib/WAUSync/Protocols/UsyncLIDProtocol.d.ts +9 -0
- package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +28 -0
- package/lib/WAUSync/Protocols/index.d.ts +4 -0
- package/lib/WAUSync/Protocols/index.js +4 -0
- package/lib/WAUSync/USyncQuery.d.ts +28 -0
- package/lib/WAUSync/USyncQuery.js +93 -0
- package/lib/WAUSync/USyncUser.d.ts +12 -0
- package/lib/WAUSync/USyncUser.js +22 -0
- package/lib/WAUSync/index.d.ts +3 -0
- package/lib/WAUSync/index.js +3 -0
- package/lib/assets/wasm/loader.js +5 -0
- package/lib/assets/wasm/whatsapp.wasm +0 -0
- package/lib/assets/wasm/worker-modules.js +273 -0
- package/lib/index.d.ts +21 -0
- package/lib/index.js +22 -0
- package/package.json +111 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Logo WhatsBibz
|
|
2
|
+
|
|
3
|
+
Gelembung pesan hijau (`#0a6958`) dengan buku terbuka di dalamnya, wordmark **WhatsBib'S** dan
|
|
4
|
+
tagline *WHATSAPP LIBRARY*. Sumber utamanya adalah `preview-2048.png` (raster 2048×2048, dari
|
|
5
|
+
pemilik proyek); semua berkas lain diturunkan dari situ.
|
|
6
|
+
|
|
7
|
+
| Berkas | Pakai untuk |
|
|
8
|
+
|---|---|
|
|
9
|
+
| `preview.png` | lockup lengkap 1024×1024 — README, halaman npm, dokumen |
|
|
10
|
+
| `preview-2048.png` | master resolusi penuh (tidak ikut dipublish ke npm) |
|
|
11
|
+
| `png/logo-1600.png`, `png/logo-800.png` | lockup dipangkas rapi (tanpa margin besar) |
|
|
12
|
+
| `png/mark-1024.png`, `png/mark-512.png` | gelembung saja, **latar transparan** — avatar, watermark |
|
|
13
|
+
| `png/icon-{16…1024}.png` | ikon persegi sudut membulat (tile putih + gelembung) — avatar npm/GitHub, app icon |
|
|
14
|
+
| `png/banner-1200x360.png` | banner README/sosial |
|
|
15
|
+
| `whatsbibz.ico` | favicon multi-ukuran 16–256 |
|
|
16
|
+
|
|
17
|
+
Warna: hijau utama `#0a6958`, teks `#2b2e33`, latar putih. Ruang kosong minimal di sekeliling
|
|
18
|
+
lockup = tinggi huruf **B**. Jangan dimiringkan, diberi outline, atau diganti fontnya.
|
|
19
|
+
|
|
20
|
+
Regenerasi turunan (mark transparan, ikon, ICO, banner) dari master: skrip yang dipakai ada di
|
|
21
|
+
riwayat proyek (`scripts/` tidak menyertakannya karena hanya dijalankan sekali saat rebrand).
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
import type { WASocket } from '../index.js';
|
|
3
|
+
import type { SocketConfig, WABrowserDescription, ConnectionState } from '../Types/index.js';
|
|
4
|
+
import type { PairingCodeMeta } from './pairing.js';
|
|
5
|
+
import type { IdentityDescription } from './device-identity.js';
|
|
6
|
+
|
|
7
|
+
export interface BibzWhatsLogger {
|
|
8
|
+
info?(...args: any[]): void;
|
|
9
|
+
warn?(...args: any[]): void;
|
|
10
|
+
error?(...args: any[]): void;
|
|
11
|
+
debug?(...args: any[]): void;
|
|
12
|
+
ok?(...args: any[]): void;
|
|
13
|
+
log?(...args: any[]): void;
|
|
14
|
+
}
|
|
15
|
+
export interface BibzWhatsOptions {
|
|
16
|
+
/** nomor bot (digit, dengan kode negara) — wajib untuk pairing code */
|
|
17
|
+
phone?: string;
|
|
18
|
+
/** kode custom 8 karakter A-Z0-9; kosong = kode acak bawaan */
|
|
19
|
+
pairingCode?: string;
|
|
20
|
+
/** folder kredensial (default 'bibzwhats-session') */
|
|
21
|
+
authDir?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Identitas perangkat tertaut. Tidak diisi / 'auto' = profil PALING STABIL
|
|
24
|
+
* (Mac OS/Chrome) + rotasi otomatis bila server menolak (428 sebelum QR / 400 pairing).
|
|
25
|
+
* Kustom: tuple ['Arch Linux','Chrome','6.12.44'], string 'archLinux:Firefox',
|
|
26
|
+
* 'Mac OS/Safari/15.6.1', id profil 'linux-chrome', atau env BIBZ_BROWSER.
|
|
27
|
+
*/
|
|
28
|
+
identity?: 'auto' | string | WABrowserDescription | null;
|
|
29
|
+
/** alias lama `identity` (tuple eksplisit) */
|
|
30
|
+
browser?: WABrowserDescription | null;
|
|
31
|
+
/** mode auto: maksimum pergantian identitas sebelum menyerah (default 4) */
|
|
32
|
+
maxIdentityRotations?: number;
|
|
33
|
+
logger?: BibzWhatsLogger;
|
|
34
|
+
/** cetak QR ASCII ke console saat fallback (butuh qrcode-terminal) */
|
|
35
|
+
printQR?: boolean;
|
|
36
|
+
/** opsi tambahan langsung ke makeWASocket (override) */
|
|
37
|
+
socketConfig?: Partial<SocketConfig>;
|
|
38
|
+
maxReconnectAttempts?: number;
|
|
39
|
+
maxSessionWipes?: number;
|
|
40
|
+
qrFallbackAfterMs?: number;
|
|
41
|
+
pairingRequestDelayMs?: number;
|
|
42
|
+
restartDelayMs?: number;
|
|
43
|
+
wipeReconnectDelayMs?: number;
|
|
44
|
+
reconnectStepMs?: number;
|
|
45
|
+
reconnectMaxMs?: number;
|
|
46
|
+
forceIPv4?: boolean;
|
|
47
|
+
fetchLatestVersion?: boolean;
|
|
48
|
+
groupMetadataTtlMs?: number;
|
|
49
|
+
/**
|
|
50
|
+
* Override `companion_platform_display` saat pairing kode. WA memvalidasi string ini
|
|
51
|
+
* dengan allow-list; default diturunkan otomatis ke nilai valid ("Chrome (Mac OS)",
|
|
52
|
+
* "Chrome (Linux)" untuk Arch Linux, dst.). Isi hanya bila benar-benar perlu.
|
|
53
|
+
*/
|
|
54
|
+
companionPlatformDisplay?: string | null;
|
|
55
|
+
/** default true: 'ready' dipancarkan untuk setiap socket baru (pertama & tiap reconnect) */
|
|
56
|
+
readyOnEveryConnect?: boolean;
|
|
57
|
+
}
|
|
58
|
+
export declare const BIBZWHATS_DEFAULTS: Readonly<Required<Omit<BibzWhatsOptions, 'phone' | 'pairingCode' | 'logger' | 'printQR' | 'socketConfig'>>>;
|
|
59
|
+
export declare function wipeAuthDir(dir: string): boolean;
|
|
60
|
+
export declare function makeSocketNetworkOptions(opts?: { forceIPv4?: boolean }): Partial<SocketConfig>;
|
|
61
|
+
export declare function sessionWipeReason(status: number | undefined, message?: string): string | null;
|
|
62
|
+
|
|
63
|
+
export interface BibzWhatsEvents {
|
|
64
|
+
'pairing-code': (code: string, meta: PairingCodeMeta) => void;
|
|
65
|
+
qr: (qr: string) => void;
|
|
66
|
+
socket: (sock: WASocket) => void;
|
|
67
|
+
open: (sock: WASocket) => void;
|
|
68
|
+
/** socket baru siap dipakai — pasang handler pesan di sini (tiap reconnect bila readyOnEveryConnect) */
|
|
69
|
+
ready: (sock: WASocket) => void;
|
|
70
|
+
/** hanya sekali seumur client */
|
|
71
|
+
'first-ready': (sock: WASocket) => void;
|
|
72
|
+
user: (digits: string) => void;
|
|
73
|
+
close: (info: { status?: number; error?: Error }) => void;
|
|
74
|
+
reconnecting: (info: { delay: number; attempt: number; fresh: boolean; identity?: string; /** 401 setelah sambung ulang saat pairing masih tertunda → kredensial diganti & kode diminta ulang */ pairingPending?: boolean }) => void;
|
|
75
|
+
/** hanya mode auto: identitas diganti karena server menolak profil sebelumnya */
|
|
76
|
+
'identity-changed': (info: IdentityDescription & { profileId: string; reason: string }) => void;
|
|
77
|
+
'session-wiped': (reason: string) => void;
|
|
78
|
+
'give-up': (message: string) => void;
|
|
79
|
+
'connection.update': (update: Partial<ConnectionState>) => void;
|
|
80
|
+
}
|
|
81
|
+
export interface BibzWhatsClient extends EventEmitter {
|
|
82
|
+
on<K extends keyof BibzWhatsEvents>(event: K, listener: BibzWhatsEvents[K]): this;
|
|
83
|
+
once<K extends keyof BibzWhatsEvents>(event: K, listener: BibzWhatsEvents[K]): this;
|
|
84
|
+
off<K extends keyof BibzWhatsEvents>(event: K, listener: BibzWhatsEvents[K]): this;
|
|
85
|
+
/** socket aktif saat ini (berubah setelah reconnect) */
|
|
86
|
+
/** identitas companion efektif: browser & companion_platform_display yang dipakai saat pairing */
|
|
87
|
+
readonly identity: IdentityDescription & { mode: 'custom' | 'auto'; source: string; profileId: string; tried: Array<{ id: string; reason: string }> };
|
|
88
|
+
readonly sock: WASocket | null;
|
|
89
|
+
readonly initialSock: WASocket;
|
|
90
|
+
readonly options: Required<BibzWhatsOptions>;
|
|
91
|
+
isConnected(): boolean;
|
|
92
|
+
close(): void;
|
|
93
|
+
logout(): Promise<void>;
|
|
94
|
+
}
|
|
95
|
+
export declare function createBibzWhats(options?: BibzWhatsOptions): Promise<BibzWhatsClient>;
|
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
// BibzWhats — createBibzWhats()
|
|
2
|
+
// Pembungkus tingkat tinggi di atas makeWASocket:
|
|
3
|
+
// • versi WA Web TERBARU diambil di setiap connect (server menolak versi lama)
|
|
4
|
+
// • pairing code (custom/acak) + fallback QR headless
|
|
5
|
+
// • auto-reconnect dengan backoff, 515 restartRequired → reconnect segera
|
|
6
|
+
// • sesi korup / 401 loggedOut / 500 badSession / 411 → wipe otomatis + identitas baru
|
|
7
|
+
// • cache metadata grup bawaan (cachedGroupMetadata)
|
|
8
|
+
// • event 'ready' dipancarkan untuk SETIAP socket baru (pertama & tiap reconnect)
|
|
9
|
+
// — pasang handler pesan di sini; 'first-ready' hanya sekali
|
|
10
|
+
//
|
|
11
|
+
// API Baileys asli (sock.ev, sock.sendMessage, ...) tetap tersedia lewat
|
|
12
|
+
// client.sock — tidak ada yang disembunyikan.
|
|
13
|
+
|
|
14
|
+
import fs from 'fs';
|
|
15
|
+
import https from 'https';
|
|
16
|
+
import { EventEmitter } from 'events';
|
|
17
|
+
import pino from 'pino';
|
|
18
|
+
import { Boom } from '@hapi/boom';
|
|
19
|
+
import makeWASocket from '../Socket/index.js';
|
|
20
|
+
import { DEFAULT_CONNECTION_CONFIG } from '../Defaults/index.js';
|
|
21
|
+
import { DisconnectReason } from '../Types/index.js';
|
|
22
|
+
import { useMultiFileAuthState, fetchLatestWaWebVersion } from '../Utils/index.js';
|
|
23
|
+
import { Browsers } from '../Utils/browser-utils.js';
|
|
24
|
+
import { lintIdentity, derivePairingDisplay } from '../Utils/platform-identity.js';
|
|
25
|
+
import { resolveDeviceIdentity, createIdentityRotator, isIdentityRejection, describeIdentity, clearIdentityState, loadIdentityState, saveIdentityState } from './device-identity.js';
|
|
26
|
+
import { createPairingController } from './pairing.js';
|
|
27
|
+
|
|
28
|
+
export const BIBZWHATS_DEFAULTS = Object.freeze({
|
|
29
|
+
authDir: 'bibzwhats-session',
|
|
30
|
+
/**
|
|
31
|
+
* identitas perangkat tertaut. Tidak diisi / 'auto' = pilih profil PALING STABIL
|
|
32
|
+
* (Browsers.macOS('Chrome')) dan berpindah otomatis bila server menolak.
|
|
33
|
+
* Kustom: tuple ['Arch Linux','Chrome','6.12.44'], string 'archLinux:Firefox' /
|
|
34
|
+
* 'Mac OS/Safari/15.6.1' / id profil 'linux-chrome', atau env BIBZ_BROWSER.
|
|
35
|
+
*/
|
|
36
|
+
identity: 'auto',
|
|
37
|
+
/** alias lama untuk `identity` (kompatibel): tuple browser eksplisit */
|
|
38
|
+
browser: null,
|
|
39
|
+
/** mode auto: maksimum pergantian identitas sebelum menyerah */
|
|
40
|
+
maxIdentityRotations: 4,
|
|
41
|
+
maxReconnectAttempts: 10,
|
|
42
|
+
maxSessionWipes: 3,
|
|
43
|
+
qrFallbackAfterMs: 90000,
|
|
44
|
+
pairingRequestDelayMs: 20000,
|
|
45
|
+
restartDelayMs: 2000,
|
|
46
|
+
wipeReconnectDelayMs: 10000,
|
|
47
|
+
reconnectStepMs: 10000,
|
|
48
|
+
reconnectMaxMs: 60000,
|
|
49
|
+
forceIPv4: true,
|
|
50
|
+
fetchLatestVersion: true,
|
|
51
|
+
groupMetadataTtlMs: 5 * 60 * 1000,
|
|
52
|
+
/**
|
|
53
|
+
* string yang divalidasi WA saat pairing kode; null = diturunkan otomatis ke nilai
|
|
54
|
+
* yang PASTI valid: Mac OS → "Chrome (Mac OS)", Arch Linux → "Chrome (Linux)"
|
|
55
|
+
*/
|
|
56
|
+
companionPlatformDisplay: null,
|
|
57
|
+
/** 'ready' tiap socket baru (true) atau hanya sekali (false, perilaku lama) */
|
|
58
|
+
readyOnEveryConnect: true,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
/** Hapus folder sesi & buat ulang. Aman dipanggil saat folder tak ada. */
|
|
62
|
+
export function wipeAuthDir(dir) {
|
|
63
|
+
try {
|
|
64
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
65
|
+
} catch {}
|
|
66
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function makeSocketNetworkOptions({ forceIPv4 = true } = {}) {
|
|
71
|
+
return forceIPv4 ? { fetchAgent: new https.Agent({ keepAlive: true, family: 4 }) } : {};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Peta statusCode → alasan wipe sesi (null = jangan wipe).
|
|
76
|
+
* @param {number|undefined} status
|
|
77
|
+
* @param {string} message
|
|
78
|
+
*/
|
|
79
|
+
export function sessionWipeReason(status, message = '') {
|
|
80
|
+
if (status === DisconnectReason.loggedOut) return '401 loggedOut';
|
|
81
|
+
if (status === 500) return '500 badSession';
|
|
82
|
+
if (status === 411) return '411 multideviceMismatch';
|
|
83
|
+
if (/corrupt|invalid creds|bad session|unauthorized/i.test(String(message))) return `error sesi: ${String(message).slice(0, 60)}`;
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const noopLogger = { info() {}, warn() {}, error() {}, debug() {}, trace() {}, ok() {} };
|
|
88
|
+
|
|
89
|
+
function mergeLogger(logger) {
|
|
90
|
+
const base = logger || console;
|
|
91
|
+
return {
|
|
92
|
+
info: (...a) => (base.info || base.log || noopLogger.info).call(base, ...a),
|
|
93
|
+
warn: (...a) => (base.warn || base.log || noopLogger.warn).call(base, ...a),
|
|
94
|
+
error: (...a) => (base.error || base.log || noopLogger.error).call(base, ...a),
|
|
95
|
+
debug: (...a) => (base.debug || noopLogger.debug).call(base, ...a),
|
|
96
|
+
ok: (...a) => (base.ok || base.info || base.log || noopLogger.ok).call(base, ...a),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* @typedef {object} BibzWhatsOptions
|
|
102
|
+
* @property {string} phone nomor bot (digit, dengan kode negara) — wajib untuk pairing code
|
|
103
|
+
* @property {string} [pairingCode] kode custom 8 karakter A-Z0-9; kosong = acak bawaan
|
|
104
|
+
* @property {string} [authDir] folder kredensial (default 'bibzwhats-session')
|
|
105
|
+
* @property {'auto'|string|[string,string,string]} [identity] identitas perangkat tertaut: 'auto' (default, paling stabil + rotasi otomatis) atau kustom
|
|
106
|
+
* @property {[string,string,string]} [browser] alias lama `identity` (tuple eksplisit)
|
|
107
|
+
* @property {object} [logger] {info,warn,error,ok,debug}
|
|
108
|
+
* @property {boolean} [printQR] cetak QR ASCII ke console saat fallback (butuh qrcode-terminal)
|
|
109
|
+
* @property {object} [socketConfig] opsi tambahan langsung ke makeWASocket (override)
|
|
110
|
+
* @property {string} [companionPlatformDisplay] override `companion_platform_display` (WA memvalidasi dgn allow-list; default diturunkan otomatis, mis. "Chrome (Mac OS)" / "Chrome (Linux)")
|
|
111
|
+
* @property {boolean} [readyOnEveryConnect] default true — 'ready' tiap socket baru; false = hanya sekali
|
|
112
|
+
*/
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Buat client BibzWhats. Mengembalikan EventEmitter dengan event:
|
|
116
|
+
* 'pairing-code' (code, {custom, fallback}) 'qr' (qr)
|
|
117
|
+
* 'open' (sock) 'ready' (sock — tiap socket BARU pertama kali open; pasang handler di sini)
|
|
118
|
+
* 'first-ready' (sock — hanya sekali seumur client)
|
|
119
|
+
* 'close' ({status, error}) 'reconnecting' ({delay, attempt})
|
|
120
|
+
* 'session-wiped' (reason) 'user' (digits) 'give-up' (message)
|
|
121
|
+
* 'identity-changed' ({browser, linkedDeviceName, pairingDisplay, profileId, reason}) — hanya mode auto
|
|
122
|
+
* @param {BibzWhatsOptions} options
|
|
123
|
+
*/
|
|
124
|
+
export async function createBibzWhats(options = {}) {
|
|
125
|
+
const opts = { ...BIBZWHATS_DEFAULTS, ...options };
|
|
126
|
+
const log = mergeLogger(opts.logger);
|
|
127
|
+
const emitter = new EventEmitter();
|
|
128
|
+
const groupMetadataCache = new Map();
|
|
129
|
+
|
|
130
|
+
// ── Identitas perangkat tertaut: kustom (opsi/env) atau otomatis (paling stabil) ──
|
|
131
|
+
const resolved = resolveDeviceIdentity({ identity: opts.identity === 'auto' ? undefined : opts.identity, browser: opts.browser, authDir: opts.authDir });
|
|
132
|
+
for (const note of resolved.notes) (note.level === 'warn' ? log.warn : log.info)(`BibzWhats: ${note.message}`);
|
|
133
|
+
const rotator = createIdentityRotator({
|
|
134
|
+
candidates: resolved.candidates.slice(0, Math.max(1, opts.maxIdentityRotations + 1)),
|
|
135
|
+
authDir: opts.authDir, mode: resolved.mode, logger: log,
|
|
136
|
+
onChange: (c, reason) => emitter.emit('identity-changed', { ...describeIdentity(c.browser, opts), profileId: c.id, reason }),
|
|
137
|
+
});
|
|
138
|
+
const currentBrowser = () => rotator.current.browser;
|
|
139
|
+
log.info(`BibzWhats: identitas perangkat ${resolved.mode === 'auto' ? 'OTOMATIS' : 'KUSTOM'} (${resolved.source}) → ${currentBrowser()[1]} (${currentBrowser()[0]})`);
|
|
140
|
+
// Peringatan dini identitas (sebelum menyentuh server): display tak valid → 400 saat pairing.
|
|
141
|
+
for (const note of lintIdentity({ browser: currentBrowser(), companionPlatformDisplay: opts.companionPlatformDisplay, syncFullHistory: opts.socketConfig?.syncFullHistory })) {
|
|
142
|
+
(note.level === 'warn' ? log.warn : log.info)(`BibzWhats: ${note.message}`);
|
|
143
|
+
}
|
|
144
|
+
const identityInfo = () => Object.freeze({
|
|
145
|
+
...describeIdentity(currentBrowser(), opts),
|
|
146
|
+
mode: resolved.mode, source: resolved.source, profileId: rotator.current.id,
|
|
147
|
+
tried: rotator.tried,
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
let closedByUser = false;
|
|
151
|
+
let attempts = 0;
|
|
152
|
+
let wipeCount = 0;
|
|
153
|
+
let currentSock = null;
|
|
154
|
+
let readyCalled = false;
|
|
155
|
+
let stopPairingRef = () => {};
|
|
156
|
+
// Pairing code sudah diterbitkan server untuk kredensial saat ini (belum terdaftar).
|
|
157
|
+
// Kalau koneksi putus di fase ini, server menolak kredensial yang sama dengan 401
|
|
158
|
+
// ('pairing pending') — bukan masalah nomor/IP: ganti kredensial & minta kode lagi.
|
|
159
|
+
let pairingIssuedAt = 0;
|
|
160
|
+
let pendingPairingWipes = 0;
|
|
161
|
+
// Semua reconnect dijadwalkan lewat satu tempat supaya close() bisa membatalkannya
|
|
162
|
+
// (tanpa ini timer tertinggal → proses tidak bisa keluar & socket "hantu" tersambung lagi).
|
|
163
|
+
let reconnectTimer = null;
|
|
164
|
+
const scheduleReconnect = (delay) => {
|
|
165
|
+
if (reconnectTimer) clearTimeout(reconnectTimer);
|
|
166
|
+
reconnectTimer = setTimeout(() => {
|
|
167
|
+
reconnectTimer = null;
|
|
168
|
+
if (closedByUser) return;
|
|
169
|
+
void connect().catch((e) => log.error(`BibzWhats: reconnect gagal: ${e?.message || e}`));
|
|
170
|
+
}, delay);
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const wipeSession = (reason, { counted = true } = {}) => {
|
|
174
|
+
if (counted) wipeCount += 1;
|
|
175
|
+
stopPairingRef();
|
|
176
|
+
// Simpan identitas yang sudah terbukti stabil: wipe sesi (401/500/411) BUKAN
|
|
177
|
+
// penolakan identitas. Kalau identitas ikut hilang, restart berikutnya mulai
|
|
178
|
+
// lagi dari kandidat pertama dan perangkat bisa muncul dengan nama berbeda.
|
|
179
|
+
const keepIdentity = loadIdentityState(opts.authDir);
|
|
180
|
+
wipeAuthDir(opts.authDir);
|
|
181
|
+
if (keepIdentity && /^(qr-received|pairing-accepted|open)$/.test(String(keepIdentity.reason))) {
|
|
182
|
+
saveIdentityState(opts.authDir, { ...keepIdentity, reason: keepIdentity.reason });
|
|
183
|
+
} else {
|
|
184
|
+
clearIdentityState(opts.authDir);
|
|
185
|
+
}
|
|
186
|
+
groupMetadataCache.clear();
|
|
187
|
+
emitter.emit('session-wiped', reason);
|
|
188
|
+
log.warn(`BibzWhats: sesi dihapus otomatis (${reason}) — folder ${opts.authDir} dibersihkan`);
|
|
189
|
+
if (counted && wipeCount > opts.maxSessionWipes) {
|
|
190
|
+
const message =
|
|
191
|
+
`BibzWhats: ${wipeCount}x hapus sesi tapi masih ditolak — ini BUKAN masalah sesi. ` +
|
|
192
|
+
'Cek: (1) nomor terdaftar & aktif di WhatsApp, (2) digit benar, (3) IP server mungkin dibatasi WA.';
|
|
193
|
+
log.error(message);
|
|
194
|
+
emitter.emit('give-up', message);
|
|
195
|
+
closedByUser = true;
|
|
196
|
+
}
|
|
197
|
+
attempts = 0;
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
async function connect() {
|
|
201
|
+
if (closedByUser) return currentSock;
|
|
202
|
+
let authState;
|
|
203
|
+
let saveCreds;
|
|
204
|
+
try {
|
|
205
|
+
({ state: authState, saveCreds } = await useMultiFileAuthState(opts.authDir));
|
|
206
|
+
if (!authState?.creds) throw new Error('creds kosong');
|
|
207
|
+
} catch (e) {
|
|
208
|
+
log.error(`BibzWhats: sesi KORUP di ${opts.authDir} (${e?.message || e}) — membersihkan otomatis...`);
|
|
209
|
+
wipeSession('sesi korup');
|
|
210
|
+
if (closedByUser) throw new Error(`BibzWhats: pemulihan sesi gagal ${wipeCount}x — periksa nomor/IP lalu start ulang.`);
|
|
211
|
+
({ state: authState, saveCreds } = await useMultiFileAuthState(opts.authDir));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
let version;
|
|
215
|
+
if (opts.fetchLatestVersion) {
|
|
216
|
+
try {
|
|
217
|
+
const latest = await fetchLatestWaWebVersion();
|
|
218
|
+
// hanya pakai hasil fetch bila benar-benar dari web.whatsapp.com; bila gagal
|
|
219
|
+
// jangan menurunkan versi (upstream issue #2777: versi lama → 408 loop)
|
|
220
|
+
if (latest.isLatest) version = latest.version;
|
|
221
|
+
log.info(`BibzWhats: versi WA web ${(version || DEFAULT_CONNECTION_CONFIG.version).join('.')}${latest.isLatest ? '' : ' (bawaan library)'}`);
|
|
222
|
+
} catch (e) {
|
|
223
|
+
log.warn(`BibzWhats: gagal ambil versi WA terbaru (${e?.message || e}) — pakai versi bawaan`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const sock = makeWASocket({
|
|
228
|
+
...(version ? { version } : {}),
|
|
229
|
+
auth: authState,
|
|
230
|
+
logger: pino({ level: 'silent' }),
|
|
231
|
+
browser: currentBrowser(),
|
|
232
|
+
...(opts.companionPlatformDisplay ? { companionPlatformDisplay: opts.companionPlatformDisplay } : {}),
|
|
233
|
+
printQRInTerminal: false,
|
|
234
|
+
generateHighQualityLinkPreview: false,
|
|
235
|
+
syncFullHistory: false,
|
|
236
|
+
markOnlineOnConnect: true,
|
|
237
|
+
keepAliveIntervalMs: 25000,
|
|
238
|
+
retryRequestDelayMs: 350,
|
|
239
|
+
...makeSocketNetworkOptions({ forceIPv4: opts.forceIPv4 }),
|
|
240
|
+
getMessage: async () => undefined,
|
|
241
|
+
cachedGroupMetadata: async (jid) => {
|
|
242
|
+
const cached = groupMetadataCache.get(jid);
|
|
243
|
+
if (cached && Date.now() - cached.ts < opts.groupMetadataTtlMs) return cached.metadata;
|
|
244
|
+
try {
|
|
245
|
+
const metadata = await sock.groupMetadata(jid);
|
|
246
|
+
groupMetadataCache.set(jid, { metadata, ts: Date.now() });
|
|
247
|
+
return metadata;
|
|
248
|
+
} catch {
|
|
249
|
+
return undefined;
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
...(opts.socketConfig || {}),
|
|
253
|
+
});
|
|
254
|
+
currentSock = sock;
|
|
255
|
+
emitter.emit('socket', sock);
|
|
256
|
+
|
|
257
|
+
const cacheGroups = (groups) => {
|
|
258
|
+
for (const group of groups || []) {
|
|
259
|
+
const jid = group?.id || group?.jid;
|
|
260
|
+
if (jid && group) groupMetadataCache.set(jid, { metadata: group, ts: Date.now() });
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
sock.ev.on('groups.update', cacheGroups);
|
|
264
|
+
sock.ev.on('groups.upsert', cacheGroups);
|
|
265
|
+
|
|
266
|
+
let qrFallback = null;
|
|
267
|
+
let pairingStartedAt = 0;
|
|
268
|
+
let pairingStarted = false;
|
|
269
|
+
let sockReady = false;
|
|
270
|
+
let sawQr = false;
|
|
271
|
+
let rotatedByPairing = false;
|
|
272
|
+
const pairing = createPairingController({
|
|
273
|
+
sock,
|
|
274
|
+
phone: opts.phone,
|
|
275
|
+
pairingCode: opts.pairingCode,
|
|
276
|
+
logger: log,
|
|
277
|
+
onCode: (code, meta) => {
|
|
278
|
+
pairingIssuedAt = Date.now();
|
|
279
|
+
rotator.markStable('pairing-accepted');
|
|
280
|
+
emitter.emit('pairing-code', code, meta);
|
|
281
|
+
},
|
|
282
|
+
onRejected: (error, info) => {
|
|
283
|
+
// 400 = companion_platform_display identitas ini tidak dikenal server.
|
|
284
|
+
// Mode auto: ganti identitas & sambung ulang dengan creds yang sama
|
|
285
|
+
// (belum terdaftar, jadi aman). Mode kustom: biarkan pemakai yang memutuskan.
|
|
286
|
+
if (!isIdentityRejection({ phase: 'pairing', status: info?.code, errorData: info?.code })) return false;
|
|
287
|
+
if (!rotator.next('400 bad-request saat pairing kode')) return false;
|
|
288
|
+
rotatedByPairing = true;
|
|
289
|
+
pairing.stop();
|
|
290
|
+
try { sock.end(new Boom('identity rotation', { statusCode: DisconnectReason.connectionClosed })); } catch {}
|
|
291
|
+
return true; // rotasi diambil alih; jangan jadwalkan ulang di controller
|
|
292
|
+
},
|
|
293
|
+
});
|
|
294
|
+
const stopPairing = () => {
|
|
295
|
+
pairing.stop();
|
|
296
|
+
if (qrFallback) {
|
|
297
|
+
clearTimeout(qrFallback);
|
|
298
|
+
qrFallback = null;
|
|
299
|
+
}
|
|
300
|
+
pairingStartedAt = 0;
|
|
301
|
+
};
|
|
302
|
+
stopPairingRef = stopPairing;
|
|
303
|
+
|
|
304
|
+
sock.ev.on('creds.update', (update) => {
|
|
305
|
+
void saveCreds(update);
|
|
306
|
+
if (sock.authState.creds.registered) stopPairing();
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
sock.ev.on('connection.update', (u) => {
|
|
310
|
+
const { connection, qr, lastDisconnect } = u;
|
|
311
|
+
emitter.emit('connection.update', u);
|
|
312
|
+
|
|
313
|
+
if (qr && !sock.authState.creds.registered) {
|
|
314
|
+
if (!sawQr) { sawQr = true; rotator.markStable('qr-received'); }
|
|
315
|
+
if (!pairingStartedAt) pairingStartedAt = Date.now();
|
|
316
|
+
if (opts.phone && !pairingStarted) {
|
|
317
|
+
pairingStarted = true;
|
|
318
|
+
log.info('BibzWhats: perangkat belum terdaftar — meminta pairing code...');
|
|
319
|
+
void pairing.start();
|
|
320
|
+
}
|
|
321
|
+
const showQr = !opts.phone || Date.now() - pairingStartedAt > opts.qrFallbackAfterMs;
|
|
322
|
+
if (showQr) {
|
|
323
|
+
emitter.emit('qr', qr);
|
|
324
|
+
if (opts.printQR) void printQrToTerminal(qr, log);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
if (sock.authState.creds.registered) stopPairing();
|
|
328
|
+
|
|
329
|
+
if (connection === 'open' && !sock.authState.creds.registered && opts.phone) {
|
|
330
|
+
qrFallback = setTimeout(() => {
|
|
331
|
+
if (!sock.authState.creds.registered && !pairingStarted) {
|
|
332
|
+
pairingStarted = true;
|
|
333
|
+
log.info('BibzWhats: QR tidak diterima — minta pairing code langsung...');
|
|
334
|
+
void pairing.start();
|
|
335
|
+
}
|
|
336
|
+
}, opts.pairingRequestDelayMs);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (connection === 'open') {
|
|
340
|
+
if (qrFallback) {
|
|
341
|
+
clearTimeout(qrFallback);
|
|
342
|
+
qrFallback = null;
|
|
343
|
+
}
|
|
344
|
+
attempts = 0;
|
|
345
|
+
wipeCount = 0;
|
|
346
|
+
pairingIssuedAt = 0;
|
|
347
|
+
pendingPairingWipes = 0;
|
|
348
|
+
rotator.markStable('open');
|
|
349
|
+
const digits = String(sock.user?.id || '').split(':')[0].split('@')[0].replace(/\D/g, '');
|
|
350
|
+
log.ok(`BibzWhats: TERHUBUNG sebagai +${digits}`);
|
|
351
|
+
if (digits) emitter.emit('user', digits);
|
|
352
|
+
emitter.emit('open', sock);
|
|
353
|
+
// 'ready' = socket ini siap dipakai & belum pernah dilaporkan. Setelah
|
|
354
|
+
// reconnect, socket lama sudah mati (listener ikut hilang) → pemakai
|
|
355
|
+
// HARUS memasang ulang handler pesan di socket baru.
|
|
356
|
+
if (!sockReady && (opts.readyOnEveryConnect || !readyCalled)) {
|
|
357
|
+
sockReady = true;
|
|
358
|
+
emitter.emit('ready', sock);
|
|
359
|
+
}
|
|
360
|
+
if (!readyCalled) {
|
|
361
|
+
readyCalled = true;
|
|
362
|
+
emitter.emit('first-ready', sock);
|
|
363
|
+
}
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (connection === 'close') {
|
|
368
|
+
stopPairing();
|
|
369
|
+
const err = lastDisconnect?.error;
|
|
370
|
+
const status = err?.output?.statusCode;
|
|
371
|
+
emitter.emit('close', { status, error: err });
|
|
372
|
+
|
|
373
|
+
// Socket ditutup oleh kita sendiri setelah rotasi identitas (400 saat pairing):
|
|
374
|
+
// sambung ulang segera dengan identitas baru, jangan lewat backoff biasa.
|
|
375
|
+
if (rotatedByPairing) {
|
|
376
|
+
emitter.emit('reconnecting', { delay: opts.restartDelayMs, attempt: attempts, fresh: false, identity: rotator.current.id });
|
|
377
|
+
scheduleReconnect(opts.restartDelayMs);
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Server menolak IDENTITAS (bukan sesi/nomor): 428 sebelum QR muncul, atau 405.
|
|
382
|
+
// Mode auto → ganti profil & sambung ulang segera; mode kustom → hanya diberi tahu.
|
|
383
|
+
if (!sock.authState.creds.registered && isIdentityRejection({ status, sawQr, phase: 'connect' })) {
|
|
384
|
+
if (rotator.next(`${status} sebelum QR`)) {
|
|
385
|
+
emitter.emit('reconnecting', { delay: opts.restartDelayMs, attempt: attempts, fresh: false, identity: rotator.current.id });
|
|
386
|
+
scheduleReconnect(opts.restartDelayMs);
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
if (resolved.mode === 'custom') {
|
|
390
|
+
log.error(`BibzWhats: server menolak identitas kustom ${currentBrowser()[1]} (${currentBrowser()[0]}) (statusCode ${status} sebelum QR). ` +
|
|
391
|
+
`Coba identitas lain atau biarkan kosong (mode otomatis).`);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (status === DisconnectReason.restartRequired) {
|
|
396
|
+
log.ok(`BibzWhats: pairing SUKSES — server minta restart (515) — reconnect dalam ${opts.restartDelayMs / 1000}s...`);
|
|
397
|
+
attempts = 0;
|
|
398
|
+
scheduleReconnect(opts.restartDelayMs);
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const pairingPending = !sock.authState.creds.registered && pairingIssuedAt > 0 && Date.now() - pairingIssuedAt < 15 * 60_000;
|
|
403
|
+
if (status === DisconnectReason.loggedOut && pairingPending && pendingPairingWipes < opts.maxReconnectAttempts) {
|
|
404
|
+
pendingPairingWipes += 1;
|
|
405
|
+
const delay = pendingPairingWipes <= 3 ? Math.max(opts.restartDelayMs, 2000) : opts.wipeReconnectDelayMs;
|
|
406
|
+
log.warn(`BibzWhats: pairing sebelumnya masih tertunda di server (401 setelah sambung ulang) — kredensial diganti, pairing code diminta ulang dalam ${delay / 1000}s...`);
|
|
407
|
+
wipeSession('401 pairing tertunda', { counted: false });
|
|
408
|
+
if (closedByUser) return;
|
|
409
|
+
emitter.emit('reconnecting', { delay, attempt: 0, fresh: true, pairingPending: true });
|
|
410
|
+
scheduleReconnect(delay);
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (status === DisconnectReason.loggedOut && sock.authState.creds.registered) {
|
|
415
|
+
log.error('BibzWhats: perangkat DILEPASKAN dari WhatsApp (401) — sesi dihapus & pairing ulang.');
|
|
416
|
+
} else if (status === DisconnectReason.loggedOut) {
|
|
417
|
+
log.error('BibzWhats: koneksi DITOLAK sebelum pairing (401) — nomor belum aktif / digit salah / pairing pending / IP dibatasi.');
|
|
418
|
+
} else if (status) {
|
|
419
|
+
log.warn(`BibzWhats: koneksi tutup (statusCode ${status}) — ${err?.message || ''}`);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const wipeReason = sessionWipeReason(status, err?.message);
|
|
423
|
+
if (wipeReason) {
|
|
424
|
+
wipeSession(wipeReason);
|
|
425
|
+
if (closedByUser) return;
|
|
426
|
+
log.info(`BibzWhats: reconnect dengan sesi BARU dalam ${opts.wipeReconnectDelayMs / 1000}s...`);
|
|
427
|
+
emitter.emit('reconnecting', { delay: opts.wipeReconnectDelayMs, attempt: 0, fresh: true });
|
|
428
|
+
scheduleReconnect(opts.wipeReconnectDelayMs);
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (closedByUser) return;
|
|
433
|
+
attempts += 1;
|
|
434
|
+
if (attempts >= opts.maxReconnectAttempts) {
|
|
435
|
+
const message = `BibzWhats: reconnect dihentikan setelah ${attempts}x gagal berturut — periksa nomor & jaringan.`;
|
|
436
|
+
log.error(message);
|
|
437
|
+
emitter.emit('give-up', message);
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
// Koneksi yang sudah 'sehat' (server menerbitkan pairing code) lalu putus karena
|
|
441
|
+
// jaringan → percobaan pertama cepat; backoff bertahap baru berlaku bila gagal lagi.
|
|
442
|
+
const healthyDrop = attempts === 1 && pairingIssuedAt > 0 && status === DisconnectReason.connectionClosed;
|
|
443
|
+
const delay = healthyDrop ? Math.max(opts.restartDelayMs, 1000) : Math.min(opts.reconnectStepMs * attempts, opts.reconnectMaxMs);
|
|
444
|
+
log.info(`BibzWhats: reconnect otomatis dalam ${Math.round(delay / 1000)}s (percobaan ${attempts}/${opts.maxReconnectAttempts})...`);
|
|
445
|
+
emitter.emit('reconnecting', { delay, attempt: attempts, fresh: false });
|
|
446
|
+
scheduleReconnect(delay);
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
return sock;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
const sock = await connect();
|
|
454
|
+
|
|
455
|
+
// CATATAN: getter TIDAK boleh lewat Object.assign — Object.assign mengevaluasi
|
|
456
|
+
// getter sekali dan menyalin nilainya, sehingga client.sock akan "membeku" di
|
|
457
|
+
// socket pertama setelah reconnect. Pakai defineProperty.
|
|
458
|
+
Object.defineProperty(emitter, 'identity', { get: () => identityInfo(), enumerable: true });
|
|
459
|
+
Object.defineProperty(emitter, 'sock', {
|
|
460
|
+
/** socket aktif saat ini (berubah setelah reconnect) */
|
|
461
|
+
get: () => currentSock,
|
|
462
|
+
enumerable: true,
|
|
463
|
+
configurable: false,
|
|
464
|
+
});
|
|
465
|
+
const client = Object.assign(emitter, {
|
|
466
|
+
/** socket pertama (untuk kompatibilitas) */
|
|
467
|
+
initialSock: sock,
|
|
468
|
+
options: opts,
|
|
469
|
+
isConnected: () => !!currentSock?.user && !!currentSock?.ws?.isOpen,
|
|
470
|
+
close() {
|
|
471
|
+
closedByUser = true;
|
|
472
|
+
stopPairingRef();
|
|
473
|
+
if (reconnectTimer) { clearTimeout(reconnectTimer); reconnectTimer = null; }
|
|
474
|
+
try {
|
|
475
|
+
currentSock?.ev?.removeAllListeners?.('connection.update');
|
|
476
|
+
currentSock?.end?.();
|
|
477
|
+
} catch {}
|
|
478
|
+
},
|
|
479
|
+
async logout() {
|
|
480
|
+
closedByUser = true;
|
|
481
|
+
stopPairingRef();
|
|
482
|
+
if (reconnectTimer) { clearTimeout(reconnectTimer); reconnectTimer = null; }
|
|
483
|
+
try {
|
|
484
|
+
await currentSock?.logout?.();
|
|
485
|
+
} catch {}
|
|
486
|
+
wipeAuthDir(opts.authDir);
|
|
487
|
+
},
|
|
488
|
+
});
|
|
489
|
+
return client;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
async function printQrToTerminal(qr, log) {
|
|
493
|
+
try {
|
|
494
|
+
const mod = await import('qrcode-terminal');
|
|
495
|
+
const QR = mod.default || mod;
|
|
496
|
+
QR.generate(qr, (q) => console.log(q), { small: true });
|
|
497
|
+
} catch {
|
|
498
|
+
log.warn('BibzWhats: qrcode-terminal tidak terpasang — QR mentah: ' + qr.slice(0, 40) + '…');
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|