Skip to content

refactor(gateway): centralize JID normalization in lib/identity.js#2503

Merged
houko merged 8 commits into
librefang:mainfrom
f-liva:feat/identity-a-module
Apr 14, 2026
Merged

refactor(gateway): centralize JID normalization in lib/identity.js#2503
houko merged 8 commits into
librefang:mainfrom
f-liva:feat/identity-a-module

Conversation

@f-liva

@f-liva f-liva commented Apr 14, 2026

Copy link
Copy Markdown
Contributor

Summary

Phase 4 §A of the openclaw-style WhatsApp gateway overhaul. Pure refactor: extracts every inline JID/LID/E.164 manipulation in `packages/whatsapp-gateway/index.js` (~8 ad-hoc sites) into a single `lib/identity.js` module with a tested, behavior-preserving API. Adds structured logging on identity-resolution failures (ID-03) so unresolvable LIDs surface in logs instead of silently producing a malformed peer string.

The refactor enables the upcoming `lid_cache` SQLite persistence (Phase 4 §B) by giving it a single point to hook write-through.

What `lib/identity.js` exposes

  • `isLidJid(jid)` — `@lid` and `@hosted.lid` (the latter is documented in Baileys for future-proofing)
  • `isGroupJid(jid)` — `@g.us`
  • `normalizeDeviceScopedJid(jid)` — strips multi-device `:` suffix
  • `extractE164(jid)` — returns the phone number with leading `+` from `@s.whatsapp.net` JIDs
  • `resolvePeerId(jid, { lidToPnCache, senderPn, participant }) -> { peer, confidence }` with confidence tags `'direct' | 'cache' | 'participant' | 'lid_unresolved' | 'group'`
  • `deriveOwnerJids(ownerNumbers)` — moves OWNER_NUMBERS → OWNER_JIDS resolution into the module
  • `phoneToJid(phone)` — outbound phone-to-JID conversion (deduplicates 3 byte-identical inline call sites)

Latent bug closed

Legacy inline `'+' + senderPnJid.replace(/@.*$/, '')` produced malformed `+123:45` for device-scoped JIDs. `extractE164` now correctly returns `+123`. Documented in commit `bd1dcb4f` + asserted in tests.

Behavior preservation

Each refactor commit is bisectable — full test suite passes at every step. Pre-refactor inline logic was reproduced verbatim as `legacyX` helpers inside the equivalence test block, then asserted equal to the new module output across 5 fixture shapes (plain PN, LID+senderPn, LID+cache hit, LID+participant, LID-unresolvable) plus outbound phone-to-JID and owner-derivation.

Test plan

  • `test/identity.test.js` — 41 assertions across 7 suites (each helper isolated)
  • `index.test.js` — +13 tests including before/after equivalence + ID-03 log shape
  • Total `node --test`: 142 pass (was 129)
  • `node --check packages/whatsapp-gateway/index.js` clean

Rollback

Pure refactor — revert PR. No flag needed.

Files

  • `packages/whatsapp-gateway/lib/identity.js` (new, 107 lines)
  • `packages/whatsapp-gateway/test/identity.test.js` (new, 206 lines)
  • `packages/whatsapp-gateway/index.js` (refactored ~8 call sites)
  • `packages/whatsapp-gateway/index.test.js` (+160 lines)

No new dependencies. Phase 4 §B (persisted `lid_cache` table) is the natural follow-up.

Federico Liva added 8 commits April 14, 2026 15:43
- Pure functional module: isLidJid, isGroupJid, normalizeDeviceScopedJid,
  extractE164, phoneToJid, resolvePeerId, deriveOwnerJids
- No side effects on require, no hidden state, cache passed as param
- resolvePeerId honors 5-step heuristic from CONTEXT §A Specifics
- Unit tests: 41 assertions across 7 describe blocks
Add import of identity helpers next to existing lib/echo-tracker import.
No call-site changes yet — subsequent commits migrate sites one at a time.
Replace inline Set(map(n => n.replace(/^\+/, '') + '@s.whatsapp.net'))
with the module helper. Behavior-preserving.
Unify the 3 identical inline copies in sendMessage/sendImage/sendAudio
(they were verbatim duplicates; diff confirmed identical). Group JIDs
still passthrough, phones still coerced to @s.whatsapp.net form.
…D-01, ID-03)

- Main sender-resolution cascade (~line 1171-1197) replaced with a single
  resolvePeerId() call that honors the 5-step heuristic from CONTEXT §A.
- Phone derivation via extractE164() — also fixes latent bug where
  device-scoped incoming JIDs ('123:[email protected]') previously yielded
  malformed '+123:45' phone strings; now correctly yields '+123'.
- console.warn for unresolved identity becomes structured JSON per ID-03:
  {event: 'identity_unresolved', jid, reason, lid_cache_size, confidence}.
- Preserved side effects outside the pure function per §Concerns #1:
  senderPn cache write and CS-02 resolveLidProactively both still run
  BEFORE resolvePeerId — the function only resolves, it never writes.
- Renamed local 'isLidJid' (shadowed module fn) to 'isLid' per §Concerns
  #2 — all downstream references (ownerLidJids check at line ~1211)
  updated to the alias.
- isLidJid/isGroupJid module fns replace inline endsWith checks.
- phoneToJid replaces inline '+'-strip + '@s.whatsapp.net' append.
Final inline endsWith('@g.us') site removed. Zero inline JID suffix
manipulation remains in index.js.
- 12 equivalence assertions comparing pre-refactor inline logic to
  lib/identity helpers across: isLid, isGroup, deriveOwnerJids,
  phoneToJid, resolvePeerId (5 fixture shapes: plain phone, LID+senderPn,
  LID+cache, LID+participant, LID unresolvable), extractE164 device-strip
  latent fix, normalizeDeviceScopedJid passthrough.
- 1 assertion validating ID-03 identity_unresolved JSON log shape
  (event, jid, reason, lid_cache_size, confidence all present).
- Test count: 129 -> 142.
@github-actions github-actions Bot added size/L 250-999 lines changed ready-for-review PR is ready for maintainer review area/sdk JavaScript and Python SDKs and removed ready-for-review PR is ready for maintainer review size/L 250-999 lines changed labels Apr 14, 2026
@github-actions
github-actions Bot requested a review from houko April 14, 2026 14:24
@github-actions github-actions Bot added the no-rust-required This task does not require Rust knowledge label Apr 14, 2026

@houko houko left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Clean extraction refactor with good equivalence tests and latent bug fix coverage.

@houko
houko merged commit 9bcac27 into librefang:main Apr 14, 2026
27 of 28 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/sdk JavaScript and Python SDKs no-rust-required This task does not require Rust knowledge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants