Setup & identity
Looking things up on XChat needs only a logged-in session. Sending anything needs an XChat identity: two P-256 keypairs whose public halves are published to X and whose private halves you keep.
You can create a key once, persist it, and load it on later runs.
import Emusks from "emusks";
const client = new Emusks();
await client.login("your_auth_token");
const identity = await client.xchat.createIdentity({ pin: "2468" }); // first run
// save `identity` somewhere safe, then on later runs:
await client.xchat.loadIdentity(identity);client.xchat.createIdentity(opts?)
Generate a fresh chat identity, publish the public half, back it up under your PIN, and load it into the client. Returns a serializable identity object: persist it (it contains your private keys) and reuse it with loadIdentity.
By default this is PIN-backed with Juicebox, just like what the normal XChat uses, so the identity can be recovered with the PIN. Use the same passcode format the app uses, a 4-digit code, so the identity is also unlockable from the official X UI (the realms accept arbitrary bytes, but the app's passcode field is 4 digits).
| Option | Type | Description |
|---|---|---|
pin | string | Your passcode (4 digits, like the X app). Required unless selfCustody is set. Backs the keys up to the realms. |
selfCustody | boolean | Skip the PIN backup: you hold the only copy of the keys (no PIN recovery). |
registrationMethod | string | Override the published method (CustomPin for PIN-backed, SelfCustody, ManagedPin). |
// PIN-backed (default, like the app)
const identity = await client.xchat.createIdentity({ pin: "2468" });
await fs.writeFile("xchat-identity.json", JSON.stringify(identity)); // also holds your private keys
// or self-custody (no PIN backup)
const local = await client.xchat.createIdentity({ selfCustody: true });TIP
PIN-backed setup uses a bundled WebAssembly build of the Juicebox SDK; it loads only when you actually create or recover a PIN-backed identity. If the backup fails, createIdentity rolls back the published key so you never end up with an un-backed-up identity. Either way, the returned object also holds the private keys, so persist it.
WARNING
With selfCustody: true there is no cross-device PIN recovery: lose the identity object and you lose access to that identity's chats.
client.xchat.loadIdentity(identity)
Load a previously created identity so you can send. Pass the object from createIdentity (or one assembled from saved key material). Prefer using this over xchat.recover when you can, since it's much faster and doesn't require the entire very complex recovery process.
| Field | Type | Description |
|---|---|---|
version | string | Your published key version |
publicKeyB64 | string | Your identity public key (SPKI base64) |
identityPrivateJwk / signingPrivateJwk | object | Your private keys as JWK |
userId | string | Optional; defaults to the logged-in user |
const identity = JSON.parse(await fs.readFile("xchat-identity.json", "utf8"));
await client.xchat.loadIdentity(identity);Once loaded, head to Sending messages.
client.xchat.recover(pin)
Recover your identity from X's PIN-protected realms using only your PIN and this logged-in session, no saved key file. This is the "new device" flow: it fetches your published key, pulls the backed-up private keys from the realms with your PIN, reconstructs the keypairs, and loads them. After it returns you can read and send on all your existing conversations.
| Param | Type | Description |
|---|---|---|
pin | string | The PIN you used with createIdentity |
const client = new Emusks();
await client.login("your_auth_token");
await client.xchat.recover("2468"); // identity restored from the realms, nothing stored locally
for (const convo of await client.xchat.conversations()) {
const { messages } = await client.xchat.read(convo.conversationId.split(":").find((id) => id !== me.id));
// ... all your chats, decrypted
}Only works for a PIN-backed identity (the default). If you used selfCustody: true, there is nothing in the realms to recover; loadIdentity your saved object instead.