Skip to content

feat: add new_device flag to pair additional accounts into one store - #203

Open
Ceirced wants to merge 1 commit into
krypton-byte:masterfrom
Ceirced:feat/new-device-pairing
Open

feat: add new_device flag to pair additional accounts into one store#203
Ceirced wants to merge 1 commit into
krypton-byte:masterfrom
Ceirced:feat/new-device-pairing

Conversation

@Ceirced

@Ceirced Ceirced commented Jul 22, 2026

Copy link
Copy Markdown

Problem

In a database that already contains a session, it is currently impossible to pair a second account. A jid-less client resolves its device via container.GetFirstDevice(), which only creates a blank device when the store is empty — so ClientFactory.new_client(uuid=...) on a populated store silently resumes the first stored session instead: no QR is ever emitted, and the resumed session kicks the original client with a replaced-stream error. container.NewDevice() — whatsmeow's API for exactly this — is not exposed anywhere in goneonize.

This is the failure described in #104 ("a new client was created, but there is no display to scan the QR code"). The README's multi-session example only covers resuming known JIDs, matching the comment in main.go: "If you want multiple sessions, remember their JIDs and use .GetDevice(jid)" — onboarding session #2 has no supported path today. Downstream apps currently have to work around this with one database per account.

Change

  • goneonize/main.go: new newDevice C.int parameter on the Neonize export; when set (and no jid is given) the client is built on container.NewDevice() instead of GetFirstDevice(), so a fresh QR pairing starts alongside existing sessions.
  • neonize/client.py, neonize/aioze/client.py: new_device: bool = False on NewClient/NewAClient and both ClientFactory.new_client(); flag passed through to Go.
  • neonize/_binder.py: matching ctypes.c_int in the Neonize argtypes.
  • README.md: multi-session example gains an "add another account" line.

Usage:

new_account = client_factory.new_client(uuid="my-second-account", new_device=True)

Compatibility

Default behavior is byte-for-byte unchanged: jid → GetDevice, no jid → GetFirstDevice. The C signature change is internal — the Python layer and the goneonize binary ship together per release. The blank device from NewDevice() is persisted by whatsmeow on successful pairing, exactly like the empty-store first pairing today.

Testing

  • go build ./... passes on the patched goneonize.
  • python -m py_compile on the three touched Python files.
  • Marked draft because I have not yet run a live two-account QR pairing against this build — will do so and report back, but happy for CI/maintainer eyes meanwhile.

Fixes #104

A jid-less client resolves its device via container.GetFirstDevice(),
which only creates a blank device when the store is empty — so in a
database that already holds a session, pairing a second account is
impossible: the client silently resumes the first stored session and no
QR is ever emitted (and the resumed session kicks the original client
with a replaced-stream error).

Expose whatsmeow's container.NewDevice() through a new newDevice
parameter on the Neonize export, threaded through NewClient/NewAClient
and both ClientFactory.new_client() as new_device=False. Passing
new_device=True with a uuid forces a brand-new blank device, so a fresh
QR pairing starts alongside the existing sessions. Default behavior is
unchanged.
@Ceirced

Ceirced commented Jul 23, 2026

Copy link
Copy Markdown
Author

Following up on the draft note — I've now run the live two-account QR pairing against this branch's build (darwin/arm64, Go 1.26.5, real WhatsApp servers). Everything checks out:

1. Default path unaffected (empty store): new_client(uuid=...) with no flag → QR emitted → scanned → paired device …:46, one device row in the store.

2. The fix (populated store): new_client(uuid=..., new_device=True) on that same store → a fresh QR was emitted alongside the existing session (the old path never shows a QR here) → scanned → paired device …:47. The store now holds both device rows; the first row is untouched.

3. Coexistence: connected both devices from the same store simultaneously and held them online — ConnectedEv fired on both, no LoggedOutEv / stream-replaced on either.

I also reproduced the failure mode this PR fixes, on a store seeded with an existing device row: without the flag no QR is ever emitted — whatsmeow resumes the stored session, gets a 401, and deletes the device row. With new_device=True the QR arrives and the stored row survives.

One unrelated upstream note from testing multi-session use: starting two clients' connect() at the same instant aborts with fatal error: concurrent map writes on the global StopSignal map (in the Neonize export) — pre-existing, a few seconds of stagger between connects avoids it. Happy to file a separate issue.

Undrafting — ready for review.

@Ceirced
Ceirced marked this pull request as ready for review July 23, 2026 13:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

How to add new client for async multisession

1 participant