()
| 93 | } |
| 94 | |
| 95 | export async function runTwitterOAuthFlow(): Promise<{ tokenPath: string; scope?: string }> { |
| 96 | const cfg = loadXApiConfig(); |
| 97 | if (!cfg.callbackUrl) { |
| 98 | throw new Error('Missing X_CALLBACK_URL in .env.local'); |
| 99 | } |
| 100 | |
| 101 | const { url, state, verifier } = buildTwitterOAuthUrl(); |
| 102 | const callback = new URL(cfg.callbackUrl); |
| 103 | const port = Number(callback.port || 80); |
| 104 | const pathname = callback.pathname; |
| 105 | |
| 106 | const code = await new Promise<string>((resolve, reject) => { |
| 107 | const server = http.createServer((req, res) => { |
| 108 | try { |
| 109 | const reqUrl = new URL(req.url ?? '/', `http://127.0.0.1:${port}`); |
| 110 | if (reqUrl.pathname !== pathname) { |
| 111 | res.statusCode = 404; |
| 112 | res.end('Not found'); |
| 113 | return; |
| 114 | } |
| 115 | |
| 116 | const returnedState = reqUrl.searchParams.get('state'); |
| 117 | const returnedCode = reqUrl.searchParams.get('code'); |
| 118 | const error = reqUrl.searchParams.get('error'); |
| 119 | |
| 120 | if (error) { |
| 121 | res.statusCode = 400; |
| 122 | res.end(`OAuth error: ${error}`); |
| 123 | server.close(); |
| 124 | reject(new Error(`OAuth error: ${error}`)); |
| 125 | return; |
| 126 | } |
| 127 | |
| 128 | if (!returnedCode || returnedState !== state) { |
| 129 | res.statusCode = 400; |
| 130 | res.end('Invalid OAuth callback'); |
| 131 | server.close(); |
| 132 | reject(new Error('Invalid OAuth callback state/code')); |
| 133 | return; |
| 134 | } |
| 135 | |
| 136 | res.statusCode = 200; |
| 137 | res.end('ft auth complete. You can close this tab.'); |
| 138 | server.close(); |
| 139 | resolve(returnedCode); |
| 140 | } catch (err) { |
| 141 | server.close(); |
| 142 | reject(err); |
| 143 | } |
| 144 | }); |
| 145 | |
| 146 | server.listen(port, '127.0.0.1', () => { |
| 147 | console.log('Open this URL in your browser to authorize X bookmarks access:'); |
| 148 | console.log(url); |
| 149 | }); |
| 150 | }); |
| 151 | |
| 152 | const token = await exchangeCodeForToken(code, verifier); |
no test coverage detected