fix
This commit is contained in:
@@ -253,6 +253,24 @@ function setupAskAIEventListeners() {
|
|||||||
handleAIResponse(event.detail);
|
handleAIResponse(event.detail);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Listen for OAuth callback completion from iframe
|
||||||
|
window.addEventListener('message', function(event) {
|
||||||
|
if (event.data.type === 'oauth_success') {
|
||||||
|
console.log('Received OAuth success message:', event.data);
|
||||||
|
|
||||||
|
// Close any OAuth popups/iframes
|
||||||
|
const oauthFrame = document.getElementById('oauth-frame');
|
||||||
|
if (oauthFrame) {
|
||||||
|
oauthFrame.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reload the page to refresh OAuth app state
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.reload();
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Track IME composition state
|
// Track IME composition state
|
||||||
let isComposing = false;
|
let isComposing = false;
|
||||||
const aiQuestionInput = document.getElementById('aiQuestion');
|
const aiQuestionInput = document.getElementById('aiQuestion');
|
||||||
@@ -284,52 +302,8 @@ function setupAskAIEventListeners() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// OAuth Callback handling
|
|
||||||
function handleOAuthCallback() {
|
|
||||||
// Check if we're on the callback page
|
|
||||||
if (window.location.pathname === '/oauth/callback') {
|
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
|
||||||
const code = urlParams.get('code');
|
|
||||||
const error = urlParams.get('error');
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
console.error('OAuth error:', error);
|
|
||||||
// Redirect to home page with error
|
|
||||||
setTimeout(() => {
|
|
||||||
window.location.href = '/?oauth_error=' + encodeURIComponent(error);
|
|
||||||
}, 1000);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (code) {
|
|
||||||
console.log('OAuth callback successful, code received');
|
|
||||||
|
|
||||||
// Get the original page from localStorage or use home page
|
|
||||||
const originalPage = localStorage.getItem('oauth_original_page') || '/';
|
|
||||||
localStorage.removeItem('oauth_original_page');
|
|
||||||
|
|
||||||
// Wait a bit for OAuth app to process the callback
|
|
||||||
setTimeout(() => {
|
|
||||||
console.log('Redirecting back to:', originalPage);
|
|
||||||
window.location.href = originalPage;
|
|
||||||
}, 2000);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store current page before OAuth if we're not on callback page
|
|
||||||
if (window.location.pathname !== '/oauth/callback') {
|
|
||||||
localStorage.setItem('oauth_original_page', window.location.href);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize Ask AI when DOM is loaded
|
// Initialize Ask AI when DOM is loaded
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
// Handle OAuth callback first
|
|
||||||
handleOAuthCallback();
|
|
||||||
|
|
||||||
// Then initialize Ask AI
|
|
||||||
setupAskAIEventListeners();
|
setupAskAIEventListeners();
|
||||||
console.log('Ask AI initialized successfully');
|
console.log('Ask AI initialized successfully');
|
||||||
});
|
});
|
||||||
|
@@ -44,7 +44,7 @@ body {
|
|||||||
|
|
||||||
.oauth-header-content {
|
.oauth-header-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-start;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
max-width: 800px;
|
max-width: 800px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
@@ -62,7 +62,74 @@ body {
|
|||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 100%;
|
}
|
||||||
|
|
||||||
|
/* OAuth User Profile in Header */
|
||||||
|
.oauth-user-profile {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-avatar-section {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-avatar-section .profile-avatar {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
border: 2px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-avatar-fallback {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--background-secondary);
|
||||||
|
border: 2px solid var(--border);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-info {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-display-name {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--text);
|
||||||
|
margin-bottom: 2px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-handle {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-bottom: 2px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-did {
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
opacity: 0.7;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Buttons */
|
/* Buttons */
|
||||||
@@ -647,6 +714,41 @@ body {
|
|||||||
.chat-container {
|
.chat-container {
|
||||||
height: 300px;
|
height: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* OAuth User Profile Mobile */
|
||||||
|
.oauth-user-profile {
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-avatar-section .profile-avatar,
|
||||||
|
.profile-avatar-fallback {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-display-name {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-handle {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-did {
|
||||||
|
font-size: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.oauth-header-content {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.oauth-header-actions {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Avatar Styles */
|
/* Avatar Styles */
|
||||||
|
@@ -69,8 +69,32 @@ export default function App() {
|
|||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<div style={{ padding: '20px', textAlign: 'center' }}>
|
<div style={{
|
||||||
<p>読み込み中...</p>
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
minHeight: '200px',
|
||||||
|
padding: '40px',
|
||||||
|
textAlign: 'center'
|
||||||
|
}}>
|
||||||
|
<div style={{
|
||||||
|
width: '40px',
|
||||||
|
height: '40px',
|
||||||
|
border: '4px solid #f3f3f3',
|
||||||
|
borderTop: '4px solid #667eea',
|
||||||
|
borderRadius: '50%',
|
||||||
|
animation: 'spin 1s linear infinite',
|
||||||
|
marginBottom: '16px'
|
||||||
|
}} />
|
||||||
|
<p style={{ color: '#666', margin: 0 }}>読み込み中...</p>
|
||||||
|
|
||||||
|
<style jsx>{`
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -115,6 +139,34 @@ export default function App() {
|
|||||||
<div className="app">
|
<div className="app">
|
||||||
<header className="oauth-app-header">
|
<header className="oauth-app-header">
|
||||||
<div className="oauth-header-content">
|
<div className="oauth-header-content">
|
||||||
|
{user && (
|
||||||
|
<div className="oauth-user-profile">
|
||||||
|
<div className="profile-avatar-section">
|
||||||
|
{user.avatar ? (
|
||||||
|
<img
|
||||||
|
src={user.avatar}
|
||||||
|
alt={user.displayName || user.handle}
|
||||||
|
className="profile-avatar"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="profile-avatar-fallback">
|
||||||
|
{(user.displayName || user.handle || '?').charAt(0).toUpperCase()}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="profile-info">
|
||||||
|
<div className="profile-display-name">
|
||||||
|
{user.displayName || user.handle}
|
||||||
|
</div>
|
||||||
|
<div className="profile-handle">
|
||||||
|
@{user.handle}
|
||||||
|
</div>
|
||||||
|
<div className="profile-did">
|
||||||
|
{user.did}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="oauth-header-actions">
|
<div className="oauth-header-actions">
|
||||||
<AuthButton
|
<AuthButton
|
||||||
user={user}
|
user={user}
|
||||||
|
@@ -1,54 +1,36 @@
|
|||||||
import React, { useEffect, useState } from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
export default function OAuthCallback({ onAuthSuccess }) {
|
|
||||||
const [status, setStatus] = useState('OAuth認証処理中...')
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
handleCallback()
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const handleCallback = async () => {
|
|
||||||
try {
|
|
||||||
// BrowserOAuthClientが自動的にコールバックを処理します
|
|
||||||
// URLのパラメータを確認して成功を通知
|
|
||||||
const urlParams = new URLSearchParams(window.location.search)
|
|
||||||
const code = urlParams.get('code')
|
|
||||||
const error = urlParams.get('error')
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
throw new Error(`OAuth error: ${error}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (code) {
|
|
||||||
setStatus('認証成功!元のページに戻ります...')
|
|
||||||
|
|
||||||
// Get the referring page or use root
|
|
||||||
const referrer = document.referrer || window.location.origin
|
|
||||||
const returnUrl = referrer.includes('/oauth/callback') ? window.location.origin : referrer
|
|
||||||
|
|
||||||
// 少し待ってから元のページにリダイレクト
|
|
||||||
setTimeout(() => {
|
|
||||||
window.location.href = returnUrl
|
|
||||||
}, 1500)
|
|
||||||
} else {
|
|
||||||
setStatus('認証情報が見つかりません')
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Callback error:', error)
|
|
||||||
setStatus('認証エラー: ' + error.message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
export default function OAuthCallback() {
|
||||||
return (
|
return (
|
||||||
<div style={{ padding: '20px', textAlign: 'center' }}>
|
<div style={{
|
||||||
<h2>OAuth認証</h2>
|
display: 'flex',
|
||||||
<p>{status}</p>
|
flexDirection: 'column',
|
||||||
{status.includes('エラー') && (
|
alignItems: 'center',
|
||||||
<button onClick={() => window.location.href = '/'}>
|
justifyContent: 'center',
|
||||||
メインページに戻る
|
minHeight: '50vh',
|
||||||
</button>
|
padding: '40px',
|
||||||
)}
|
textAlign: 'center'
|
||||||
|
}}>
|
||||||
|
<div style={{
|
||||||
|
width: '50px',
|
||||||
|
height: '50px',
|
||||||
|
border: '4px solid #f3f3f3',
|
||||||
|
borderTop: '4px solid #667eea',
|
||||||
|
borderRadius: '50%',
|
||||||
|
animation: 'spin 1s linear infinite',
|
||||||
|
marginBottom: '20px'
|
||||||
|
}} />
|
||||||
|
<h2 style={{ color: '#333', marginBottom: '12px' }}>OAuth認証処理中...</h2>
|
||||||
|
<p style={{ color: '#666', fontSize: '14px' }}>
|
||||||
|
認証が完了しましたら自動で元のページに戻ります
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<style jsx>{`
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
@@ -18,6 +18,32 @@ export function useAuth() {
|
|||||||
if (authResult) {
|
if (authResult) {
|
||||||
setUser(authResult.user)
|
setUser(authResult.user)
|
||||||
setAgent(authResult.agent)
|
setAgent(authResult.agent)
|
||||||
|
|
||||||
|
// If we're on callback page and authentication succeeded, notify parent
|
||||||
|
if (window.location.pathname === '/oauth/callback') {
|
||||||
|
console.log('OAuth callback completed, notifying parent window')
|
||||||
|
|
||||||
|
// Get referrer or use stored return URL
|
||||||
|
const returnUrl = sessionStorage.getItem('oauth_return_url') ||
|
||||||
|
document.referrer ||
|
||||||
|
window.location.origin
|
||||||
|
|
||||||
|
sessionStorage.removeItem('oauth_return_url')
|
||||||
|
|
||||||
|
// Notify parent window if in iframe, otherwise redirect directly
|
||||||
|
if (window.parent !== window) {
|
||||||
|
window.parent.postMessage({
|
||||||
|
type: 'oauth_success',
|
||||||
|
returnUrl: returnUrl,
|
||||||
|
user: authResult.user
|
||||||
|
}, '*')
|
||||||
|
} else {
|
||||||
|
// Direct redirect
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.href = returnUrl
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Auth initialization failed:', error)
|
console.error('Auth initialization failed:', error)
|
||||||
@@ -27,6 +53,11 @@ export function useAuth() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const login = async (handle) => {
|
const login = async (handle) => {
|
||||||
|
// Store current page URL for post-auth redirect
|
||||||
|
if (window.location.pathname !== '/oauth/callback') {
|
||||||
|
sessionStorage.setItem('oauth_return_url', window.location.href)
|
||||||
|
}
|
||||||
|
|
||||||
await oauthService.login(handle)
|
await oauthService.login(handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -65,6 +65,8 @@ export class OAuthService {
|
|||||||
async processSession(session) {
|
async processSession(session) {
|
||||||
const did = session.sub || session.did
|
const did = session.sub || session.did
|
||||||
let handle = session.handle || 'unknown'
|
let handle = session.handle || 'unknown'
|
||||||
|
let displayName = null
|
||||||
|
let avatar = null
|
||||||
|
|
||||||
// Create Agent directly with session (per official docs)
|
// Create Agent directly with session (per official docs)
|
||||||
try {
|
try {
|
||||||
@@ -77,21 +79,40 @@ export class OAuthService {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
this.sessionInfo = { did, handle }
|
// Get profile information using authenticated agent
|
||||||
|
if (this.agent) {
|
||||||
// Resolve handle if missing
|
|
||||||
if (handle === 'unknown' && this.agent) {
|
|
||||||
try {
|
try {
|
||||||
await new Promise(resolve => setTimeout(resolve, 300))
|
await new Promise(resolve => setTimeout(resolve, 300))
|
||||||
const profile = await this.agent.getProfile({ actor: did })
|
const profile = await this.agent.getProfile({ actor: did })
|
||||||
handle = profile.data.handle
|
handle = profile.data.handle || handle
|
||||||
this.sessionInfo.handle = handle
|
displayName = profile.data.displayName || null
|
||||||
|
avatar = profile.data.avatar || null
|
||||||
|
|
||||||
|
console.log('Profile fetched from session:', {
|
||||||
|
did,
|
||||||
|
handle,
|
||||||
|
displayName,
|
||||||
|
avatar: avatar ? 'present' : 'none'
|
||||||
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('Failed to resolve handle:', error)
|
console.log('Failed to get profile from session:', error)
|
||||||
|
// Keep the basic info we have
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { did, handle }
|
this.sessionInfo = {
|
||||||
|
did,
|
||||||
|
handle,
|
||||||
|
displayName,
|
||||||
|
avatar
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
did,
|
||||||
|
handle,
|
||||||
|
displayName,
|
||||||
|
avatar
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async login(handle) {
|
async login(handle) {
|
||||||
|
Reference in New Issue
Block a user