wamw Friday I/O 6回目
株式会社ワムウでは、毎週金曜日は 興味がある事柄に取り組み、その成果を何らかの形でアウトプットする日としています。
今日は時間がなかったので、メモです。
はじめに
下記コードは既存コードからメモ用に抜き出し、書き換えたものです。
これがそのまま完動するわけではないので、あくまで処理の流れをメモったものとなります。
前提
レンダラプロセスから何らかのアクションにてメインプロセスへ connect-to-github
を送るとする。
その際メインプロセス側で下記のような形でアクセストークンを取得したい。
// in main process
ipcMain.on('connect-to-github', async (event: Electron.Event) => {
const credentials: GithubOAuthCredentials = {
scopes: ['user', 'public_repo', 'repo'],
client_id: process.env.GITHUB_CLIENT_ID || '',
client_secret: process.env.GITHUB_CLIENT_SECRET || ''
}
const token: string = await getToken(credentials)
event.sender.send('github-connected', token)
})
GithubOAuthCredentials
はどこかで適当に。
interface GithubOAuthCredentials {
scopes: string[]
client_id: string
client_secret: string
}
アクセストークン取得方法
アクセストークンを取得するためには code
を取得する必要がある。
通常のWebアプリケーションの場合、アプリケーションの画面上でOAuth認証画面へ遷移し、認証後にリダイレクトURIへ遷移し返ってくる、という挙動から問題はないのだが、Electronの場合リダイレクトURIなどというものが存在しないため困ることになる。
これを解決するために「リダイレクトした、もしくはどこかの画面に遷移した際URIに code
が存在したらその値を取得しウィンドウを閉じる」などの動きを作る必要がある。
上記の getToken
は下記のような形だとする。
export async function getToken(credentials: GithubOAuthCredentials): Promise<string> {
const code = await getCode(credentials)
const uri = `${accessTokenUri}?code=${code}&client_id=${credentials.client_id}&client_secret=${credentials.client_secret}`
const headers = { 'Accept': 'application/json' }
const response = await fetch(uri, { headers })
const json = await response.json()
const accessToken: string = json.access_token
return accessToken
}
この場合の getCode
は下記のような動きとなる。
createOAuthWindow
は適当なウィンドウを作成する関数。
getCode
が要求された場合、新規ウィンドウを立ち上げてそのウィンドウ上でGithubの認証を通す。
認証が通った後、リダイレクトがなされた場合、もしくは認証済みの場合単に遷移するのでその code
を取得し、開いたウィンドウを閉じる。
const oauthWindow = Electron.BrowserWindow | null
function getCode(credentials: GithubOAuthCredentials): Promise<string> {
return new Promise((resolve, reject) => {
if (!oauthWindow || oauthWindow.isDestroyed) {
oauthWindow = createOAuthWindow()
oauthWindow.on('close', () => {
oauthWindow = null
})
oauthWindow.webContents.on('did-get-redirect-request', (event: Electron.Event, oldUrl: string, newUrl: string): void => {
const code = extractCode(newUrl)
if (code) {
oauthWindow.destroy()
}
resolve(code)
})
oauthWindow.webContents.on('will-navigate', (event: Electron.Event, url: string): void => {
const code = extractCode(url)
if (code) {
oauthWindow.destroy()
}
resolve(code)
})
}
const uri = `${authorizeUri}?client_id=${credentials.client_id}&scope=${credentials.scopes}`
oauthWindow.loadURL(uri)
oauthWindow.show()
})
}
extractCode
はこんな感じ。
function extractCode(url: string): string {
const rawCode: RegExpExecArray | null = /code=([^&]*)/.exec(url) || null
const code: string = (rawCode && rawCode.length > 1) ? rawCode[1] : ''
const error: RegExpExecArray | null = /\?error=(.+)$/.exec(url) || null
if (error) {
throw new Error(error[1])
}
return code
}