mirror your GitHub repos to tangled.org automatically
1

Configure Feed

Select the types of activity you want to include in your feed.

1import { App } from '@octokit/app' 2 3let cachedApp: App | undefined 4 5function useApp(): App { 6 if (cachedApp) return cachedApp 7 const appId = process.env.NUXT_GITHUB_APP_ID 8 const privateKey = process.env.NUXT_GITHUB_APP_PRIVATE_KEY 9 if (!appId || !privateKey) { 10 throw new Error('NUXT_GITHUB_APP_ID and NUXT_GITHUB_APP_PRIVATE_KEY must be set') 11 } 12 // The OAuth client id/secret are optional at construction time so the 13 // webhook + sync paths (server-to-server only) keep working without them; 14 // requireOAuthApp() throws explicitly when the /connect flow needs them. 15 const clientId = process.env.NUXT_GITHUB_APP_CLIENT_ID 16 const clientSecret = process.env.NUXT_GITHUB_APP_CLIENT_SECRET 17 cachedApp = new App({ 18 appId, 19 ...(clientId && clientSecret ? { oauth: { clientId, clientSecret } } : {}), 20 // Vercel env vars escape newlines; restore them so PEM parsing works. 21 privateKey: privateKey.replaceAll('\\n', '\n'), 22 }) 23 return cachedApp 24} 25 26export type InstallationOctokit = Awaited<ReturnType<App['getInstallationOctokit']>> 27export type UserOctokit = Awaited<ReturnType<App['oauth']['getUserOctokit']>> 28 29/** Get an Octokit pre-authed for a specific GitHub App installation. */ 30export async function installationOctokit(installationId: number): Promise<InstallationOctokit> { 31 const app = useApp() 32 return app.getInstallationOctokit(installationId) 33} 34 35/** Mint a short-lived installation access token for authenticating git fetches. */ 36export async function installationToken(octokit: InstallationOctokit): Promise<string> { 37 const { token } = (await octokit.auth({ type: 'installation' })) as { token: string } 38 return token 39} 40 41function requireOAuthApp(): App { 42 if (!process.env.NUXT_GITHUB_APP_CLIENT_ID || !process.env.NUXT_GITHUB_APP_CLIENT_SECRET) { 43 throw createError({ 44 statusCode: 500, 45 statusMessage: 'GitHub user-OAuth is not configured (set NUXT_GITHUB_APP_CLIENT_ID and NUXT_GITHUB_APP_CLIENT_SECRET)', 46 }) 47 } 48 return useApp() 49} 50 51/** 52 * Build the GitHub user-to-server OAuth authorize URL. `state` is round-tripped 53 * back to the callback for CSRF protection; the callback verifies it against a 54 * sealed cookie. 55 */ 56export function githubOAuthUrl(opts: { state: string, redirectUri: string }): string { 57 const app = requireOAuthApp() 58 const { url } = app.oauth.getWebFlowAuthorizationUrl({ 59 state: opts.state, 60 redirectUrl: opts.redirectUri, 61 allowSignup: false, 62 }) 63 return url 64} 65 66/** 67 * Exchange an OAuth `code` for a user access token and return an Octokit 68 * authenticated as that user. 69 */ 70export async function userOctokitFromCode(code: string): Promise<UserOctokit> { 71 const app = requireOAuthApp() 72 return app.oauth.getUserOctokit({ code }) 73} 74 75/** 76 * True if the authenticated user administers `installationId`. The 77 * user-to-server `GET /user/installations` endpoint only returns installations 78 * the user can administer, so membership in the list is the ownership proof. 79 */ 80export async function userAdministersInstallation( 81 userOctokit: UserOctokit, 82 installationId: number, 83): Promise<boolean> { 84 // A user with >100 app installations is implausible here, so one page is 85 // enough; `/user/installations` only lists installs the user administers. 86 const { data } = await userOctokit.request('GET /user/installations', { per_page: 100 }) 87 return data.installations.some(install => install.id === installationId) 88} 89 90/** Resolve an installation's account login (for display on the connect page). */ 91export async function installationAccountLogin(installationId: number): Promise<string | null> { 92 const app = useApp() 93 try { 94 const { data } = await app.octokit.request('GET /app/installations/{installation_id}', { 95 installation_id: installationId, 96 }) 97 const account = data.account 98 if (account && 'login' in account) return account.login 99 return null 100 } 101 catch { 102 return null 103 } 104} 105 106/** Test hook. */ 107export function clearGitHubAppCache() { 108 cachedApp = undefined 109}