{"version":3,"sources":["../../../src/oauth2/provider/x.ts"],"sourcesContent":["import { invariant } from '@shware/utils';\nimport { OAuth2Error } from '../error';\nimport type { OAuth2Token, Provider } from '../types';\nimport {\n  createAuthorizationUri,\n  exchangeAuthorizationCode,\n  refreshAccessToken,\n  revokeToken,\n} from './common';\n\nexport interface XOptions {\n  userFields?: UserField[];\n}\n\nconst defaultUserFields = [\n  'id',\n  'name',\n  'profile_image_url',\n  'url',\n  'username',\n  'location',\n  'verified',\n];\n\nexport function createXProvider(options?: XOptions): Provider {\n  return {\n    authorizationUri: 'https://x.com/i/oauth2/authorize',\n    tokenUri: 'https://api.x.com/2/oauth2/token',\n    tokenRefreshUri: 'https://api.x.com/2/oauth2/token',\n    tokenRevokeUri: 'https://api.x.com/2/oauth2/revoke',\n    // https://docs.x.com/x-api/users/user-lookup-me\n    userInfoUri: 'https://api.x.com/2/users/me',\n    defaultScope: ['users.read', 'tweet.read', 'offline.access'],\n    createAuthorizationUri(params) {\n      if (!params.pkce) throw new Error('pkce is required for x');\n      return createAuthorizationUri({\n        ...params,\n        scope: params.scope ?? this.defaultScope,\n        authorizationUri: this.authorizationUri,\n      });\n    },\n    async exchangeAuthorizationCode(params) {\n      if (!params.pkce) throw new Error('pkce is required for x');\n      const response = await exchangeAuthorizationCode({\n        ...params,\n        tokenUri: this.tokenUri,\n        authentication: 'basic',\n      });\n      if (!response.ok) {\n        const { error, error_description } = (await response.json()) as XErrorResponse;\n        throw new OAuth2Error(response.status, error, error_description);\n      }\n      return (await response.json()) as XToken;\n    },\n    async getUserInfo({ access_token }) {\n      invariant(access_token, 'access_token is required');\n      invariant(this.userInfoUri, 'userInfoUri is required');\n\n      const userFields = options?.userFields ?? defaultUserFields;\n      const headers = { Accept: 'application/json', Authorization: `Bearer ${access_token}` };\n\n      const url = new URL(this.userInfoUri);\n      userFields.forEach((field) => url.searchParams.append('user.fields', field));\n\n      const response = await fetch(this.userInfoUri, { headers });\n      if (!response.ok) {\n        const { errors } = (await response.json()) as XAPIErrorResponse;\n        throw new OAuth2Error(\n          response.status,\n          'invalid_request',\n          `Failed to fetch user info: ${errors.at(0)?.detail ?? errors.at(0)?.title}`\n        );\n      }\n      const profile = (await response.json()) as XUserInfo;\n      return {\n        data: profile,\n        claims: {\n          sub: profile.data.id,\n          name: profile.data.name,\n          picture: profile.data.profile_image_url,\n          email: profile.data.email,\n          email_verified: profile.data.verified || false,\n          preferred_username: profile.data.username,\n          profile: `https://x.com/${profile.data.username}`,\n          website: profile.data.url,\n        },\n      };\n    },\n    async refreshAccessToken(params) {\n      invariant(this.tokenRefreshUri, 'tokenRefreshUri is required');\n      const response = await refreshAccessToken({\n        ...params,\n        tokenUri: this.tokenRefreshUri,\n        authentication: 'basic',\n      });\n      if (!response.ok) {\n        const { error, error_description } = (await response.json()) as XErrorResponse;\n        throw new OAuth2Error(response.status, error, error_description);\n      }\n      return (await response.json()) as XToken;\n    },\n    async revokeToken(params) {\n      invariant(this.tokenRevokeUri, 'tokenRevokeUri is required');\n      return revokeToken({\n        ...params,\n        tokenRevokeUri: this.tokenRevokeUri,\n        authentication: 'basic',\n      });\n    },\n  };\n}\n\nexport const x = createXProvider();\n\ntype UserField =\n  | 'affiliation'\n  | 'connection_status'\n  | 'created_at'\n  | 'description'\n  | 'entities'\n  | 'id'\n  | 'is_identity_verified'\n  | 'location'\n  | 'most_recent_tweet_id'\n  | 'name'\n  | 'parody'\n  | 'pinned_tweet_id'\n  | 'profile_banner_url'\n  | 'profile_image_url'\n  | 'protected'\n  | 'public_metrics'\n  | 'receives_your_dm'\n  | 'subscription'\n  | 'subscription_type'\n  | 'url'\n  | 'username'\n  | 'verified'\n  | 'verified_followers_count'\n  | 'verified_type'\n  | 'withheld';\n\nexport interface XUser {\n  id: string;\n  name: string;\n  email?: string;\n  username: string;\n  location?: string;\n  entities?: {\n    url: { urls: UrlEntity[] };\n    description: {\n      hashtags: HashtagEntity[];\n      cashtags: HashtagEntity[];\n      mentions: MentionEntity[];\n    };\n  };\n  verified?: boolean;\n  description?: string;\n  url?: string;\n  profile_image_url?: string;\n  protected?: boolean;\n  pinned_tweet_id?: string;\n  created_at?: string;\n}\n\nexport interface XUserInfo {\n  data: XUser;\n  includes?: { tweets?: { id: string; text: string }[]; users?: XUser[] };\n}\n\ntype UrlEntity = {\n  start: number;\n  end: number;\n  url: string;\n  display_url: string;\n  expanded_url: string;\n};\ntype HashtagEntity = { start: number; end: number; tag: string };\ntype MentionEntity = { start: number; end: string; username: string };\n\nexport interface XToken extends OAuth2Token {\n  token_type: string;\n  expires_in: number;\n  access_token: string;\n  scope: string;\n  refresh_token: string;\n}\n\ntype XErrorResponse = { error: string; error_description: string };\ntype XAPIErrorResponse = {\n  errors: { title: string; type: string; detail?: string; status?: number }[];\n};\n"],"mappings":"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","names":[]}