51 lines
1.5 KiB
JavaScript
51 lines
1.5 KiB
JavaScript
const DEVICE_CODE_URL = 'https://github.com/login/device/code';
|
|
const ACCESS_TOKEN_URL = 'https://github.com/login/oauth/access_token';
|
|
const DEVICE_GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:device_code';
|
|
|
|
const encodeForm = (params) => {
|
|
const body = new URLSearchParams();
|
|
for (const [key, value] of Object.entries(params)) {
|
|
if (value == null) continue;
|
|
body.set(key, String(value));
|
|
}
|
|
return body.toString();
|
|
};
|
|
|
|
async function postForm(url, params) {
|
|
const response = await fetch(url, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
Accept: 'application/json',
|
|
},
|
|
body: encodeForm(params),
|
|
});
|
|
|
|
const payload = await response.json().catch(() => null);
|
|
if (!response.ok) {
|
|
const message = payload?.error_description || payload?.error || response.statusText;
|
|
const error = new Error(message || 'GitHub request failed');
|
|
error.status = response.status;
|
|
error.payload = payload;
|
|
throw error;
|
|
}
|
|
return payload;
|
|
}
|
|
|
|
export async function startDeviceFlow({ clientId, scope }) {
|
|
return postForm(DEVICE_CODE_URL, {
|
|
client_id: clientId,
|
|
scope,
|
|
});
|
|
}
|
|
|
|
export async function exchangeDeviceCode({ clientId, deviceCode }) {
|
|
// GitHub returns 200 with {error: 'authorization_pending'|...} for non-success states.
|
|
const payload = await postForm(ACCESS_TOKEN_URL, {
|
|
client_id: clientId,
|
|
device_code: deviceCode,
|
|
grant_type: DEVICE_GRANT_TYPE,
|
|
});
|
|
return payload;
|
|
}
|