# Auth State Custom & Testing

## Memahami Auth State

Semua data autentikasi bot disimpan di `AuthenticationCreds` + `SignalKeyStore`. Ini yang bikin session bot tetap valid walau direstart.

### Struktur AuthenticationCreds

```ts
type AuthenticationCreds = {
  // === Kunci Kriptografi ===
  noiseKey: KeyPair              // Kunci Noise Protocol (koneksi WS)
  signedIdentityKey: KeyPair     // Identity Key (long-term)
  signedPreKey: SignedKeyPair    // Signed Pre-Key (medium-term)
  pairingEphemeralKeyPair: KeyPair // Kunci pairing code
  registrationId: number         // ID registrasi (14-bit random)
  firstUnuploadedPreKeyId: number // ID pre-key pertama yang belum diupload
  nextPreKeyId: number           // ID pre-key berikutnya

  // === Data Session ===
  me?: Contact                   // Profile bot sendiri
  account?: proto.IADVSignedDeviceIdentity  // Identitas perangkat
  signalIdentities?: SignalIdentity[]      // Identity key + address
  myAppStateKeyId?: string       // Key ID buat app state sync
  lastAccountSyncTimestamp?: number
  platform?: string              // Platform (ios/android/web)

  // === History Sync ===
  processedHistoryMessages: MinimalMessage[]
  accountSyncCounter: number
  accountSettings: AccountSettings

  // === Status ===
  registered: boolean            // Udah terdaftar di WA?
  pairingCode: string | undefined
  lastPropHash: string | undefined
  routingInfo: Buffer | undefined
  additionalData?: any
}
```

### SignalKeyStore

Semua kunci Signal (pre-keys, sessions, sender-keys) disimpan di `SignalKeyStore`:

```ts
type SignalDataTypeMap = {
  'pre-key': KeyPair                    // One-time pre-keys
  session: Uint8Array                   // Signal sessions (serialized)
  'sender-key': Uint8Array              // Sender keys for groups
  'sender-key-memory': { [jid]: boolean }
  'app-state-sync-key': AppStateSyncKeyData
  'app-state-sync-version': LTHashState
  'lid-mapping': LIDMapping            // PN ↔ LID mapping
  'device-list': string[]              // Known device list
  'tctoken': Buffer                    // Privacy tokens
  'identity-key': KeyPair              // Cached identity keys
}
```

---

## Custom Auth State (Pake Database)

`useMultiFileAuthState` simpen semua data ke file. Kalo mau pake database (PostgreSQL, Redis, MongoDB), lu tinggal implement `SignalKeyStore` sendiri.

### SignalKeyStore Interface

```ts
interface SignalKeyStore {
  get(type: string, ids: string[]): Promise<{ [id: string]: any }>
  set(data: { [type: string]: { [id: string]: any } }): Promise<void>
  clear?(): Promise<void>
}
```

### Contoh: Redis

```ts
import { createClient } from 'redis'
import makeWASocket, { initAuthCreds, BufferJSON } from '@mataram/wa'

const client = createClient()
await client.connect()

function buildRedisStore(prefix = 'wa:') {
  return {
    async get(type, ids) {
      const entries: Record<string, any> = {}
      for (const id of ids) {
        const val = await client.get(`${prefix}${type}:${id}`)
        if (val) entries[id] = JSON.parse(val, BufferJSON.reviver)
      }
      return entries
    },
    async set(data) {
      const pipe = client.multi()
      for (const [type, items] of Object.entries(data)) {
        for (const [id, val] of Object.entries(items)) {
          const key = `${prefix}${type}:${id}`
          if (val === null) {
            pipe.del(key)
          } else {
            pipe.set(key, JSON.stringify(val, BufferJSON.replacer))
          }
        }
      }
      await pipe.exec()
    },
  }
}

async function start() {
  const keys = buildRedisStore()
  const creds = initAuthCreds()

  const sock = makeWASocket({
    auth: { creds, keys },
  })
}
```

### Contoh: MongoDB

```ts
import { MongoClient } from 'mongodb'

const mongo = await MongoClient.connect('mongodb://localhost:27017')
const db = mongo.db('wa_bot')

function buildMongoStore() {
  return {
    async get(type, ids) {
      const entries: Record<string, any> = {}
      const docs = await db.collection(type).find({ _id: { $in: ids } }).toArray()
      for (const doc of docs) {
        entries[doc._id] = doc.data
      }
      return entries
    },
    async set(data) {
      const writes: any[] = []
      for (const [type, items] of Object.entries(data)) {
        for (const [id, val] of Object.entries(items)) {
          if (val === null) {
            writes.push({ deleteOne: { filter: { _id: id } } })
          } else {
            writes.push({
              updateOne: {
                filter: { _id: id },
                update: { $set: { data: val } },
                upsert: true,
              },
            })
          }
        }
      }
      if (writes.length) await db.collection('keys').bulkWrite(writes)
    },
  }
}
```

### Contoh: Simpen Creds ke Database

```ts
let savedCreds: AuthenticationCreds | null = null

const credsStore = {
  async get() {
    if (savedCreds) return savedCreds
    const doc = await db.collection('creds').findOne({ _id: 'main' })
    savedCreds = doc ? JSON.parse(doc.data, BufferJSON.reviver) : initAuthCreds()
    return savedCreds!
  },
  async set(creds: AuthenticationCreds) {
    savedCreds = creds
    await db.collection('creds').updateOne(
      { _id: 'main' },
      { $set: { data: JSON.stringify(creds, BufferJSON.replacer) } },
      { upsert: true }
    )
  },
}

const sock = makeWASocket({
  auth: { creds: await credsStore.get(), keys: buildMongoStore() },
})

sock.ev.on('creds.update', async (update) => {
  const creds = await credsStore.get()
  Object.assign(creds, update)
  await credsStore.set(creds)
})
```

---

## Testing Workflow

### Unit Tests

Jest test file di `src/__tests__/`:

```ts
// src/__tests__/Utils/rich.test.ts
import { buildRichMessageContent } from '../../Utils/rich'

describe('buildRichMessageContent', () => {
  it('converts heading to submessage', () => {
    const result = buildRichMessageContent('# Hello', '0@bot')
    expect(result.botForwardedMessage).toBeDefined()
  })
})
```

```bash
yarn test           # unit + integration
yarn test:e2e       # end-to-end (butuh mock server)
```

### Manual Test tanpa Bot

Buat script singkat aja:

```ts
// test-rich.js
import makeWASocket, { useMultiFileAuthState } from '@mataram/wa'

async function test() {
  const { state, saveCreds } = await useMultiFileAuthState('test_auth')
  const sock = makeWASocket({ auth: state })
  sock.ev.on('creds.update', saveCreds)
  sock.ev.on('connection.update', async ({ connection }) => {
    if (connection === 'open') {
      await sock.sendMessage('628xxx@s.whatsapp.net', {
        text: '# Test\n**bold** *italic* `code`',
        rich: true,
      })
      console.log('Sent!')
    }
  })
}
test()
```

### Debug Mode

```ts
import P from 'pino'
const sock = makeWASocket({
  logger: P({ level: 'debug' }),
})
```

Ini bakal nampilin semua raw binary node dari WhatsApp — berguna banget buat debug protocol.

### Simulasi Pairing Ulang

Buat pairing code tanpa connect penuh:

```ts
import makeWASocket, { useMultiFileAuthState } from '@mataram/wa'

async function pairOnly() {
  const { state } = await useMultiFileAuthState('auth')
  const sock = makeWASocket({ auth: state, syncFullHistory: false, fireInitQueries: false })
  sock.ev.on('connection.update', async ({ qr, pairingCode }) => {
    if (qr) console.log('QR:', qr)
    if (pairingCode) console.log('Kode:', pairingCode)
  })
}
```

### Cek Fitur Baru

Cek version terbaru:

```ts
import { fetchLatestVersion } from '@mataram/wa'
const { version } = await fetchLatestVersion()
console.log('WA Web version:', version)
```

Cek state akun:

```ts
const timelock = await sock.fetchAccountReachoutTimelock()
const cap = await sock.fetchNewChatMessageCap()
console.log({ timelock, cap })
```

---

## Best Practices

### 1. Backup Session

Session ada di `creds.json` + semua file di folder auth. Backup rutin:

```bash
tar -czf auth-backup.tar.gz auth_info/
```

Kalo session ilang: harus pairing ulang, semua kunci Signal hilang.

### 2. Jangan Commit Session

Udah include di `.gitignore`:

```
auth_info*/
baileys_auth_info*
furina/
*.session.json
```

### 3. Cache Group Metadata

Group metadata jarang berubah. Cache biar ga request tiap kali:

```ts
import NodeCache from 'node-cache'
const groupCache = new NodeCache({ stdTTL: 300 }) // 5 menit

const sock = makeWASocket({
  cachedGroupMetadata: async (jid) => groupCache.get(jid),
})
```

Pas dapet `groups.update`, update cache:

```ts
sock.ev.on('groups.update', (updates) => {
  for (const g of updates) groupCache.set(g.id, g)
})
```

### 4. Handle Retry

Pasang `getMessage` callback biar retry mechanism bisa kerja:

```ts
const sock = makeWASocket({
  getMessage: async (key) => {
    // Ambil dari database sendiri
    return messagesDb.get(key.id)
  },
  maxMsgRetryCount: 5,
  enableRecentMessageCache: true,
  enableAutoSessionRecreation: true,
})
```

### 5. Filter History Sync

Kalo bot di grup dengan ribuan pesan, filter sync biar ga overload:

```ts
const sock = makeWASocket({
  shouldSyncHistoryMessage: (msg) => {
    // syncType 0 = initial bootstrap
    // syncType 1 = normal
    // syncType 2 = full
    return msg.syncType !== 2 // skip FULL sync
  },
})
```

### 6. Gunakan Event Buffer

Proses event dalem `sock.ev.process()` biar batch processing:

```ts
sock.ev.process((events) => {
  // Semua events dalem 1 tick bakal dikumpulin
  for (const [event, data] of events) {
    // Handle batch
  }
})
```
