# url-builder ## Purpose Provides pure URL-building and body-encoding utilities used internally by all OAuth 2.0 grant flows. Keeping these helpers in a single module avoids duplication across the authorization-code, client-credentials, and token-refresh modules while keeping each flow module focused on business logic rather than HTTP serialization details. These utilities are deliberately stateless and side-effect-free so they can be tested in complete isolation from network calls. ## Features - `buildAuthorizationUrl` — constructs a complete authorization endpoint URL with PKCE, state, scopes, and optional OIDC parameters (`nonce`, `prompt`, `login_hint`). - `buildTokenRequestBody` — serializes a flat string record to `application/x-www-form-urlencoded` using `encodeURIComponent`. - `normalizeTokenResponse` — maps a raw token-endpoint JSON body to a `TokenSet`-compatible shape and derives `expiresAt` from `expires_in`. ## Usage: Basic Example ```ts import { buildAuthorizationUrl, buildTokenRequestBody } from "@suaveplan/oauth-client/utils/url-builder"; const url = buildAuthorizationUrl( { clientId: "my-app", authorizationUrl: "https://auth.example.com/authorize", tokenUrl: "…" }, { state: "abc", codeChallenge: "xyz", codeChallengeMethod: "S256" }, ); const body = buildTokenRequestBody({ grant_type: "authorization_code", code: "AUTH_CODE", redirect_uri: "https://app.example.com/callback", client_id: "my-app", code_verifier: "VERIFIER", }); ``` ## Usage: Advanced Example ```ts import { buildAuthorizationUrl, normalizeTokenResponse, } from "@suaveplan/oauth-client/utils/url-builder"; // OIDC authorization request with nonce and prompt const url = buildAuthorizationUrl( { clientId: "my-app", authorizationUrl: "https://accounts.google.com/o/oauth2/v2/auth", tokenUrl: "https://oauth2.googleapis.com/token", redirectUri: "https://app.example.com/callback", scopes: ["openid", "profile", "email"], }, { state: "state123", codeChallenge: "challenge456", codeChallengeMethod: "S256", nonce: "nonce789", prompt: "consent", loginHint: "user@example.com", extra: { access_type: "offline" }, }, ); // Normalize raw token response const raw = { access_token: "at", token_type: "Bearer", expires_in: 3600 }; const tokenSet = normalizeTokenResponse(raw, Date.now()); // tokenSet.expiresAt is set ~1 hour from now ``` ## API Reference ### `buildAuthorizationUrl(config, params): string` Builds a full authorization URL. - `config` — `OAuthConfig` providing `clientId`, `authorizationUrl`, optional `redirectUri` and `scopes`. - `params.state` — CSRF-protection state value. - `params.codeChallenge` — PKCE code challenge. - `params.codeChallengeMethod` — `"S256"` or `"plain"`. - `params.scope?` — overrides `config.scopes` for this request. - `params.nonce?` — OIDC nonce. - `params.prompt?` — UI prompt hint. - `params.loginHint?` — pre-fill login hint. - `params.extra?` — additional query parameters. ### `buildTokenRequestBody(params: Record): string` Returns a percent-encoded `application/x-www-form-urlencoded` string. Both keys and values are encoded with `encodeURIComponent`. ### `normalizeTokenResponse(raw: Record, now: number): TokenSet` Maps a raw JSON body to `{ accessToken, tokenType, expiresIn?, expiresAt?, refreshToken?, idToken?, scope? }`. The `now` parameter (milliseconds since epoch) is used to compute `expiresAt = now + expiresIn * 1000`. ## Implementation Notes `buildAuthorizationUrl` uses the platform `URL` constructor and `URLSearchParams` for correct percent-encoding of complex values. The URL constructor is universally available in all targeted runtimes (Node 10+, browsers, Bun, Deno, edge workers). `buildTokenRequestBody` uses `encodeURIComponent` directly rather than `URLSearchParams` to produce a simple string body that can be passed to fetch without additional wrappers. `normalizeTokenResponse` is intentionally defensive — it uses `typeof` guards rather than Zod for performance in hot paths, leaving schema validation to the caller.