Fix capacitor build: Implement proper backend connectivity and authentication
- Update BACKEND_BASE_URL to plugincompass.com:9445 - Add comprehensive authentication system (login, signup, logout) - Implement JWT token management with automatic refresh - Add plugin/app management API (CRUD operations) - Add session management for chat/builder - Update mobile UI with real backend integration - Add user account and usage tracking - Enable plugin loading and editing from backend - Fix module type for ES6 support
This commit is contained in:
@@ -1,4 +1,15 @@
|
||||
const BACKEND_BASE_URL = window.BACKEND_BASE_URL || 'https://api.example.com';
|
||||
// Capacitor Bridge for Plugin Compass Mobile App
|
||||
// Connects to backend at plugincompass.com:9445
|
||||
|
||||
const BACKEND_BASE_URL = window.BACKEND_BASE_URL || 'https://plugincompass.com:9445';
|
||||
|
||||
// Storage keys
|
||||
const STORAGE_KEYS = {
|
||||
USER: 'plugin_compass_user',
|
||||
TOKEN: 'plugin_compass_token',
|
||||
REFRESH_TOKEN: 'plugin_compass_refresh_token',
|
||||
SESSION: 'plugin_compass_session'
|
||||
};
|
||||
|
||||
let PreferencesImpl = null;
|
||||
|
||||
@@ -58,31 +69,378 @@ export const Preferences = {
|
||||
}
|
||||
};
|
||||
|
||||
// Token management
|
||||
async function getAuthToken() {
|
||||
try {
|
||||
const { value } = await Preferences.get({ key: STORAGE_KEYS.TOKEN });
|
||||
return value;
|
||||
} catch (e) {
|
||||
console.error('[AUTH] Failed to get token:', e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function setAuthToken(token) {
|
||||
try {
|
||||
await Preferences.set({ key: STORAGE_KEYS.TOKEN, value: token });
|
||||
} catch (e) {
|
||||
console.error('[AUTH] Failed to set token:', e);
|
||||
}
|
||||
}
|
||||
|
||||
async function getRefreshToken() {
|
||||
try {
|
||||
const { value } = await Preferences.get({ key: STORAGE_KEYS.REFRESH_TOKEN });
|
||||
return value;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function setRefreshToken(token) {
|
||||
try {
|
||||
await Preferences.set({ key: STORAGE_KEYS.REFRESH_TOKEN, value: token });
|
||||
} catch (e) {
|
||||
console.error('[AUTH] Failed to set refresh token:', e);
|
||||
}
|
||||
}
|
||||
|
||||
async function clearAuth() {
|
||||
try {
|
||||
await Preferences.remove({ key: STORAGE_KEYS.TOKEN });
|
||||
await Preferences.remove({ key: STORAGE_KEYS.REFRESH_TOKEN });
|
||||
await Preferences.remove({ key: STORAGE_KEYS.USER });
|
||||
await Preferences.remove({ key: STORAGE_KEYS.SESSION });
|
||||
} catch (e) {
|
||||
console.error('[AUTH] Failed to clear auth:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// API client with authentication
|
||||
async function apiClient(endpoint, options = {}) {
|
||||
const url = endpoint.startsWith('http') ? endpoint : `${BACKEND_BASE_URL}${endpoint}`;
|
||||
|
||||
const token = await getAuthToken();
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
...(token ? { 'Authorization': `Bearer ${token}` } : {}),
|
||||
...(options.headers || {})
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
...options,
|
||||
headers,
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
// Handle token expiration
|
||||
if (response.status === 401) {
|
||||
const refreshed = await refreshAccessToken();
|
||||
if (refreshed) {
|
||||
// Retry with new token
|
||||
const newToken = await getAuthToken();
|
||||
headers['Authorization'] = `Bearer ${newToken}`;
|
||||
const retryResponse = await fetch(url, {
|
||||
...options,
|
||||
headers,
|
||||
credentials: 'include'
|
||||
});
|
||||
return handleResponse(retryResponse);
|
||||
} else {
|
||||
// Token refresh failed, clear auth
|
||||
await clearAuth();
|
||||
throw new Error('Session expired. Please sign in again.');
|
||||
}
|
||||
}
|
||||
|
||||
return handleResponse(response);
|
||||
} catch (error) {
|
||||
console.error(`[API] Request failed: ${endpoint}`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleResponse(response) {
|
||||
const text = await response.text();
|
||||
let json;
|
||||
try {
|
||||
json = text ? JSON.parse(text) : {};
|
||||
} catch (parseErr) {
|
||||
throw new Error(`Invalid JSON response: ${parseErr.message}`);
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
const err = new Error(json.error || json.message || `HTTP ${response.status}`);
|
||||
err.status = response.status;
|
||||
err.data = json;
|
||||
throw err;
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
async function refreshAccessToken() {
|
||||
try {
|
||||
const refreshToken = await getRefreshToken();
|
||||
if (!refreshToken) return false;
|
||||
|
||||
const response = await fetch(`${BACKEND_BASE_URL}/api/auth/refresh`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ refreshToken })
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
if (data.token) {
|
||||
await setAuthToken(data.token);
|
||||
if (data.refreshToken) {
|
||||
await setRefreshToken(data.refreshToken);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (e) {
|
||||
console.error('[AUTH] Token refresh failed:', e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Authentication functions
|
||||
export async function loginWithEmail(email, password, remember = false) {
|
||||
try {
|
||||
const response = await apiClient('/api/login', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ email, password, remember })
|
||||
});
|
||||
|
||||
if (response.ok && response.user && response.token) {
|
||||
// Store tokens
|
||||
await setAuthToken(response.token);
|
||||
if (response.refreshToken) {
|
||||
await setRefreshToken(response.refreshToken);
|
||||
}
|
||||
|
||||
// Store user info
|
||||
await Preferences.set({
|
||||
key: STORAGE_KEYS.USER,
|
||||
value: JSON.stringify(response.user)
|
||||
});
|
||||
|
||||
return { success: true, user: response.user };
|
||||
} else {
|
||||
return { success: false, error: response.error || 'Login failed' };
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[AUTH] Login error:', error);
|
||||
return { success: false, error: error.message || 'Login failed' };
|
||||
}
|
||||
}
|
||||
|
||||
export async function signupWithEmail(email, password) {
|
||||
try {
|
||||
const response = await apiClient('/api/signup', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ email, password })
|
||||
});
|
||||
|
||||
if (response.ok && response.user && response.token) {
|
||||
await setAuthToken(response.token);
|
||||
if (response.refreshToken) {
|
||||
await setRefreshToken(response.refreshToken);
|
||||
}
|
||||
|
||||
await Preferences.set({
|
||||
key: STORAGE_KEYS.USER,
|
||||
value: JSON.stringify(response.user)
|
||||
});
|
||||
|
||||
return { success: true, user: response.user };
|
||||
} else {
|
||||
return { success: false, error: response.error || 'Signup failed' };
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[AUTH] Signup error:', error);
|
||||
return { success: false, error: error.message || 'Signup failed' };
|
||||
}
|
||||
}
|
||||
|
||||
export async function logout() {
|
||||
try {
|
||||
// Call logout endpoint if available
|
||||
await apiClient('/api/logout', {
|
||||
method: 'POST',
|
||||
headers: {}
|
||||
}).catch(() => {});
|
||||
} finally {
|
||||
await clearAuth();
|
||||
}
|
||||
}
|
||||
|
||||
export async function getCurrentUser() {
|
||||
try {
|
||||
// First check stored user
|
||||
const { value } = await Preferences.get({ key: STORAGE_KEYS.USER });
|
||||
if (value) {
|
||||
const user = JSON.parse(value);
|
||||
|
||||
// Verify token is still valid by fetching fresh user data
|
||||
try {
|
||||
const freshUser = await apiClient('/api/me');
|
||||
if (freshUser) {
|
||||
await Preferences.set({
|
||||
key: STORAGE_KEYS.USER,
|
||||
value: JSON.stringify(freshUser)
|
||||
});
|
||||
return freshUser;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[AUTH] Could not refresh user data:', e.message);
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
return null;
|
||||
} catch (e) {
|
||||
console.error('[AUTH] Failed to get current user:', e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function isAuthenticated() {
|
||||
const token = await getAuthToken();
|
||||
if (!token) return false;
|
||||
|
||||
const user = await getCurrentUser();
|
||||
return !!user;
|
||||
}
|
||||
|
||||
// OAuth helpers
|
||||
export function getOAuthUrl(provider, redirectUri = null) {
|
||||
const params = new URLSearchParams();
|
||||
if (redirectUri) {
|
||||
params.set('redirect_uri', redirectUri);
|
||||
}
|
||||
params.set('mobile', 'true');
|
||||
return `${BACKEND_BASE_URL}/auth/${provider}?${params.toString()}`;
|
||||
}
|
||||
|
||||
export async function handleOAuthCallback(provider, code, state) {
|
||||
try {
|
||||
const response = await apiClient(`/auth/${provider}/callback`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ code, state })
|
||||
});
|
||||
|
||||
if (response.token) {
|
||||
await setAuthToken(response.token);
|
||||
if (response.refreshToken) {
|
||||
await setRefreshToken(response.refreshToken);
|
||||
}
|
||||
|
||||
if (response.user) {
|
||||
await Preferences.set({
|
||||
key: STORAGE_KEYS.USER,
|
||||
value: JSON.stringify(response.user)
|
||||
});
|
||||
}
|
||||
|
||||
return { success: true, user: response.user };
|
||||
} else {
|
||||
return { success: false, error: response.error || 'OAuth failed' };
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[AUTH] OAuth error:', error);
|
||||
return { success: false, error: error.message || 'OAuth failed' };
|
||||
}
|
||||
}
|
||||
|
||||
// Plugin/App management
|
||||
export async function getUserApps() {
|
||||
try {
|
||||
const apps = await apiClient('/api/apps');
|
||||
return { success: true, apps: apps || [] };
|
||||
} catch (error) {
|
||||
console.error('[APPS] Failed to load apps:', error);
|
||||
return { success: false, error: error.message, apps: [] };
|
||||
}
|
||||
}
|
||||
|
||||
export async function getApp(appId) {
|
||||
try {
|
||||
const app = await apiClient(`/api/apps/${appId}`);
|
||||
return { success: true, app };
|
||||
} catch (error) {
|
||||
console.error('[APPS] Failed to load app:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
export async function createApp(appData) {
|
||||
try {
|
||||
const app = await apiClient('/api/apps', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(appData)
|
||||
});
|
||||
return { success: true, app };
|
||||
} catch (error) {
|
||||
console.error('[APPS] Failed to create app:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateApp(appId, appData) {
|
||||
try {
|
||||
const app = await apiClient(`/api/apps/${appId}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(appData)
|
||||
});
|
||||
return { success: true, app };
|
||||
} catch (error) {
|
||||
console.error('[APPS] Failed to update app:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteApp(appId) {
|
||||
try {
|
||||
await apiClient(`/api/apps/${appId}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error('[APPS] Failed to delete app:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
export async function syncApp(appId) {
|
||||
if (!appId || appId.trim() === '') {
|
||||
throw new Error('appId is required');
|
||||
}
|
||||
|
||||
const { value } = await Preferences.get({ key: `app_${appId}` });
|
||||
if (!value) {
|
||||
throw new Error(`App not found: ${appId}`);
|
||||
try {
|
||||
// Get app from backend instead of local storage
|
||||
const result = await getApp(appId);
|
||||
if (!result.success || !result.app) {
|
||||
throw new Error(`App not found: ${appId}`);
|
||||
}
|
||||
|
||||
// Sync with backend
|
||||
const response = await apiClient('/desktop/apps/sync', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(result.app)
|
||||
});
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('[SYNC] Failed to sync app:', error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
const appData = JSON.parse(value);
|
||||
|
||||
const response = await fetch(`${BACKEND_BASE_URL}/desktop/apps/sync`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(appData),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Sync failed: ${response.status}`);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
export async function runOpencodeTask(appId, taskName, args = []) {
|
||||
@@ -96,8 +454,158 @@ export async function runOpencodeTask(appId, taskName, args = []) {
|
||||
throw new Error('OpenCode execution is not supported on mobile devices. Please use the desktop app for this feature.');
|
||||
}
|
||||
|
||||
// Session management
|
||||
export async function getSessions() {
|
||||
try {
|
||||
const sessions = await apiClient('/api/sessions');
|
||||
return { success: true, sessions: sessions || [] };
|
||||
} catch (error) {
|
||||
console.error('[SESSIONS] Failed to load sessions:', error);
|
||||
return { success: false, error: error.message, sessions: [] };
|
||||
}
|
||||
}
|
||||
|
||||
export async function getSession(sessionId) {
|
||||
try {
|
||||
const session = await apiClient(`/api/sessions/${sessionId}`);
|
||||
return { success: true, session };
|
||||
} catch (error) {
|
||||
console.error('[SESSIONS] Failed to load session:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
export async function createSession(sessionData) {
|
||||
try {
|
||||
const session = await apiClient('/api/sessions', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(sessionData)
|
||||
});
|
||||
return { success: true, session };
|
||||
} catch (error) {
|
||||
console.error('[SESSIONS] Failed to create session:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
export async function sendMessage(sessionId, messageData) {
|
||||
try {
|
||||
const result = await apiClient(`/api/sessions/${sessionId}/messages`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(messageData)
|
||||
});
|
||||
return { success: true, ...result };
|
||||
} catch (error) {
|
||||
console.error('[MESSAGES] Failed to send message:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
// User account
|
||||
export async function getUserAccount() {
|
||||
try {
|
||||
const account = await apiClient('/api/account');
|
||||
return { success: true, account };
|
||||
} catch (error) {
|
||||
console.error('[ACCOUNT] Failed to load account:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateUserAccount(accountData) {
|
||||
try {
|
||||
const account = await apiClient('/api/account', {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(accountData)
|
||||
});
|
||||
return { success: true, account };
|
||||
} catch (error) {
|
||||
console.error('[ACCOUNT] Failed to update account:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
export async function getUsageSummary() {
|
||||
try {
|
||||
const usage = await apiClient('/api/account/usage');
|
||||
return { success: true, usage };
|
||||
} catch (error) {
|
||||
console.error('[USAGE] Failed to load usage:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
// Legacy exports for backward compatibility
|
||||
window.nativeBridge = {
|
||||
Preferences,
|
||||
syncApp,
|
||||
runOpencodeTask
|
||||
runOpencodeTask,
|
||||
loginWithEmail,
|
||||
signupWithEmail,
|
||||
logout,
|
||||
getCurrentUser,
|
||||
isAuthenticated,
|
||||
getUserApps,
|
||||
getApp,
|
||||
createApp,
|
||||
updateApp,
|
||||
deleteApp,
|
||||
getSessions,
|
||||
createSession,
|
||||
sendMessage,
|
||||
getOAuthUrl,
|
||||
handleOAuthCallback,
|
||||
getUserAccount,
|
||||
updateUserAccount,
|
||||
getUsageSummary,
|
||||
apiClient
|
||||
};
|
||||
|
||||
// Expose all functions globally for the web UI
|
||||
Object.assign(window, {
|
||||
loginWithEmail,
|
||||
signupWithEmail,
|
||||
logout,
|
||||
getCurrentUser,
|
||||
isAuthenticated,
|
||||
getUserApps,
|
||||
getApp,
|
||||
createApp,
|
||||
updateApp,
|
||||
deleteApp,
|
||||
getSessions,
|
||||
getSession,
|
||||
createSession,
|
||||
sendMessage,
|
||||
getOAuthUrl,
|
||||
handleOAuthCallback,
|
||||
getUserAccount,
|
||||
updateUserAccount,
|
||||
getUsageSummary,
|
||||
apiClient
|
||||
});
|
||||
|
||||
export {
|
||||
loginWithEmail,
|
||||
signupWithEmail,
|
||||
logout,
|
||||
getCurrentUser,
|
||||
isAuthenticated,
|
||||
getUserApps,
|
||||
getApp,
|
||||
createApp,
|
||||
updateApp,
|
||||
deleteApp,
|
||||
getSessions,
|
||||
getSession,
|
||||
createSession,
|
||||
sendMessage,
|
||||
getOAuthUrl,
|
||||
handleOAuthCallback,
|
||||
getUserAccount,
|
||||
updateUserAccount,
|
||||
getUsageSummary,
|
||||
apiClient,
|
||||
BACKEND_BASE_URL,
|
||||
STORAGE_KEYS
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user