npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@volter/twin-xidentity

v0.1.37

Published

Local X (Twitter) identity twin — the real x.com authorize screen, the full OAuth 2.0 authorization-code + PKCE round trip (token, refresh, revoke), and GET /2/users/me with X's real field/expansion/problem envelopes, so an unmodified X client completes s

Readme

@volter/twin-xidentity

A local, stateful, vendor-faithful twin of X (Twitter) identity — the x.com authorize screen, the full OAuth 2.0 authorization-code + PKCE round trip (token, refresh, revoke), and GET /2/users/me with X's real field/expansion/problem envelopes. Point an unmodified X client at it and complete a whole sign-in-with-X flow offline.

bun run packages/twin/xidentity/src/cli.ts mirror
# xidentity twin (X OAuth 2.0 + /2/users/me) at http://127.0.0.1:54321
# authorize screen: http://127.0.0.1:54321/i/oauth2/authorize?response_type=code&client_id=…

Deliberately NON-OIDC — because the vendor is

X's OAuth 2.0 issues opaque bearer tokens: no id_token, no JWKS, no discovery document. Identity is fetched from GET /2/users/me with the access token. That non-OIDC shape is the whole reason this vendor needs its own pack next to googleoauth — an integration built against an OIDC provider (id_token claims, openid email profile scopes) breaks against X in exactly the ways this twin reproduces: the scope model is X's own catalog (the OpenAPI's 26 scopes plus the announcement-added users.email, which gates confirmed_email — a recorded intra-vendor discrepancy), the refresh token only arrives with offline.access, PKCE is required, and the only identity read is the API call.

It is a browser-facing protocol pack (the googleoauth class): the authorize screen is served as HTML at the vendor's real path (/i/oauth2/authorize), server-rendered from the twin's own kernel projection by the same React components the browser bundle ships — one renderer, so API↔UI parity cannot drift. The decision is two plain <form> submits ("Authorize app" / "Cancel"), so no JavaScript is required to complete an OAuth round trip against this twin. The bundle and stylesheet the twin serves are committed text (src/xidentity-consent-client.gen.ts, written from client/ by bun scripts/consent-clients.ts and drift-gated by scripts/consent-clients.test.ts): the serve path reads constants and never runs a bundler.

A derived pack (Protocol 3)

The pack is derived (architecture) from X's own OpenAPI document, X API v2 version 2.168 (spec/openapi.json.gz, the same bytes the x pack vendors; provenance in spec/SOURCE.md), corrected by spec/patches.json: the x pack's corrections to the wire's names (tweet.fields, pinned_tweet_id, most_recent_tweet_id), and X's OAuth endpoints the document does not list, patched in from docs.x.com's Authorization Code Flow page and OAuth API reference. bun scripts/derive-pack.ts xidentity writes the wire (src/generated/). GET /2/users/me, POST /2/oauth2/token, POST /2/oauth2/revoke, POST /oauth/request_token, POST /oauth/access_token and POST /1.1/oauth/invalidate_token (and its .json form) are semantics handlers (src/semantics/); every other operation of the document is the gap, X's 404 problem — in a World the x pack claims those paths on api.x.com and api.twitter.com. The authorize pages are screens (src/screens/), and the World's doors are src/xidentity-doors.ts. src/manifest.ts declares the state machines the handlers and screens ask on every move (a screen's settling, a code's redemption, a refresh token's rotation and revocation, an access token's revocation, an OAuth 1.0a request token's life, an OAuth 1.0a access token's invalidation).

A credential is bookkeeping, kept by its SHA-256 — an authorization code, an access token, a refresh token, an OAuth 1.0a request token and an OAuth 2.0 App's client secret live under _ types, never as a subject id or a field of an entry that could be deployed or read in a changeset. A World that ran the protocol 1 pack keeps its credentials under the types they were written as; they still resolve, and the first move on one writes it as bookkeeping. Two rows keep a secret by contract: the OAuth 1.0a App (oauth1_app) and access token (oauth1_token), which the x pack reads by the token itself through the kernel's owner read (xidentity-oauth1.ts states the row shapes); moving them is that contract's change, on both packs at once.

Not done by the Protocol 3 procedure yet: the customer life, the vendor's published examples and the report (journeys/); the capability manifest stays the pack's T0.

Coverage

Partial and honest. The manifest (src/xidentity-capabilities.ts) is the enumerated identity service-area denominator, not a claim to enumerate the whole X vendor surface. It is authored top-down from X's own artefacts (first fetched 2026-08-21): the X API v2 OpenAPI document (2.167 then; 2.168 vendored now) for /2/users/me, the Problem error family and the scope catalog with X's consent descriptions; the docs.x.com OAuth 2.0 guides for the authorize/token/refresh/ revoke requests; the docs.x.com rate-limit pages for the x-rate-limit-* headers and the 75/15-minute per-user figure; and the official SDK sources (@xdevplatform/xdk, twitter-api-typescript-sdk) for the token/revoke response shapes the docs never show as JSON. It currently reads 103 done / 162 covered (59 todo, and the login leg is not an API surface: it is the seeded session, so it is not in that denominator). The todos are real X identity surface this twin does not model — most notably the v1.1 app-only bearer flow, OAuth 1.0a's timestamp window and nonce replay, the unmodelled user.fields (entities, withheld, subscription, the relational fields), expansion hydration — plus a family of wire-pinning todos: behaviours the twin models from RFC 6749/7009 or widely-reported captures because no fetched official artefact states them (exact token error wording, the authorize error channel, refresh-token rotation, the code TTL). Each such done names its evidence boundary in the manifest and its pinning todo.

What is real here

  • The complete round trip. Authorization request → authorize screen → 302 to redirect_uri with exactly state+code (the documented callback shape) → the code is redeemable exactly once, PKCE-verified with real SHA-256 → refresh_token grant (rotating) → revoke kills the whole grant. One pack against one state root, so the code minted at the screen is honoured at the token endpoint by construction.
  • PKCE required, S256 and plain, exactly as X requires it; the legacy official SDK's lowercase s256 spelling is accepted because the vendor's own client emits it.
  • Exact callback matching — X documents exact-match validation, so a trailing slash is a different URI and redirect_uri_mismatch-class bugs reproduce.
  • Confidential vs public clients — HTTP Basic for confidential (the docs' rule), body client_id for public; a confidential client without its Basic header is refused.
  • Vendor-shaped opaque credentials — the twin's codes/tokens are base64url blobs whose decoded structure (<opaque>:<unix-ms>:1:1:ac|at|rt:1) matches what the docs' own examples decode to.
  • The identity read — GET /2/users/me with the OpenAPI's scope demands (tweet.read + users.read), the spec's user.fields, expansions and tweet.fields / post.fields enums with the wire's invalid-parameter envelope (a query parameter the operation does not take is refused the same way), the documented x-rate-limit-* headers, 75/15min per-user accounting, and the documented 429 + legacy code 88 refusal. user.fields=profile_image_url is the seeded photo, or X's default avatar URL (abs.twimg.com/…/default_profile_normal.png) for a persona seeded without one — the same answer the x pack gives for the same person; a pulled persona whose reply withheld the field is not given one.
  • SDK fidelity — the unmodified current official SDK (@xdevplatform/xdk) drives users.getMe() against the twin through its own public baseUrl config (xidentity-sdk.integration.test.ts); the OAuth legs its hardcoded hosts cannot re-aim are proven over real transport in the docs' own curl shapes.

OAuth 1.0a (src/semantics/oauth1.ts, src/screens/oauth1.ts, src/xidentity-oauth1.ts)

The three-legged flow as docs.x.com's API reference and "Obtaining access tokens using 3-legged OAuth flow" state it, which is what Postiz (gitroomhq/postiz-app) connects an X account with through twitter-api-v2:

  • POST oauth/request_token, signed by a registered App with HMAC-SHA1, answers oauth_token, oauth_token_secret and oauth_callback_confirmed=true; a callback the App does not list is code 415, oob is the PIN flow, and x_auth_access_type=read narrows a Read-and-write App.
  • GET oauth/authorize and GET oauth/authenticate (served on api.x.com, where X serves them) show the screen naming the App and the signed-in account, listing what its permission allows; its form posts back to POST oauth/authorize on the same host, as X's screen does (on api.x.com, which the x pack shares, that path and the screen's /_twin/assets/consent.{js,css} are this pack's). Authorize app returns to the callback with exactly oauth_token + oauth_verifier, Cancel with denied=<request token>. authenticate skips the screen once the person approved the App (unless force_login=true).
  • POST oauth/access_token trades the approved request token and verifier, once, for <user id>-<token>, its secret, user_id and screen_name; approving the same App again returns the token already held. POST 1.1/oauth/invalidate_token revokes it.
  • The access token signs GET /2/users/me here and the posting surface in the x pack (which reads this pack's oauth1_token rows). An unknown consumer key or a signature that does not verify is code 32 at the legs and the about:blank 401 on /2.
  • The World's own App is the pair the World's env names (X_API_KEY / X_API_SECRET, or TWITTER_API_KEY / TWITTER_API_SECRET), read with worldEnvValue, named for the World and accepting any callback — the slack pack's World-app rule. Other Apps are registered through POST /_twin/oauth1_apps.

Not modelled (manifest todos): the timestamp window and nonce replay (the twin's clock is the World's, an app signs with its host's), request-token expiry, the login step force_login and screen_name would show, and the live wording of several refusals, each pinned by name.

What is not

The twin authenticates nobody: no password, no 2FA, no risk engine. The x.com "signed in" session is a seeded persona row — POST /_twin/session switches it, which is the honest local equivalent of "the browser is already signed in". This is X's identity surface; posts, timelines and media are the x pack's, which shares X's hosts with this one by path.

Twin-only scaffolding (not vendor surface, not counted)

GET /_twin/consent (re-render a pending authorize screen by request handle) and POST /_twin/consent (the Authorize/Cancel form post — X's real form posts to an undocumented internal endpoint; the OAuth 1.0a screen's posts to POST oauth/authorize, above), POST /_twin/oauth1_apps (register an OAuth 1.0a App: consumer key and secret, callback URLs, permission), POST /_twin/clients, POST /_twin/accounts, POST /_twin/session, POST /_twin/rate_limit (arm a deterministic 429), and the /_twin/assets/* page assets. This list is the audit trail the conformance census deliberately excludes — keep it in lockstep with the doors in xidentity-doors.ts.

The three operations

  • pull — syncXIdentityFromReal(execute) observes the real account behind the operator's own user token (GET /2/users/me, every modelled field) over an injected executor; live runs use liveXIdentityExecute(accessToken), the ONE place a real X request may be issued, guarded by the fail-closed rate budget (xidentity-budget.ts: X's own 75/15min scheme, live-fetched 2026-08-21). The client registry and the grant's scope set are not observable (no API reads X Apps; X has no token introspection) — reasoned gaps, not fakes. The same read is the protocol 2 refresh adapter, syncXIdentityFromRemote, over the kernel's executor.
  • write — the default: local consent flows mint local credentials, no real calls.
  • push — structurally impossible (X exposes no write API for this surface) and reported as such, never faked: performXIdentityAction answers every entry performed: false.

Serving

world-xidentity serve starts one server for the WHOLE surface; point x.com, twitter.com, api.x.com and api.twitter.com at it (the injector's xidentity VENDOR_HOSTS entry does exactly that). mirror is the same server plus a ready-to-open authorize URL; conformance runs the endpoint census (one real probe per claimed endpoint + the hand-enumerated router surface + a reachability witness per resource type).