197 lines
6.6 KiB
JavaScript
197 lines
6.6 KiB
JavaScript
(function attachBackgroundKiroDesktopClient(root, factory) {
|
|
root.MultiPageBackgroundKiroDesktopClient = factory();
|
|
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundKiroDesktopClientModule() {
|
|
const DEFAULT_REGION = 'us-east-1';
|
|
const DEFAULT_START_URL = 'https://view.awsapps.com/start';
|
|
const DEFAULT_SCOPES = Object.freeze([
|
|
'codewhisperer:completions',
|
|
'codewhisperer:analysis',
|
|
'codewhisperer:conversations',
|
|
'codewhisperer:transformations',
|
|
'codewhisperer:taskassist',
|
|
]);
|
|
|
|
function cleanString(value = '') {
|
|
return String(value ?? '').trim();
|
|
}
|
|
|
|
function normalizeRegion(value = '', fallback = DEFAULT_REGION) {
|
|
return cleanString(value) || fallback;
|
|
}
|
|
|
|
function buildOidcBaseUrl(region = DEFAULT_REGION) {
|
|
return `https://oidc.${normalizeRegion(region)}.amazonaws.com`;
|
|
}
|
|
|
|
async function readResponse(response) {
|
|
const text = await response.text();
|
|
let json = null;
|
|
try {
|
|
json = text ? JSON.parse(text) : null;
|
|
} catch (_error) {
|
|
json = null;
|
|
}
|
|
return { text, json };
|
|
}
|
|
|
|
async function sha256Bytes(input) {
|
|
const encoder = new TextEncoder();
|
|
return new Uint8Array(await crypto.subtle.digest('SHA-256', encoder.encode(String(input || ''))));
|
|
}
|
|
|
|
function base64UrlEncode(bytes) {
|
|
let binary = '';
|
|
for (const byte of bytes) {
|
|
binary += String.fromCharCode(byte);
|
|
}
|
|
return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
|
|
}
|
|
|
|
async function sha256Hex(input) {
|
|
const bytes = await sha256Bytes(input);
|
|
return Array.from(bytes).map((byte) => byte.toString(16).padStart(2, '0')).join('');
|
|
}
|
|
|
|
function randomUrlSafeString(length = 64) {
|
|
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';
|
|
const size = Math.max(32, Math.floor(Number(length) || 64));
|
|
const bytes = new Uint8Array(size);
|
|
crypto.getRandomValues(bytes);
|
|
let output = '';
|
|
for (let index = 0; index < size; index += 1) {
|
|
output += alphabet[bytes[index] % alphabet.length];
|
|
}
|
|
return output;
|
|
}
|
|
|
|
async function generatePkcePair() {
|
|
const codeVerifier = randomUrlSafeString(64);
|
|
const codeChallenge = base64UrlEncode(await sha256Bytes(codeVerifier));
|
|
return {
|
|
codeVerifier,
|
|
codeChallenge,
|
|
};
|
|
}
|
|
|
|
function chooseRedirectPort() {
|
|
return 49152 + Math.floor(Math.random() * 16384);
|
|
}
|
|
|
|
function buildRedirectUri(port) {
|
|
const normalizedPort = Math.max(1, Math.floor(Number(port) || 0));
|
|
if (!normalizedPort) {
|
|
throw new Error('缺少桌面授权回调端口。');
|
|
}
|
|
return `http://127.0.0.1:${normalizedPort}/oauth/callback`;
|
|
}
|
|
|
|
async function registerDesktopClient(params = {}, fetchImpl) {
|
|
if (typeof fetchImpl !== 'function') {
|
|
throw new Error('registerDesktopClient requires fetch support.');
|
|
}
|
|
const region = normalizeRegion(params.region);
|
|
const oidcBaseUrl = buildOidcBaseUrl(region);
|
|
const response = await fetchImpl(`${oidcBaseUrl}/client/register`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
clientName: cleanString(params.clientName) || 'Kiro IDE',
|
|
clientType: 'public',
|
|
scopes: Array.isArray(params.scopes) && params.scopes.length ? params.scopes : DEFAULT_SCOPES,
|
|
grantTypes: ['authorization_code', 'refresh_token'],
|
|
redirectUris: ['http://127.0.0.1/oauth/callback'],
|
|
issuerUrl: cleanString(params.issuerUrl) || DEFAULT_START_URL,
|
|
}),
|
|
});
|
|
const body = await readResponse(response);
|
|
if (!response.ok) {
|
|
throw new Error(`Kiro 桌面客户端注册失败:${cleanString(body.text || response.statusText) || response.status}`);
|
|
}
|
|
|
|
const clientId = cleanString(body.json?.clientId);
|
|
const clientSecret = String(body.json?.clientSecret || '');
|
|
if (!clientId || !clientSecret) {
|
|
throw new Error('Kiro 桌面客户端注册响应缺少 clientId 或 clientSecret。');
|
|
}
|
|
|
|
return {
|
|
region,
|
|
clientId,
|
|
clientSecret,
|
|
clientSecretExpiresAt: Number(body.json?.clientSecretExpiresAt || 0) || 0,
|
|
clientIdHash: await sha256Hex(JSON.stringify({
|
|
startUrl: cleanString(params.issuerUrl) || DEFAULT_START_URL,
|
|
clientId,
|
|
})),
|
|
};
|
|
}
|
|
|
|
function buildAuthorizeUrl(params = {}) {
|
|
const region = normalizeRegion(params.region);
|
|
const search = new URLSearchParams();
|
|
search.set('response_type', 'code');
|
|
search.set('client_id', cleanString(params.clientId));
|
|
search.set('redirect_uri', cleanString(params.redirectUri));
|
|
search.set('scopes', Array.isArray(params.scopes) && params.scopes.length
|
|
? params.scopes.join(',')
|
|
: DEFAULT_SCOPES.join(','));
|
|
search.set('state', cleanString(params.state));
|
|
search.set('code_challenge', cleanString(params.codeChallenge));
|
|
search.set('code_challenge_method', 'S256');
|
|
return `${buildOidcBaseUrl(region)}/authorize?${search.toString()}`;
|
|
}
|
|
|
|
async function exchangeDesktopAuthorizationCode(params = {}, fetchImpl) {
|
|
if (typeof fetchImpl !== 'function') {
|
|
throw new Error('exchangeDesktopAuthorizationCode requires fetch support.');
|
|
}
|
|
const region = normalizeRegion(params.region);
|
|
const response = await fetchImpl(`${buildOidcBaseUrl(region)}/token`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
clientId: cleanString(params.clientId),
|
|
clientSecret: String(params.clientSecret || ''),
|
|
grantType: 'authorization_code',
|
|
code: cleanString(params.code),
|
|
redirectUri: cleanString(params.redirectUri),
|
|
codeVerifier: cleanString(params.codeVerifier),
|
|
}),
|
|
});
|
|
const body = await readResponse(response);
|
|
if (!response.ok) {
|
|
throw new Error(`Kiro 桌面授权换取 Token 失败:${cleanString(body.text || response.statusText) || response.status}`);
|
|
}
|
|
|
|
const accessToken = String(body.json?.accessToken || '');
|
|
const refreshToken = String(body.json?.refreshToken || '');
|
|
if (!accessToken || !refreshToken) {
|
|
throw new Error('Kiro 桌面授权换取 Token 响应缺少 accessToken 或 refreshToken。');
|
|
}
|
|
|
|
return {
|
|
accessToken,
|
|
refreshToken,
|
|
expiresIn: Number(body.json?.expiresIn || 0) || 0,
|
|
tokenType: cleanString(body.json?.tokenType),
|
|
region,
|
|
};
|
|
}
|
|
|
|
return {
|
|
DEFAULT_REGION,
|
|
DEFAULT_SCOPES,
|
|
DEFAULT_START_URL,
|
|
buildAuthorizeUrl,
|
|
buildRedirectUri,
|
|
chooseRedirectPort,
|
|
exchangeDesktopAuthorizationCode,
|
|
generatePkcePair,
|
|
registerDesktopClient,
|
|
};
|
|
});
|