diff --git a/infrastructure/control-panel/package.json b/infrastructure/control-panel/package.json index 69874414..39e80d92 100644 --- a/infrastructure/control-panel/package.json +++ b/infrastructure/control-panel/package.json @@ -20,7 +20,7 @@ "@eslint/js": "^9.18.0", "@storybook/addon-svelte-csf": "^5.0.7", "@storybook/sveltekit": "^9.0.17", - "@sveltejs/adapter-static": "^3.0.8", + "@sveltejs/adapter-node": "^5.3.3", "@sveltejs/kit": "^2.22.0", "@sveltejs/vite-plugin-svelte": "^6.0.0", "@tailwindcss/vite": "^4.0.0", diff --git a/infrastructure/control-panel/src/lib/components/EVaultList.svelte b/infrastructure/control-panel/src/lib/components/EVaultList.svelte index 9916d3d3..0d141d0f 100644 --- a/infrastructure/control-panel/src/lib/components/EVaultList.svelte +++ b/infrastructure/control-panel/src/lib/components/EVaultList.svelte @@ -148,27 +148,27 @@ Name Namespace Status Age Service URL @@ -178,14 +178,14 @@ {#each evaults as evault} {evault.name} - + {evault.namespace} - + + {evault.age || 'Unknown'} - + {#if evault.serviceUrl}
-

{title}

+

{title}

- + | null = null; - private isInitialized = false; - - constructor() { - // Only initialize on the server side - if (typeof window === 'undefined') { - this.init(); - } - } - - private async init() { - if (this.isInitialized) return; - - try { - // Initialize LowDB with JSON file adapter - const adapter = new JSONFile(CACHE_FILE); - this.db = new Low(adapter, defaultData); - - // Load existing data or create default - await this.db.read(); - if (!this.db.data) { - this.db.data = defaultData; - await this.db.write(); - } - - this.isInitialized = true; - } catch (error) { - console.error('Failed to initialize cache service:', error); - } - } - - async getCachedEVaults(): Promise { - if (typeof window !== 'undefined') { - // In browser, try to get from localStorage as a simple cache - try { - const cached = localStorage.getItem('evault-cache'); - if (cached) { - const data = JSON.parse(cached); - if (data.evaults && Array.isArray(data.evaults)) { - return data.evaults; - } - } - } catch (error) { - console.log('No localStorage cache available'); - } - return []; - } - - await this.init(); - return this.db?.data?.evaults || []; - } - - async isCacheStale(): Promise { - if (typeof window !== 'undefined') { - return true; // Always stale in browser - } - - await this.init(); - const lastUpdated = this.db?.data?.lastUpdated || 0; - const now = Date.now(); - const fiveMinutes = 5 * 60 * 1000; // 5 minutes in milliseconds - - return (now - lastUpdated) > fiveMinutes; - } - - async updateCache(evaults: EVault[]): Promise { - if (typeof window !== 'undefined') { - // In browser, save to localStorage - try { - const cacheData = { - evaults, - lastUpdated: Date.now(), - isStale: false - }; - localStorage.setItem('evault-cache', JSON.stringify(cacheData)); - } catch (error) { - console.error('Failed to save to localStorage:', error); - } - return; - } - - await this.init(); - if (this.db) { - this.db.data = { - evaults, - lastUpdated: Date.now(), - isStale: false - }; - await this.db.write(); - } - } - - async markStale(): Promise { - if (typeof window !== 'undefined') { - return; // No-op in browser - } - - await this.init(); - if (this.db && this.db.data) { - this.db.data.isStale = true; - await this.db.write(); - } - } - - getCacheStatus(): { lastUpdated: number; isStale: boolean; itemCount: number } { - if (typeof window !== 'undefined') { - // In browser, get from localStorage - try { - const cached = localStorage.getItem('evault-cache'); - if (cached) { - const data = JSON.parse(cached); - return { - lastUpdated: data.lastUpdated || 0, - isStale: data.isStale || false, - itemCount: data.evaults?.length || 0 - }; - } - } catch (error) { - console.log('No localStorage cache available'); - } - return { lastUpdated: 0, isStale: true, itemCount: 0 }; - } - - if (!this.db?.data) { - return { lastUpdated: 0, isStale: true, itemCount: 0 }; - } - - return { - lastUpdated: this.db.data.lastUpdated, - isStale: this.db.data.isStale, - itemCount: this.db.data.evaults.length - }; - } - - async clearCache(): Promise { - if (typeof window !== 'undefined') { - // In browser, clear localStorage - try { - localStorage.removeItem('evault-cache'); - } catch (error) { - console.error('Failed to clear localStorage cache:', error); - } - return; - } - - await this.init(); - if (this.db) { - this.db.data = defaultData; - await this.db.write(); - } - } + private db: any | null = null; + private isInitialized = false; + + constructor() { + // Only initialize on the server side + if (typeof window === 'undefined') { + this.init(); + } + } + + private async init() { + if (this.isInitialized) return; + + try { + // Dynamically import lowdb only on server + const { Low } = await import('lowdb'); + const { JSONFile } = await import('lowdb/node'); + + // Initialize LowDB with JSON file adapter + const adapter = new JSONFile(CACHE_FILE); + this.db = new Low(adapter, defaultData); + + // Load existing data or create default + await this.db.read(); + if (!this.db.data) { + this.db.data = defaultData; + await this.db.write(); + } + + this.isInitialized = true; + } catch (error) { + console.error('Failed to initialize cache service:', error); + } + } + + async getCachedEVaults(): Promise { + if (typeof window !== 'undefined') { + // In browser, try to get from localStorage as a simple cache + try { + const cached = localStorage.getItem('evault-cache'); + if (cached) { + const data = JSON.parse(cached); + if (data.evaults && Array.isArray(data.evaults)) { + return data.evaults; + } + } + } catch (error) { + console.log('No localStorage cache available'); + } + return []; + } + + await this.init(); + return this.db?.data?.evaults || []; + } + + async isCacheStale(): Promise { + if (typeof window !== 'undefined') { + return true; // Always stale in browser + } + + await this.init(); + const lastUpdated = this.db?.data?.lastUpdated || 0; + const now = Date.now(); + const fiveMinutes = 5 * 60 * 1000; // 5 minutes in milliseconds + + return now - lastUpdated > fiveMinutes; + } + + async updateCache(evaults: EVault[]): Promise { + if (typeof window !== 'undefined') { + // In browser, save to localStorage + try { + const cacheData = { + evaults, + lastUpdated: Date.now(), + isStale: false + }; + localStorage.setItem('evault-cache', JSON.stringify(cacheData)); + } catch (error) { + console.error('Failed to save to localStorage:', error); + } + return; + } + + await this.init(); + if (this.db) { + this.db.data = { + evaults, + lastUpdated: Date.now(), + isStale: false + }; + await this.db.write(); + } + } + + async markStale(): Promise { + if (typeof window !== 'undefined') { + return; // No-op in browser + } + + await this.init(); + if (this.db && this.db.data) { + this.db.data.isStale = true; + await this.db.write(); + } + } + + getCacheStatus(): { lastUpdated: number; isStale: boolean; itemCount: number } { + if (typeof window !== 'undefined') { + // In browser, get from localStorage + try { + const cached = localStorage.getItem('evault-cache'); + if (cached) { + const data = JSON.parse(cached); + return { + lastUpdated: data.lastUpdated || 0, + isStale: data.isStale || false, + itemCount: data.evaults?.length || 0 + }; + } + } catch (error) { + console.log('No localStorage cache available'); + } + return { lastUpdated: 0, isStale: true, itemCount: 0 }; + } + + if (!this.db?.data) { + return { lastUpdated: 0, isStale: true, itemCount: 0 }; + } + + return { + lastUpdated: this.db.data.lastUpdated, + isStale: this.db.data.isStale, + itemCount: this.db.data.evaults.length + }; + } + + async clearCache(): Promise { + if (typeof window !== 'undefined') { + // In browser, clear localStorage + try { + localStorage.removeItem('evault-cache'); + } catch (error) { + console.error('Failed to clear localStorage cache:', error); + } + return; + } + + await this.init(); + if (this.db) { + this.db.data = defaultData; + await this.db.write(); + } + } } // Export a singleton instance diff --git a/infrastructure/control-panel/src/lib/services/evaultService.ts b/infrastructure/control-panel/src/lib/services/evaultService.ts index fbadda11..5e11675c 100644 --- a/infrastructure/control-panel/src/lib/services/evaultService.ts +++ b/infrastructure/control-panel/src/lib/services/evaultService.ts @@ -2,112 +2,116 @@ import type { EVault } from '../../routes/api/evaults/+server'; import { cacheService } from './cacheService'; export class EVaultService { - /** - * Get eVaults - load from cache first, then fetch fresh data - */ - static async getEVaults(): Promise { - // First, try to get cached data (fast) - let cachedData: EVault[] = []; - try { - cachedData = await cacheService.getCachedEVaults(); - } catch (error) { - console.log('No cached data available'); - } + /** + * Get eVaults - load from cache first, then fetch fresh data + */ + static async getEVaults(): Promise { + // First, try to get cached data (fast) + let cachedData: EVault[] = []; + try { + cachedData = await cacheService.getCachedEVaults(); + } catch (error) { + console.log('No cached data available'); + } - // Fire off fresh request in background (don't wait for it) - this.fetchFreshDataInBackground(); + // Fire off fresh request in background (don't wait for it) + this.fetchFreshDataInBackground(); - // Return cached data immediately (even if empty) - return cachedData; - } + // Return cached data immediately (even if empty) + return cachedData; + } - /** - * Force refresh - get fresh data and update cache - */ - static async forceRefresh(): Promise { - const freshData = await this.fetchEVaultsDirectly(); - await cacheService.updateCache(freshData); - return freshData; - } + /** + * Force refresh - get fresh data and update cache + */ + static async forceRefresh(): Promise { + const freshData = await this.fetchEVaultsDirectly(); + await cacheService.updateCache(freshData); + return freshData; + } - /** - * Get cache status information - */ - static getCacheStatus() { - return cacheService.getCacheStatus(); - } + /** + * Get cache status information + */ + static getCacheStatus() { + return cacheService.getCacheStatus(); + } - /** - * Clear the cache - */ - static async clearCache(): Promise { - await cacheService.clearCache(); - } + /** + * Clear the cache + */ + static async clearCache(): Promise { + await cacheService.clearCache(); + } - /** - * Fetch fresh data in background and update cache - */ - private static async fetchFreshDataInBackground(): Promise { - try { - const freshData = await this.fetchEVaultsDirectly(); - await cacheService.updateCache(freshData); - } catch (error) { - console.error('Background refresh failed:', error); - } - } + /** + * Fetch fresh data in background and update cache + */ + private static async fetchFreshDataInBackground(): Promise { + try { + const freshData = await this.fetchEVaultsDirectly(); + await cacheService.updateCache(freshData); + } catch (error) { + console.error('Background refresh failed:', error); + } + } - /** - * Fetch eVaults directly from API - */ - private static async fetchEVaultsDirectly(): Promise { - try { - const response = await fetch('/api/evaults'); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - const data = await response.json(); - // The backend returns { evaults: [...] } - return data.evaults || []; - } catch (error) { - console.error('Failed to fetch eVaults:', error); - throw error; - } - } + /** + * Fetch eVaults directly from API + */ + private static async fetchEVaultsDirectly(): Promise { + try { + const response = await fetch('/api/evaults'); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const data = await response.json(); + // The backend returns { evaults: [...] } + return data.evaults || []; + } catch (error) { + console.error('Failed to fetch eVaults:', error); + throw error; + } + } - /** - * Get logs for a specific eVault pod - */ - /** - * Get logs for a specific eVault pod - */ - static async getEVaultLogs(namespace: string, podName: string): Promise { - try { - const response = await fetch(`/api/evaults/${encodeURIComponent(namespace)}/${encodeURIComponent(podName)}/logs`); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - const data = await response.json(); - return data.logs || []; - } catch (error) { - console.error('Failed to fetch eVault logs:', error); - throw error; - } - } + /** + * Get logs for a specific eVault pod + */ + /** + * Get logs for a specific eVault pod + */ + static async getEVaultLogs(namespace: string, podName: string): Promise { + try { + const response = await fetch( + `/api/evaults/${encodeURIComponent(namespace)}/${encodeURIComponent(podName)}/logs` + ); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const data = await response.json(); + return data.logs || []; + } catch (error) { + console.error('Failed to fetch eVault logs:', error); + throw error; + } + } - /** - * Get metrics for a specific eVault pod - */ - static async getEVaultMetrics(namespace: string, podName: string): Promise { - try { - const response = await fetch(`/api/evaults/${encodeURIComponent(namespace)}/${encodeURIComponent(podName)}/metrics`); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - const data = await response.json(); - return data.metrics || {}; - } catch (error) { - console.error('Failed to fetch eVault metrics:', error); - throw error; - } - } + /** + * Get metrics for a specific eVault pod + */ + static async getEVaultMetrics(namespace: string, podName: string): Promise { + try { + const response = await fetch( + `/api/evaults/${encodeURIComponent(namespace)}/${encodeURIComponent(podName)}/metrics` + ); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const data = await response.json(); + return data.metrics || {}; + } catch (error) { + console.error('Failed to fetch eVault metrics:', error); + throw error; + } + } } diff --git a/infrastructure/control-panel/src/lib/services/loki.ts b/infrastructure/control-panel/src/lib/services/loki.ts index ffa1ed96..34cb6f1a 100644 --- a/infrastructure/control-panel/src/lib/services/loki.ts +++ b/infrastructure/control-panel/src/lib/services/loki.ts @@ -41,7 +41,7 @@ export class LokiService { private getAuthHeaders() { return { - 'Authorization': `Basic ${Buffer.from(`${this.username}:${this.password}`).toString('base64')}`, + Authorization: `Basic ${Buffer.from(`${this.username}:${this.password}`).toString('base64')}`, 'Content-Type': 'application/json' }; } @@ -69,7 +69,7 @@ export class LokiService { const data: LokiQueryResponse = await response.json(); const entries: LogEntry[] = []; - + for (const result of data.data.result) { for (const [timestamp, line] of result.values) { entries.push({ @@ -80,7 +80,9 @@ export class LokiService { } } - return entries.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()); + return entries.sort( + (a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime() + ); } catch (error) { console.error('Error querying Loki:', error); return []; @@ -90,9 +92,9 @@ export class LokiService { async getWeb3AdapterLogs(start?: string, end?: string): Promise { const query = '{app="web3-adapter"}'; const logs = await this.queryLogs(query, start, end); - + return logs - .map(log => this.parseLogEntry(log)) + .map((log) => this.parseLogEntry(log)) .filter((event): event is FlowEvent => event !== null); } @@ -100,7 +102,7 @@ export class LokiService { try { // Parse the JSON log line const logData = JSON.parse(log.line); - + // Check if this is a logger.info call with the structure we expect if (logData.tableName && logData.w3id && logData.platform && logData.id) { return { @@ -116,21 +118,21 @@ export class LokiService { } catch (error) { // Skip logs that can't be parsed } - + return null; } async streamLogs(query: string, onLog: (log: LogEntry) => void): Promise<() => void> { let isStreaming = true; - + const streamLogs = async () => { while (isStreaming) { try { const end = new Date().toISOString(); const start = new Date(Date.now() - 30000).toISOString(); // Last 30 seconds - + const logs = await this.queryLogs(query, start, end); - + for (const log of logs) { if (isStreaming) { // Create a unique ID for this log to prevent duplicates @@ -138,7 +140,7 @@ export class LokiService { if (!this.processedLogIds.has(logId)) { this.processedLogIds.add(logId); onLog(log); - + // Clean up old IDs to prevent memory leaks (keep last 1000) if (this.processedLogIds.size > 1000) { const idsArray = Array.from(this.processedLogIds); @@ -147,12 +149,12 @@ export class LokiService { } } } - + // Wait 2 seconds before next query - await new Promise(resolve => setTimeout(resolve, 2000)); + await new Promise((resolve) => setTimeout(resolve, 2000)); } catch (error) { console.error('Error in log stream:', error); - await new Promise(resolve => setTimeout(resolve, 5000)); + await new Promise((resolve) => setTimeout(resolve, 5000)); } } }; @@ -165,4 +167,4 @@ export class LokiService { } } -export const lokiService = new LokiService(); \ No newline at end of file +export const lokiService = new LokiService(); diff --git a/infrastructure/control-panel/src/lib/services/registry.ts b/infrastructure/control-panel/src/lib/services/registry.ts index 3778feaa..ecde4925 100644 --- a/infrastructure/control-panel/src/lib/services/registry.ts +++ b/infrastructure/control-panel/src/lib/services/registry.ts @@ -17,25 +17,26 @@ export class RegistryService { async getPlatforms(): Promise { try { const response = await fetch(`${this.baseUrl}/platforms`); - + if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const platformUrls: string[] = await response.json(); - + // Convert URLs to platform objects with friendly names - const platforms = platformUrls.map(url => { + const platforms = platformUrls.map((url) => { // Ensure URL has protocol - const urlWithProtocol = url.startsWith('http://') || url.startsWith('https://') ? url : `http://${url}`; + const urlWithProtocol = + url.startsWith('http://') || url.startsWith('https://') ? url : `http://${url}`; const urlObj = new URL(urlWithProtocol); const hostname = urlObj.hostname; const port = urlObj.port; const protocol = urlObj.protocol; - + // Extract platform name from hostname let name = hostname.split('.')[0]; - + // Capitalize and format the name if (name === 'pictique') name = 'Pictique'; else if (name === 'blabsy') name = 'Blabsy'; @@ -55,11 +56,11 @@ export class RegistryService { uptime: '24h' }; }); - + return platforms; } catch (error) { console.error('Error fetching platforms from registry:', error); - + // Return fallback platforms if registry is unavailable return [ { @@ -91,4 +92,4 @@ export class RegistryService { } } -export const registryService = new RegistryService(); \ No newline at end of file +export const registryService = new RegistryService(); diff --git a/infrastructure/control-panel/src/lib/ui/Table/Table.svelte b/infrastructure/control-panel/src/lib/ui/Table/Table.svelte index c48b92a3..d353ae97 100644 --- a/infrastructure/control-panel/src/lib/ui/Table/Table.svelte +++ b/infrastructure/control-panel/src/lib/ui/Table/Table.svelte @@ -268,7 +268,7 @@ > {#if withSelection} - + toggleCheckAll(e as boolean)} /> {/if} @@ -310,11 +310,11 @@ handleSelectedRow && handleSelectedRow(i); } }} - class="hover:bg-gray w-full select-none bg-white + class="hover:bg-gray w-full bg-white select-none {selectedRow === i && 'bg-gray!'}" > {#if withSelection} - + toggleCheckItem(i, e as boolean)} @@ -376,7 +376,7 @@ {#snippet BodyCell(data: Record, field: string, i: number)} { // First try to get external IP from nodes const { stdout: nodesOutput } = await execAsync('kubectl get nodes -o json'); const nodes = JSON.parse(nodesOutput); - + // Look for external IP in node addresses for (const node of nodes.items) { if (node.status && node.status.addresses) { @@ -43,7 +43,7 @@ export const GET: RequestHandler = async () => { if (externalIP !== 'localhost') break; } } - + // If no external IP found, try to get internal IP if (externalIP === 'localhost') { for (const node of nodes.items) { @@ -51,7 +51,10 @@ export const GET: RequestHandler = async () => { for (const address of node.status.addresses) { if (address.type === 'InternalIP' && address.address) { // Check if it's not a localhost/127.0.0.1 address - if (!address.address.startsWith('127.') && address.address !== 'localhost') { + if ( + !address.address.startsWith('127.') && + address.address !== 'localhost' + ) { externalIP = address.address; console.log('Found internal IP from node:', externalIP); break; @@ -62,30 +65,43 @@ export const GET: RequestHandler = async () => { } } } - + // If still no IP found, try minikube ip as fallback if (externalIP === 'localhost') { - const { stdout: minikubeIPOutput } = await execAsync('minikube ip 2>/dev/null || echo ""'); + const { stdout: minikubeIPOutput } = await execAsync( + 'minikube ip 2>/dev/null || echo ""' + ); if (minikubeIPOutput.trim() && minikubeIPOutput.trim() !== 'localhost') { externalIP = minikubeIPOutput.trim(); console.log('Using minikube IP:', externalIP); } } - + // If still no IP, try to get the host IP from kubectl config if (externalIP === 'localhost') { - const { stdout: configOutput } = await execAsync('kubectl config view --minify -o json'); + const { stdout: configOutput } = await execAsync( + 'kubectl config view --minify -o json' + ); const config = JSON.parse(configOutput); - if (config.clusters && config.clusters[0] && config.clusters[0].cluster && config.clusters[0].cluster.server) { + if ( + config.clusters && + config.clusters[0] && + config.clusters[0].cluster && + config.clusters[0].cluster.server + ) { const serverUrl = config.clusters[0].cluster.server; const urlMatch = serverUrl.match(/https?:\/\/([^:]+):/); - if (urlMatch && urlMatch[1] && urlMatch[1] !== 'localhost' && urlMatch[1] !== '127.0.0.1') { + if ( + urlMatch && + urlMatch[1] && + urlMatch[1] !== 'localhost' && + urlMatch[1] !== '127.0.0.1' + ) { externalIP = urlMatch[1]; console.log('Using IP from kubectl config:', externalIP); } } } - } catch (ipError) { console.log('Could not get external IP, using localhost:', ipError); } diff --git a/infrastructure/control-panel/src/routes/api/evaults/[namespace]/[pod]/logs/+server.ts b/infrastructure/control-panel/src/routes/api/evaults/[namespace]/[pod]/logs/+server.ts index caa5de85..3ba0d93b 100644 --- a/infrastructure/control-panel/src/routes/api/evaults/[namespace]/[pod]/logs/+server.ts +++ b/infrastructure/control-panel/src/routes/api/evaults/[namespace]/[pod]/logs/+server.ts @@ -15,10 +15,13 @@ export const GET: RequestHandler = async ({ params, url }) => { await execAsync(`kubectl get namespace ${namespace}`); } catch (namespaceError: any) { if (namespaceError.stderr?.includes('not found')) { - return json({ - error: `Namespace '${namespace}' not found. The eVault may have been deleted or terminated.`, - logs: [] - }, { status: 404 }); + return json( + { + error: `Namespace '${namespace}' not found. The eVault may have been deleted or terminated.`, + logs: [] + }, + { status: 404 } + ); } throw namespaceError; } @@ -28,10 +31,13 @@ export const GET: RequestHandler = async ({ params, url }) => { await execAsync(`kubectl get pod ${pod} -n ${namespace}`); } catch (podError: any) { if (podError.stderr?.includes('not found')) { - return json({ - error: `Pod '${pod}' not found in namespace '${namespace}'. The pod may have been deleted or terminated.`, - logs: [] - }, { status: 404 }); + return json( + { + error: `Pod '${pod}' not found in namespace '${namespace}'. The pod may have been deleted or terminated.`, + logs: [] + }, + { status: 404 } + ); } throw podError; } @@ -48,18 +54,24 @@ export const GET: RequestHandler = async ({ params, url }) => { return json({ logs }); } catch (error: any) { console.error('Error fetching logs:', error); - + // Handle specific kubectl errors if (error.stderr?.includes('not found')) { - return json({ - error: 'Resource not found. The eVault or pod may have been deleted.', - logs: [] - }, { status: 404 }); + return json( + { + error: 'Resource not found. The eVault or pod may have been deleted.', + logs: [] + }, + { status: 404 } + ); } - - return json({ - error: 'Failed to fetch logs. Please check if the eVault is still running.', - logs: [] - }, { status: 500 }); + + return json( + { + error: 'Failed to fetch logs. Please check if the eVault is still running.', + logs: [] + }, + { status: 500 } + ); } }; diff --git a/infrastructure/control-panel/src/routes/api/events/+server.ts b/infrastructure/control-panel/src/routes/api/events/+server.ts index bba2c954..95e0c040 100644 --- a/infrastructure/control-panel/src/routes/api/events/+server.ts +++ b/infrastructure/control-panel/src/routes/api/events/+server.ts @@ -7,10 +7,12 @@ export const GET: RequestHandler = async () => { start(controller) { let isConnected = true; let stopStreaming: (() => void) | null = null; - + // Send initial connection message if (isConnected) { - controller.enqueue(`data: ${JSON.stringify({ type: 'connected', timestamp: new Date().toISOString() })}\n\n`); + controller.enqueue( + `data: ${JSON.stringify({ type: 'connected', timestamp: new Date().toISOString() })}\n\n` + ); } // Helper function to safely enqueue data @@ -29,31 +31,28 @@ export const GET: RequestHandler = async () => { // Start streaming real logs from Loki const startRealTimeLogs = async () => { try { - stopStreaming = await lokiService.streamLogs( - '{app="web3-adapter"}', - (log) => { - // Parse the log entry to extract flow information - const flowEvent = lokiService.parseLogEntry(log); - - if (flowEvent) { - // Map the flow event to the expected format - const eventData = { - type: 'evault_sync_event', - timestamp: flowEvent.timestamp, - w3id: flowEvent.w3id, - platform: flowEvent.platform, - id: flowEvent.id, - tableName: flowEvent.tableName, - message: flowEvent.message, - // Extract platform and eVault indices for visualization - platformIndex: 0, // We'll need to map this based on actual platform names - evaultIndex: 0 // We'll need to map this based on actual eVault w3ids - }; - - safeEnqueue(eventData); - } + stopStreaming = await lokiService.streamLogs('{app="web3-adapter"}', (log) => { + // Parse the log entry to extract flow information + const flowEvent = lokiService.parseLogEntry(log); + + if (flowEvent) { + // Map the flow event to the expected format + const eventData = { + type: 'evault_sync_event', + timestamp: flowEvent.timestamp, + w3id: flowEvent.w3id, + platform: flowEvent.platform, + id: flowEvent.id, + tableName: flowEvent.tableName, + message: flowEvent.message, + // Extract platform and eVault indices for visualization + platformIndex: 0, // We'll need to map this based on actual platform names + evaultIndex: 0 // We'll need to map this based on actual eVault w3ids + }; + + safeEnqueue(eventData); } - ); + }); } catch (error) { console.error('Error starting Loki stream:', error); // Fallback to mock data if Loki is not available @@ -64,7 +63,7 @@ export const GET: RequestHandler = async () => { // Fallback mock data function const startMockData = () => { console.log('Using mock data as fallback'); - + // Step 1: Platform 1 creates a message const timeout1 = setTimeout(() => { safeEnqueue({ @@ -127,7 +126,9 @@ export const GET: RequestHandler = async () => { const heartbeat = setInterval(() => { if (isConnected) { try { - controller.enqueue(`data: ${JSON.stringify({ type: 'heartbeat', timestamp: new Date().toISOString() })}\n\n`); + controller.enqueue( + `data: ${JSON.stringify({ type: 'heartbeat', timestamp: new Date().toISOString() })}\n\n` + ); } catch (error) { console.log('Client disconnected during heartbeat, stopping stream'); isConnected = false; @@ -161,9 +162,9 @@ export const GET: RequestHandler = async () => { headers: { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', - 'Connection': 'keep-alive', + Connection: 'keep-alive', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': 'Cache-Control' } }); -}; \ No newline at end of file +}; diff --git a/infrastructure/control-panel/svelte.config.js b/infrastructure/control-panel/svelte.config.js index 4a423ba8..03c17f28 100644 --- a/infrastructure/control-panel/svelte.config.js +++ b/infrastructure/control-panel/svelte.config.js @@ -1,4 +1,4 @@ -import adapter from '@sveltejs/adapter-static'; +import adapter from '@sveltejs/adapter-node'; import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; /** @type {import('@sveltejs/kit').Config} */ diff --git a/infrastructure/control-panel/vite.config.ts b/infrastructure/control-panel/vite.config.ts index bf699a8d..8ad9f41b 100644 --- a/infrastructure/control-panel/vite.config.ts +++ b/infrastructure/control-panel/vite.config.ts @@ -3,5 +3,13 @@ import tailwindcss from '@tailwindcss/vite'; import { defineConfig } from 'vite'; export default defineConfig({ - plugins: [tailwindcss(), sveltekit()] + plugins: [tailwindcss(), sveltekit()], + optimizeDeps: { + exclude: ['lowdb', 'steno'] + }, + build: { + rollupOptions: { + external: ['lowdb', 'lowdb/node', 'steno'] + } + } }); diff --git a/infrastructure/eid-wallet/src/lib/crypto/HardwareKeyManager.ts b/infrastructure/eid-wallet/src/lib/crypto/HardwareKeyManager.ts index ee763019..dd30733a 100644 --- a/infrastructure/eid-wallet/src/lib/crypto/HardwareKeyManager.ts +++ b/infrastructure/eid-wallet/src/lib/crypto/HardwareKeyManager.ts @@ -1,30 +1,30 @@ -import type { KeyManager } from './types'; -import { KeyManagerError, KeyManagerErrorCodes } from './types'; +import type { KeyManager } from "./types"; +import { KeyManagerError, KeyManagerErrorCodes } from "./types"; import { exists as hwExists, generate as hwGenerate, getPublicKey as hwGetPublicKey, signPayload as hwSignPayload, verifySignature as hwVerifySignature, -} from '@auvo/tauri-plugin-crypto-hw-api'; +} from "@auvo/tauri-plugin-crypto-hw-api"; /** * Hardware key manager implementation using Tauri crypto hardware API */ export class HardwareKeyManager implements KeyManager { - getType(): 'hardware' | 'software' { - return 'hardware'; + getType(): "hardware" | "software" { + return "hardware"; } async exists(keyId: string): Promise { try { return await hwExists(keyId); } catch (error) { - console.error('Hardware key exists check failed:', error); + console.error("Hardware key exists check failed:", error); throw new KeyManagerError( - 'Failed to check if hardware key exists', + "Failed to check if hardware key exists", KeyManagerErrorCodes.HARDWARE_UNAVAILABLE, - keyId + keyId, ); } } @@ -35,11 +35,11 @@ export class HardwareKeyManager implements KeyManager { console.log(`Hardware key generated for ${keyId}:`, result); return result; } catch (error) { - console.error('Hardware key generation failed:', error); + console.error("Hardware key generation failed:", error); throw new KeyManagerError( - 'Failed to generate hardware key', + "Failed to generate hardware key", KeyManagerErrorCodes.KEY_GENERATION_FAILED, - keyId + keyId, ); } } @@ -47,14 +47,17 @@ export class HardwareKeyManager implements KeyManager { async getPublicKey(keyId: string): Promise { try { const publicKey = await hwGetPublicKey(keyId); - console.log(`Hardware public key retrieved for ${keyId}:`, publicKey); + console.log( + `Hardware public key retrieved for ${keyId}:`, + publicKey, + ); return publicKey; } catch (error) { - console.error('Hardware public key retrieval failed:', error); + console.error("Hardware public key retrieval failed:", error); throw new KeyManagerError( - 'Failed to get hardware public key', + "Failed to get hardware public key", KeyManagerErrorCodes.KEY_NOT_FOUND, - keyId + keyId, ); } } @@ -65,26 +68,33 @@ export class HardwareKeyManager implements KeyManager { console.log(`Hardware signature created for ${keyId}`); return signature; } catch (error) { - console.error('Hardware signing failed:', error); + console.error("Hardware signing failed:", error); throw new KeyManagerError( - 'Failed to sign payload with hardware key', + "Failed to sign payload with hardware key", KeyManagerErrorCodes.SIGNING_FAILED, - keyId + keyId, ); } } - async verifySignature(keyId: string, payload: string, signature: string): Promise { + async verifySignature( + keyId: string, + payload: string, + signature: string, + ): Promise { try { const isValid = await hwVerifySignature(keyId, payload, signature); - console.log(`Hardware signature verification for ${keyId}:`, isValid); + console.log( + `Hardware signature verification for ${keyId}:`, + isValid, + ); return isValid; } catch (error) { - console.error('Hardware signature verification failed:', error); + console.error("Hardware signature verification failed:", error); throw new KeyManagerError( - 'Failed to verify signature with hardware key', + "Failed to verify signature with hardware key", KeyManagerErrorCodes.VERIFICATION_FAILED, - keyId + keyId, ); } } diff --git a/infrastructure/eid-wallet/src/lib/crypto/KeyManagerFactory.ts b/infrastructure/eid-wallet/src/lib/crypto/KeyManagerFactory.ts index 8d01587f..838bd6ff 100644 --- a/infrastructure/eid-wallet/src/lib/crypto/KeyManagerFactory.ts +++ b/infrastructure/eid-wallet/src/lib/crypto/KeyManagerFactory.ts @@ -1,7 +1,7 @@ -import type { KeyManager, KeyManagerConfig } from './types'; -import { HardwareKeyManager } from './HardwareKeyManager'; -import { SoftwareKeyManager } from './SoftwareKeyManager'; -import { KeyManagerError, KeyManagerErrorCodes } from './types'; +import type { KeyManager, KeyManagerConfig } from "./types"; +import { HardwareKeyManager } from "./HardwareKeyManager"; +import { SoftwareKeyManager } from "./SoftwareKeyManager"; +import { KeyManagerError, KeyManagerErrorCodes } from "./types"; /** * Factory class to create appropriate key managers based on context @@ -21,7 +21,7 @@ export class KeyManagerFactory { // If in pre-verification mode, always use software keys if (config.preVerificationMode) { - console.log('Using software key manager for pre-verification mode'); + console.log("Using software key manager for pre-verification mode"); return this.getSoftwareKeyManager(); } @@ -30,10 +30,12 @@ export class KeyManagerFactory { const hardwareManager = this.getHardwareKeyManager(); // Test if hardware is available by checking if we can call exists await hardwareManager.exists(config.keyId); - console.log('Using hardware key manager'); + console.log("Using hardware key manager"); return hardwareManager; } catch (error) { - console.log('Hardware key manager not available, falling back to software'); + console.log( + "Hardware key manager not available, falling back to software", + ); return this.getSoftwareKeyManager(); } } @@ -65,10 +67,10 @@ export class KeyManagerFactory { try { const hardwareManager = this.getHardwareKeyManager(); // Try to check if a test key exists to verify hardware availability - await hardwareManager.exists('test-hardware-check'); + await hardwareManager.exists("test-hardware-check"); return true; } catch (error) { - console.log('Hardware key manager not available:', error); + console.log("Hardware key manager not available:", error); return false; } } @@ -78,12 +80,12 @@ export class KeyManagerFactory { */ static async getKeyManagerForContext( keyId: string, - context: 'onboarding' | 'signing' | 'verification' | 'pre-verification' + context: "onboarding" | "signing" | "verification" | "pre-verification", ): Promise { const config: KeyManagerConfig = { keyId, - useHardware: context !== 'pre-verification', - preVerificationMode: context === 'pre-verification', + useHardware: context !== "pre-verification", + preVerificationMode: context === "pre-verification", }; return this.getKeyManager(config); diff --git a/infrastructure/eid-wallet/src/lib/crypto/SoftwareKeyManager.ts b/infrastructure/eid-wallet/src/lib/crypto/SoftwareKeyManager.ts index b559f6f5..b7e70344 100644 --- a/infrastructure/eid-wallet/src/lib/crypto/SoftwareKeyManager.ts +++ b/infrastructure/eid-wallet/src/lib/crypto/SoftwareKeyManager.ts @@ -1,15 +1,15 @@ -import type { KeyManager, SoftwareKeyPair } from './types'; -import { KeyManagerError, KeyManagerErrorCodes } from './types'; +import type { KeyManager, SoftwareKeyPair } from "./types"; +import { KeyManagerError, KeyManagerErrorCodes } from "./types"; /** * Software key manager implementation using Web Crypto API and localStorage */ export class SoftwareKeyManager implements KeyManager { - private readonly storageKey = 'eid-wallet-software-keys'; - private readonly keyPrefix = 'software-key-'; + private readonly storageKey = "eid-wallet-software-keys"; + private readonly keyPrefix = "software-key-"; - getType(): 'hardware' | 'software' { - return 'software'; + getType(): "hardware" | "software" { + return "software"; } async exists(keyId: string): Promise { @@ -18,11 +18,11 @@ export class SoftwareKeyManager implements KeyManager { const stored = localStorage.getItem(storageKey); return stored !== null; } catch (error) { - console.error('Software key exists check failed:', error); + console.error("Software key exists check failed:", error); throw new KeyManagerError( - 'Failed to check if software key exists', + "Failed to check if software key exists", KeyManagerErrorCodes.STORAGE_ERROR, - keyId + keyId, ); } } @@ -32,25 +32,31 @@ export class SoftwareKeyManager implements KeyManager { // Check if key already exists if (await this.exists(keyId)) { console.log(`Software key ${keyId} already exists`); - return 'key-exists'; + return "key-exists"; } // Generate a new key pair using Web Crypto API const keyPair = await crypto.subtle.generateKey( { - name: 'ECDSA', - namedCurve: 'P-256', + name: "ECDSA", + namedCurve: "P-256", }, true, // extractable - ['sign', 'verify'] + ["sign", "verify"], ); // Export the private key - const privateKeyBuffer = await crypto.subtle.exportKey('pkcs8', keyPair.privateKey); + const privateKeyBuffer = await crypto.subtle.exportKey( + "pkcs8", + keyPair.privateKey, + ); const privateKeyString = this.arrayBufferToBase64(privateKeyBuffer); // Export the public key - const publicKeyBuffer = await crypto.subtle.exportKey('spki', keyPair.publicKey); + const publicKeyBuffer = await crypto.subtle.exportKey( + "spki", + keyPair.publicKey, + ); const publicKeyString = this.arrayBufferToBase64(publicKeyBuffer); // Store the key pair @@ -65,13 +71,13 @@ export class SoftwareKeyManager implements KeyManager { localStorage.setItem(storageKey, JSON.stringify(keyPairData)); console.log(`Software key pair generated and stored for ${keyId}`); - return 'key-generated'; + return "key-generated"; } catch (error) { - console.error('Software key generation failed:', error); + console.error("Software key generation failed:", error); throw new KeyManagerError( - 'Failed to generate software key', + "Failed to generate software key", KeyManagerErrorCodes.KEY_GENERATION_FAILED, - keyId + keyId, ); } } @@ -81,9 +87,9 @@ export class SoftwareKeyManager implements KeyManager { const keyPair = await this.getKeyPair(keyId); if (!keyPair) { throw new KeyManagerError( - 'Software key not found', + "Software key not found", KeyManagerErrorCodes.KEY_NOT_FOUND, - keyId + keyId, ); } @@ -91,18 +97,18 @@ export class SoftwareKeyManager implements KeyManager { // The hardware API returns multibase hex format, so we'll convert our base64 to hex const publicKeyBuffer = this.base64ToArrayBuffer(keyPair.publicKey); const publicKeyHex = this.arrayBufferToHex(publicKeyBuffer); - + // Add multibase prefix (assuming 'z' for base58btc, but we'll use hex for simplicity) return `z${publicKeyHex}`; } catch (error) { - console.error('Software public key retrieval failed:', error); + console.error("Software public key retrieval failed:", error); if (error instanceof KeyManagerError) { throw error; } throw new KeyManagerError( - 'Failed to get software public key', + "Failed to get software public key", KeyManagerErrorCodes.KEY_NOT_FOUND, - keyId + keyId, ); } } @@ -112,23 +118,25 @@ export class SoftwareKeyManager implements KeyManager { const keyPair = await this.getKeyPair(keyId); if (!keyPair) { throw new KeyManagerError( - 'Software key not found', + "Software key not found", KeyManagerErrorCodes.KEY_NOT_FOUND, - keyId + keyId, ); } // Import the private key - const privateKeyBuffer = this.base64ToArrayBuffer(keyPair.privateKey); + const privateKeyBuffer = this.base64ToArrayBuffer( + keyPair.privateKey, + ); const privateKey = await crypto.subtle.importKey( - 'pkcs8', + "pkcs8", privateKeyBuffer, { - name: 'ECDSA', - namedCurve: 'P-256', + name: "ECDSA", + namedCurve: "P-256", }, false, - ['sign'] + ["sign"], ); // Convert payload to ArrayBuffer @@ -137,11 +145,11 @@ export class SoftwareKeyManager implements KeyManager { // Sign the payload const signature = await crypto.subtle.sign( { - name: 'ECDSA', - hash: 'SHA-256', + name: "ECDSA", + hash: "SHA-256", }, privateKey, - payloadBuffer + payloadBuffer, ); // Convert signature to base64 string @@ -149,40 +157,44 @@ export class SoftwareKeyManager implements KeyManager { console.log(`Software signature created for ${keyId}`); return signatureString; } catch (error) { - console.error('Software signing failed:', error); + console.error("Software signing failed:", error); if (error instanceof KeyManagerError) { throw error; } throw new KeyManagerError( - 'Failed to sign payload with software key', + "Failed to sign payload with software key", KeyManagerErrorCodes.SIGNING_FAILED, - keyId + keyId, ); } } - async verifySignature(keyId: string, payload: string, signature: string): Promise { + async verifySignature( + keyId: string, + payload: string, + signature: string, + ): Promise { try { const keyPair = await this.getKeyPair(keyId); if (!keyPair) { throw new KeyManagerError( - 'Software key not found', + "Software key not found", KeyManagerErrorCodes.KEY_NOT_FOUND, - keyId + keyId, ); } // Import the public key const publicKeyBuffer = this.base64ToArrayBuffer(keyPair.publicKey); const publicKey = await crypto.subtle.importKey( - 'spki', + "spki", publicKeyBuffer, { - name: 'ECDSA', - namedCurve: 'P-256', + name: "ECDSA", + namedCurve: "P-256", }, false, - ['verify'] + ["verify"], ); // Convert payload and signature to ArrayBuffers @@ -192,25 +204,28 @@ export class SoftwareKeyManager implements KeyManager { // Verify the signature const isValid = await crypto.subtle.verify( { - name: 'ECDSA', - hash: 'SHA-256', + name: "ECDSA", + hash: "SHA-256", }, publicKey, signatureBuffer, - payloadBuffer + payloadBuffer, ); - console.log(`Software signature verification for ${keyId}:`, isValid); + console.log( + `Software signature verification for ${keyId}:`, + isValid, + ); return isValid; } catch (error) { - console.error('Software signature verification failed:', error); + console.error("Software signature verification failed:", error); if (error instanceof KeyManagerError) { throw error; } throw new KeyManagerError( - 'Failed to verify signature with software key', + "Failed to verify signature with software key", KeyManagerErrorCodes.VERIFICATION_FAILED, - keyId + keyId, ); } } @@ -224,7 +239,7 @@ export class SoftwareKeyManager implements KeyManager { } return JSON.parse(stored) as SoftwareKeyPair; } catch (error) { - console.error('Failed to retrieve key pair from storage:', error); + console.error("Failed to retrieve key pair from storage:", error); return null; } } @@ -235,7 +250,7 @@ export class SoftwareKeyManager implements KeyManager { private arrayBufferToBase64(buffer: ArrayBuffer): string { const bytes = new Uint8Array(buffer); - let binary = ''; + let binary = ""; for (let i = 0; i < bytes.byteLength; i++) { binary += String.fromCharCode(bytes[i]); } @@ -254,7 +269,7 @@ export class SoftwareKeyManager implements KeyManager { private arrayBufferToHex(buffer: ArrayBuffer): string { const bytes = new Uint8Array(buffer); return Array.from(bytes) - .map(b => b.toString(16).padStart(2, '0')) - .join(''); + .map((b) => b.toString(16).padStart(2, "0")) + .join(""); } } diff --git a/infrastructure/eid-wallet/src/lib/crypto/index.ts b/infrastructure/eid-wallet/src/lib/crypto/index.ts index 033af0be..770efe37 100644 --- a/infrastructure/eid-wallet/src/lib/crypto/index.ts +++ b/infrastructure/eid-wallet/src/lib/crypto/index.ts @@ -1,6 +1,6 @@ // Export all crypto-related types and classes -export type { KeyManager, KeyManagerConfig, SoftwareKeyPair } from './types'; -export { KeyManagerError, KeyManagerErrorCodes } from './types'; -export { HardwareKeyManager } from './HardwareKeyManager'; -export { SoftwareKeyManager } from './SoftwareKeyManager'; -export { KeyManagerFactory } from './KeyManagerFactory'; \ No newline at end of file +export type { KeyManager, KeyManagerConfig, SoftwareKeyPair } from "./types"; +export { KeyManagerError, KeyManagerErrorCodes } from "./types"; +export { HardwareKeyManager } from "./HardwareKeyManager"; +export { SoftwareKeyManager } from "./SoftwareKeyManager"; +export { KeyManagerFactory } from "./KeyManagerFactory"; diff --git a/infrastructure/eid-wallet/src/lib/crypto/types.ts b/infrastructure/eid-wallet/src/lib/crypto/types.ts index 7d46cbf1..ac7f14e7 100644 --- a/infrastructure/eid-wallet/src/lib/crypto/types.ts +++ b/infrastructure/eid-wallet/src/lib/crypto/types.ts @@ -26,12 +26,16 @@ export interface KeyManager { /** * Verify a signature with the given keyId and payload */ - verifySignature(keyId: string, payload: string, signature: string): Promise; + verifySignature( + keyId: string, + payload: string, + signature: string, + ): Promise; /** * Get the type of key manager (hardware or software) */ - getType(): 'hardware' | 'software'; + getType(): "hardware" | "software"; } /** @@ -60,18 +64,18 @@ export class KeyManagerError extends Error { constructor( message: string, public readonly code: string, - public readonly keyId?: string + public readonly keyId?: string, ) { super(message); - this.name = 'KeyManagerError'; + this.name = "KeyManagerError"; } } export const KeyManagerErrorCodes = { - KEY_NOT_FOUND: 'KEY_NOT_FOUND', - KEY_GENERATION_FAILED: 'KEY_GENERATION_FAILED', - SIGNING_FAILED: 'SIGNING_FAILED', - VERIFICATION_FAILED: 'VERIFICATION_FAILED', - HARDWARE_UNAVAILABLE: 'HARDWARE_UNAVAILABLE', - STORAGE_ERROR: 'STORAGE_ERROR', + KEY_NOT_FOUND: "KEY_NOT_FOUND", + KEY_GENERATION_FAILED: "KEY_GENERATION_FAILED", + SIGNING_FAILED: "SIGNING_FAILED", + VERIFICATION_FAILED: "VERIFICATION_FAILED", + HARDWARE_UNAVAILABLE: "HARDWARE_UNAVAILABLE", + STORAGE_ERROR: "STORAGE_ERROR", } as const; diff --git a/infrastructure/eid-wallet/src/lib/fragments/IdentityCard/IdentityCard.svelte b/infrastructure/eid-wallet/src/lib/fragments/IdentityCard/IdentityCard.svelte index 8ca1c910..d91eacf3 100644 --- a/infrastructure/eid-wallet/src/lib/fragments/IdentityCard/IdentityCard.svelte +++ b/infrastructure/eid-wallet/src/lib/fragments/IdentityCard/IdentityCard.svelte @@ -1,47 +1,47 @@
diff --git a/infrastructure/eid-wallet/src/lib/global/controllers/evault.ts b/infrastructure/eid-wallet/src/lib/global/controllers/evault.ts index f5a414cb..75292053 100644 --- a/infrastructure/eid-wallet/src/lib/global/controllers/evault.ts +++ b/infrastructure/eid-wallet/src/lib/global/controllers/evault.ts @@ -79,8 +79,11 @@ export class VaultController { */ private async registerDeviceForNotifications(eName: string): Promise { try { - console.log(`Registering device for notifications with eName: ${eName}`); - const success = await this.#notificationService.registerDevice(eName); + console.log( + `Registering device for notifications with eName: ${eName}`, + ); + const success = + await this.#notificationService.registerDevice(eName); if (success) { console.log("Device registered successfully for notifications"); } else { @@ -134,19 +137,30 @@ export class VaultController { while (retryCount < maxRetries) { try { const response = await axios.get( - new URL(`resolve?w3id=${w3id}`, PUBLIC_REGISTRY_URL).toString(), + new URL( + `resolve?w3id=${w3id}`, + PUBLIC_REGISTRY_URL, + ).toString(), ); return new URL("/graphql", response.data.uri).toString(); } catch (error) { retryCount++; - console.error(`Error resolving eVault endpoint (attempt ${retryCount}/${maxRetries}):`, error); + console.error( + `Error resolving eVault endpoint (attempt ${retryCount}/${maxRetries}):`, + error, + ); if (retryCount >= maxRetries) { - throw new Error("Failed to resolve eVault endpoint after all retries"); + throw new Error( + "Failed to resolve eVault endpoint after all retries", + ); } // Wait before retrying (exponential backoff) - const delay = Math.min(1000 * Math.pow(2, retryCount - 1), 10000); + const delay = Math.min( + 1000 * Math.pow(2, retryCount - 1), + 10000, + ); console.log(`Waiting ${delay}ms before resolve retry...`); await new Promise((resolve) => setTimeout(resolve, delay)); } @@ -247,10 +261,12 @@ export class VaultController { .then(async (resolvedUser) => { if (resolvedUser?.ename) { this.#store.set("vault", resolvedUser); - + // Register device for notifications - await this.registerDeviceForNotifications(resolvedUser.ename); - + await this.registerDeviceForNotifications( + resolvedUser.ename, + ); + // Set loading status // Get user data for display name const userData = await this.#userController.user; @@ -258,7 +274,8 @@ export class VaultController { userData?.name || resolvedUser?.ename; try { - if (this.profileCreationStatus === "success") return + if (this.profileCreationStatus === "success") + return; this.profileCreationStatus = "loading"; await this.createUserProfileInEVault( resolvedUser?.ename as string, @@ -285,7 +302,7 @@ export class VaultController { // Register device for notifications this.registerDeviceForNotifications(vault.ename); - if (this.profileCreationStatus === "success") return + if (this.profileCreationStatus === "success") return; // Set loading status this.profileCreationStatus = "loading"; diff --git a/infrastructure/eid-wallet/src/lib/services/NotificationService.ts b/infrastructure/eid-wallet/src/lib/services/NotificationService.ts index 17618dd9..6998feb4 100644 --- a/infrastructure/eid-wallet/src/lib/services/NotificationService.ts +++ b/infrastructure/eid-wallet/src/lib/services/NotificationService.ts @@ -1,348 +1,381 @@ -import { requestPermission, isPermissionGranted, sendNotification } from '@tauri-apps/plugin-notification'; -import { invoke } from '@tauri-apps/api/core'; -import { PUBLIC_PROVISIONER_URL } from "$env/static/public" +import { + requestPermission, + isPermissionGranted, + sendNotification, +} from "@tauri-apps/plugin-notification"; +import { invoke } from "@tauri-apps/api/core"; +import { PUBLIC_PROVISIONER_URL } from "$env/static/public"; export interface DeviceRegistration { - eName: string; - deviceId: string; - platform: 'android' | 'ios' | 'desktop'; - fcmToken?: string; // For Android/iOS push notifications - registrationTime: Date; + eName: string; + deviceId: string; + platform: "android" | "ios" | "desktop"; + fcmToken?: string; // For Android/iOS push notifications + registrationTime: Date; } export interface NotificationPayload { - title: string; - body: string; - data?: Record; + title: string; + body: string; + data?: Record; } class NotificationService { - private static instance: NotificationService; - private deviceRegistration: DeviceRegistration | null = null; - private provisionerEndpoint: string; - - constructor() { - // Get the provisioner endpoint from environment or use default - this.provisionerEndpoint = PUBLIC_PROVISIONER_URL - } - - static getInstance(): NotificationService { - if (!NotificationService.instance) { - NotificationService.instance = new NotificationService(); - } - return NotificationService.instance; - } - - /** - * Request notification permissions - */ - async requestPermissions(): Promise { - try { - // First check if permission is already granted - const isGranted = await isPermissionGranted(); - console.log('Notification permission already granted:', isGranted); - - if (isGranted) { - return true; - } - - // Request permission if not granted - const permission = await requestPermission(); - console.log('Permission request result:', permission); - return permission === 'granted'; - } catch (error) { - console.error('Failed to request notification permissions:', error); - return false; + private static instance: NotificationService; + private deviceRegistration: DeviceRegistration | null = null; + private provisionerEndpoint: string; + + constructor() { + // Get the provisioner endpoint from environment or use default + this.provisionerEndpoint = PUBLIC_PROVISIONER_URL; } - } - - /** - * Register device with the provisioner service - */ - async registerDevice(eName: string): Promise { - try { - // Get device information - const deviceId = await this.getDeviceId(); - const platform = await this.getPlatform(); - - // Request notification permissions first - const hasPermission = await this.requestPermissions(); - if (!hasPermission) { - throw new Error('Notification permissions not granted'); - } - - // Get FCM token for mobile platforms - let fcmToken: string | undefined; - if (platform === 'android' || platform === 'ios') { - fcmToken = await this.getFCMToken(); - } - - const registration: DeviceRegistration = { - eName, - deviceId, - platform, - fcmToken, - registrationTime: new Date() - }; - - // Send registration to provisioner - const response = await fetch(`${this.provisionerEndpoint}/api/devices/register`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(registration) - }); - - if (response.ok) { - this.deviceRegistration = registration; - console.log('Device registered successfully:', registration); - return true; - } else { - throw new Error(`Registration failed: ${response.statusText}`); - } - } catch (error) { - console.error('Failed to register device:', error); - return false; + + static getInstance(): NotificationService { + if (!NotificationService.instance) { + NotificationService.instance = new NotificationService(); + } + return NotificationService.instance; } - } - - /** - * Send a local notification - */ - async sendLocalNotification(payload: NotificationPayload): Promise { - try { - console.log('Attempting to send local notification:', payload); - - // Check permissions first - const hasPermission = await isPermissionGranted(); - console.log('Has notification permission:', hasPermission); - - if (!hasPermission) { - console.warn('No notification permission, requesting...'); - const granted = await this.requestPermissions(); - if (!granted) { - console.error('Failed to get notification permission'); - return; + + /** + * Request notification permissions + */ + async requestPermissions(): Promise { + try { + // First check if permission is already granted + const isGranted = await isPermissionGranted(); + console.log("Notification permission already granted:", isGranted); + + if (isGranted) { + return true; + } + + // Request permission if not granted + const permission = await requestPermission(); + console.log("Permission request result:", permission); + return permission === "granted"; + } catch (error) { + console.error("Failed to request notification permissions:", error); + return false; } - } - - console.log('Sending notification with payload:', { - title: payload.title, - body: payload.body, - icon: 'icons/32x32.png', - sound: 'default', - data: payload.data - }); - - await sendNotification({ - title: payload.title, - body: payload.body, - icon: 'icons/32x32.png', - sound: 'default', - data: payload.data - }); - - console.log('Notification sent successfully!'); - } catch (error) { - console.error('Failed to send local notification:', error); } - } - - /** - * Unregister device from the provisioner service - */ - async unregisterDevice(): Promise { - if (!this.deviceRegistration) { - return true; // Already unregistered + + /** + * Register device with the provisioner service + */ + async registerDevice(eName: string): Promise { + try { + // Get device information + const deviceId = await this.getDeviceId(); + const platform = await this.getPlatform(); + + // Request notification permissions first + const hasPermission = await this.requestPermissions(); + if (!hasPermission) { + throw new Error("Notification permissions not granted"); + } + + // Get FCM token for mobile platforms + let fcmToken: string | undefined; + if (platform === "android" || platform === "ios") { + fcmToken = await this.getFCMToken(); + } + + const registration: DeviceRegistration = { + eName, + deviceId, + platform, + fcmToken, + registrationTime: new Date(), + }; + + // Send registration to provisioner + const response = await fetch( + `${this.provisionerEndpoint}/api/devices/register`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(registration), + }, + ); + + if (response.ok) { + this.deviceRegistration = registration; + console.log("Device registered successfully:", registration); + return true; + } else { + throw new Error(`Registration failed: ${response.statusText}`); + } + } catch (error) { + console.error("Failed to register device:", error); + return false; + } } - try { - const response = await fetch(`${this.provisionerEndpoint}/api/devices/unregister`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - eName: this.deviceRegistration.eName, - deviceId: this.deviceRegistration.deviceId - }) - }); - - if (response.ok) { - this.deviceRegistration = null; - console.log('Device unregistered successfully'); - return true; - } else { - throw new Error(`Unregistration failed: ${response.statusText}`); - } - } catch (error) { - console.error('Failed to unregister device:', error); - return false; + /** + * Send a local notification + */ + async sendLocalNotification(payload: NotificationPayload): Promise { + try { + console.log("Attempting to send local notification:", payload); + + // Check permissions first + const hasPermission = await isPermissionGranted(); + console.log("Has notification permission:", hasPermission); + + if (!hasPermission) { + console.warn("No notification permission, requesting..."); + const granted = await this.requestPermissions(); + if (!granted) { + console.error("Failed to get notification permission"); + return; + } + } + + console.log("Sending notification with payload:", { + title: payload.title, + body: payload.body, + icon: "icons/32x32.png", + sound: "default", + data: payload.data, + }); + + await sendNotification({ + title: payload.title, + body: payload.body, + icon: "icons/32x32.png", + sound: "default", + data: payload.data, + }); + + console.log("Notification sent successfully!"); + } catch (error) { + console.error("Failed to send local notification:", error); + } } - } - - /** - * Check for notifications from provisioner and show them locally - */ - async checkAndShowNotifications(): Promise { - try { - console.log('🔍 Checking for notifications from provisioner...'); - - // Get current device registration - let registration = this.getDeviceRegistration(); - - // If no registration, try to get eName from vault and register - if (!registration) { - console.log('No device registration found, attempting to register...'); - - // Try to get eName from vault + + /** + * Unregister device from the provisioner service + */ + async unregisterDevice(): Promise { + if (!this.deviceRegistration) { + return true; // Already unregistered + } + try { - const vault = await this.getVaultEname(); - if (vault?.ename) { - console.log(`Found eName: ${vault.ename}, registering device...`); - const registered = await this.registerDevice(vault.ename); - if (registered) { - registration = this.getDeviceRegistration(); - console.log('Device registered successfully'); + const response = await fetch( + `${this.provisionerEndpoint}/api/devices/unregister`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + eName: this.deviceRegistration.eName, + deviceId: this.deviceRegistration.deviceId, + }), + }, + ); + + if (response.ok) { + this.deviceRegistration = null; + console.log("Device unregistered successfully"); + return true; } else { - console.log('Failed to register device'); - return; + throw new Error( + `Unregistration failed: ${response.statusText}`, + ); } - } else { - console.log('No eName found in vault, skipping notification check'); - return; - } } catch (error) { - console.error('Error getting vault eName:', error); - return; + console.error("Failed to unregister device:", error); + return false; } - } - - if (!registration) { - console.log('Still no device registration, skipping notification check'); - return; - } - - // Check for notifications from provisioner - const response = await fetch(`${this.provisionerEndpoint}/api/notifications/check`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - eName: registration.eName, - deviceId: registration.deviceId - }) - }); - - if (response.ok) { - const data = await response.json(); - if (data.notifications && data.notifications.length > 0) { - console.log(`📱 Found ${data.notifications.length} notification(s)`); - - // Show each notification locally - for (const notification of data.notifications) { - await this.sendLocalNotification(notification); - } - } else { - console.log('No new notifications'); + } + + /** + * Check for notifications from provisioner and show them locally + */ + async checkAndShowNotifications(): Promise { + try { + console.log("🔍 Checking for notifications from provisioner..."); + + // Get current device registration + let registration = this.getDeviceRegistration(); + + // If no registration, try to get eName from vault and register + if (!registration) { + console.log( + "No device registration found, attempting to register...", + ); + + // Try to get eName from vault + try { + const vault = await this.getVaultEname(); + if (vault?.ename) { + console.log( + `Found eName: ${vault.ename}, registering device...`, + ); + const registered = await this.registerDevice( + vault.ename, + ); + if (registered) { + registration = this.getDeviceRegistration(); + console.log("Device registered successfully"); + } else { + console.log("Failed to register device"); + return; + } + } else { + console.log( + "No eName found in vault, skipping notification check", + ); + return; + } + } catch (error) { + console.error("Error getting vault eName:", error); + return; + } + } + + if (!registration) { + console.log( + "Still no device registration, skipping notification check", + ); + return; + } + + // Check for notifications from provisioner + const response = await fetch( + `${this.provisionerEndpoint}/api/notifications/check`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + eName: registration.eName, + deviceId: registration.deviceId, + }), + }, + ); + + if (response.ok) { + const data = await response.json(); + if (data.notifications && data.notifications.length > 0) { + console.log( + `📱 Found ${data.notifications.length} notification(s)`, + ); + + // Show each notification locally + for (const notification of data.notifications) { + await this.sendLocalNotification(notification); + } + } else { + console.log("No new notifications"); + } + } else { + console.log( + "No notifications endpoint available or error:", + response.status, + ); + } + } catch (error) { + console.error("Error checking notifications:", error); } - } else { - console.log('No notifications endpoint available or error:', response.status); - } - } catch (error) { - console.error('Error checking notifications:', error); } - } - - /** - * Test notification - call this from browser console for debugging - */ - async testNotification(): Promise { - console.log('🧪 Testing notification...'); - await this.sendLocalNotification({ - title: 'Test Notification', - body: 'This is a test notification from eid-wallet!', - data: { test: true, timestamp: new Date().toISOString() } - }); - } - - /** - * Get current device registration info - */ - getDeviceRegistration(): DeviceRegistration | null { - return this.deviceRegistration; - } - - /** - * Check if device is registered - */ - isDeviceRegistered(): boolean { - return this.deviceRegistration !== null; - } - - /** - * Get device ID (using Tauri's device ID or generating one) - */ - private async getDeviceId(): Promise { - try { - // Try to get device ID from Tauri - const deviceId = await invoke('get_device_id'); - return deviceId; - } catch (error) { - // Fallback to generating a UUID - const { v4: uuidv4 } = await import('uuid'); - return uuidv4(); + + /** + * Test notification - call this from browser console for debugging + */ + async testNotification(): Promise { + console.log("🧪 Testing notification..."); + await this.sendLocalNotification({ + title: "Test Notification", + body: "This is a test notification from eid-wallet!", + data: { test: true, timestamp: new Date().toISOString() }, + }); + } + + /** + * Get current device registration info + */ + getDeviceRegistration(): DeviceRegistration | null { + return this.deviceRegistration; + } + + /** + * Check if device is registered + */ + isDeviceRegistered(): boolean { + return this.deviceRegistration !== null; } - } - - /** - * Get platform information - */ - private async getPlatform(): Promise<'android' | 'ios' | 'desktop'> { - try { - const platform = await invoke('get_platform'); - if (platform.includes('android')) return 'android'; - if (platform.includes('ios')) return 'ios'; - return 'desktop'; - } catch (error) { - // Fallback detection - const userAgent = navigator.userAgent.toLowerCase(); - if (userAgent.includes('android')) return 'android'; - if (userAgent.includes('iphone') || userAgent.includes('ipad')) return 'ios'; - return 'desktop'; + + /** + * Get device ID (using Tauri's device ID or generating one) + */ + private async getDeviceId(): Promise { + try { + // Try to get device ID from Tauri + const deviceId = await invoke("get_device_id"); + return deviceId; + } catch (error) { + // Fallback to generating a UUID + const { v4: uuidv4 } = await import("uuid"); + return uuidv4(); + } + } + + /** + * Get platform information + */ + private async getPlatform(): Promise<"android" | "ios" | "desktop"> { + try { + const platform = await invoke("get_platform"); + if (platform.includes("android")) return "android"; + if (platform.includes("ios")) return "ios"; + return "desktop"; + } catch (error) { + // Fallback detection + const userAgent = navigator.userAgent.toLowerCase(); + if (userAgent.includes("android")) return "android"; + if (userAgent.includes("iphone") || userAgent.includes("ipad")) + return "ios"; + return "desktop"; + } } - } - - /** - * Get FCM token for push notifications (mobile only) - */ - private async getFCMToken(): Promise { - try { - // This would need to be implemented with Firebase SDK - // For now, return undefined as we're focusing on local notifications - return undefined; - } catch (error) { - console.error('Failed to get FCM token:', error); - return undefined; + + /** + * Get FCM token for push notifications (mobile only) + */ + private async getFCMToken(): Promise { + try { + // This would need to be implemented with Firebase SDK + // For now, return undefined as we're focusing on local notifications + return undefined; + } catch (error) { + console.error("Failed to get FCM token:", error); + return undefined; + } } - } - /** - * Get eName from vault (helper method) - */ - private async getVaultEname(): Promise<{ ename: string } | null> { - try { - // Try to access vault through global state or store - // This is a simple approach - in a real app you might want to inject the vault controller - const store = await import('@tauri-apps/plugin-store').then(m => m.Store.load('global-state.json')); - const vault = await store.get<{ ename: string }>('vault'); - return vault; - } catch (error) { - console.error('Error getting vault eName:', error); - return null; + /** + * Get eName from vault (helper method) + */ + private async getVaultEname(): Promise<{ ename: string } | null> { + try { + // Try to access vault through global state or store + // This is a simple approach - in a real app you might want to inject the vault controller + const store = await import("@tauri-apps/plugin-store").then((m) => + m.Store.load("global-state.json"), + ); + const vault = await store.get<{ ename: string }>("vault"); + return vault; + } catch (error) { + console.error("Error getting vault eName:", error); + return null; + } } - } } export default NotificationService; diff --git a/infrastructure/eid-wallet/src/lib/ui/Drawer/Drawer.svelte b/infrastructure/eid-wallet/src/lib/ui/Drawer/Drawer.svelte index da6bcaed..78ab9122 100644 --- a/infrastructure/eid-wallet/src/lib/ui/Drawer/Drawer.svelte +++ b/infrastructure/eid-wallet/src/lib/ui/Drawer/Drawer.svelte @@ -1,63 +1,63 @@
- import { page } from "$app/state"; - import { goto } from "$app/navigation"; - import type { Snippet } from "svelte"; - import type { LayoutData } from "./$types"; - import type { GlobalState } from "$lib/global"; - import { getContext, onMount } from "svelte"; +import { page } from "$app/state"; +import { goto } from "$app/navigation"; +import type { Snippet } from "svelte"; +import type { LayoutData } from "./$types"; +import type { GlobalState } from "$lib/global"; +import { getContext, onMount } from "svelte"; - let { data, children }: { data: LayoutData; children: Snippet } = $props(); +let { data, children }: { data: LayoutData; children: Snippet } = $props(); - let currentRoute = $derived(page.url.pathname.split("/").pop() || "home"); - let globalState: GlobalState | undefined = $state(undefined); +let currentRoute = $derived(page.url.pathname.split("/").pop() || "home"); +let globalState: GlobalState | undefined = $state(undefined); - onMount(async () => { - // Get global state - globalState = getContext<() => GlobalState>("globalState")(); +onMount(async () => { + // Get global state + globalState = getContext<() => GlobalState>("globalState")(); - // Authentication guard for all app routes - try { - if (!globalState) { - console.log("No global state, redirecting to login"); - await goto("/login"); - return; - } + // Authentication guard for all app routes + try { + if (!globalState) { + console.log("No global state, redirecting to login"); + await goto("/login"); + return; + } - const vault = await globalState.vaultController.vault; - if (!vault) { - console.log("User not authenticated, redirecting to login"); - await goto("/login"); - return; - } + const vault = await globalState.vaultController.vault; + if (!vault) { + console.log("User not authenticated, redirecting to login"); + await goto("/login"); + return; + } - console.log("User authenticated, allowing access to app routes"); + console.log("User authenticated, allowing access to app routes"); - // Check for notifications after successful authentication - try { - const notificationService = globalState.notificationService; - await notificationService.checkAndShowNotifications(); - } catch (error) { - console.error("Failed to check notifications:", error); - } + // Check for notifications after successful authentication + try { + const notificationService = globalState.notificationService; + await notificationService.checkAndShowNotifications(); } catch (error) { - console.log("Authentication check failed, redirecting to login"); - await goto("/login"); - return; + console.error("Failed to check notifications:", error); } - }); + } catch (error) { + console.log("Authentication check failed, redirecting to login"); + await goto("/login"); + return; + } +}); - $effect(() => { - const isScanPage = currentRoute === "scan-qr"; - if (isScanPage) - return document.body.classList.add("custom-global-style"); - return document.body.classList.remove("custom-global-style"); - }); +$effect(() => { + const isScanPage = currentRoute === "scan-qr"; + if (isScanPage) return document.body.classList.add("custom-global-style"); + return document.body.classList.remove("custom-global-style"); +}); diff --git a/infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte b/infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte index 10846f69..b7706c0b 100644 --- a/infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte +++ b/infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte @@ -1,83 +1,82 @@ {#if profileCreationStatus === "loading"} diff --git a/infrastructure/eid-wallet/src/routes/(app)/scan-qr/+page.svelte b/infrastructure/eid-wallet/src/routes/(app)/scan-qr/+page.svelte index 815e472d..538bc7b7 100644 --- a/infrastructure/eid-wallet/src/routes/(app)/scan-qr/+page.svelte +++ b/infrastructure/eid-wallet/src/routes/(app)/scan-qr/+page.svelte @@ -1,1366 +1,1292 @@ diff --git a/infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte b/infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte index ef3706fb..754f8853 100644 --- a/infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte +++ b/infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte @@ -1,100 +1,99 @@
diff --git a/infrastructure/eid-wallet/src/routes/(auth)/onboarding/+page.svelte b/infrastructure/eid-wallet/src/routes/(auth)/onboarding/+page.svelte index 288cc2e8..ccd2cf6c 100644 --- a/infrastructure/eid-wallet/src/routes/(auth)/onboarding/+page.svelte +++ b/infrastructure/eid-wallet/src/routes/(auth)/onboarding/+page.svelte @@ -1,198 +1,193 @@
- import { goto } from "$app/navigation"; - import { - PUBLIC_PROVISIONER_URL, - PUBLIC_REGISTRY_URL, - } from "$env/static/public"; - import { Hero } from "$lib/fragments"; - import { GlobalState } from "$lib/global"; - import { ButtonAction } from "$lib/ui"; - import Drawer from "$lib/ui/Drawer/Drawer.svelte"; - import { capitalize } from "$lib/utils"; - import { KeyManagerFactory, type KeyManager } from "$lib/crypto"; - import axios from "axios"; - import { getContext, onMount } from "svelte"; - import { Shadow } from "svelte-loading-spinners"; - import { v4 as uuidv4 } from "uuid"; - import DocumentType from "./steps/document-type.svelte"; - import Passport from "./steps/passport.svelte"; - import Selfie from "./steps/selfie.svelte"; - import { - DocFront, - DocBack, - Selfie as SelfiePic, - reason, - status, - verifStep, - verificaitonId, - } from "./store"; +import { goto } from "$app/navigation"; +import { + PUBLIC_PROVISIONER_URL, + PUBLIC_REGISTRY_URL, +} from "$env/static/public"; +import { Hero } from "$lib/fragments"; +import { GlobalState } from "$lib/global"; +import { ButtonAction } from "$lib/ui"; +import Drawer from "$lib/ui/Drawer/Drawer.svelte"; +import { capitalize } from "$lib/utils"; +import { KeyManagerFactory, type KeyManager } from "$lib/crypto"; +import axios from "axios"; +import { getContext, onMount } from "svelte"; +import { Shadow } from "svelte-loading-spinners"; +import { v4 as uuidv4 } from "uuid"; +import DocumentType from "./steps/document-type.svelte"; +import Passport from "./steps/passport.svelte"; +import Selfie from "./steps/selfie.svelte"; +import { + DocFront, + DocBack, + Selfie as SelfiePic, + reason, + status, + verifStep, + verificaitonId, +} from "./store"; - type Document = { - country: { value: string }; - firstIssue: Date; - licenseNumber: string; - number: { - confidenceCategory: string; - value: string; - sources: string[]; - }; - placeOfIssue: string; - processNumber: string; - residencePermitType: string; - type: { value: string }; - validFrom: { - confidenceCategory: string; - value: string; - sources: string[]; - }; - validUntil: { - confidenceCategory: string; - value: string; - sources: string[]; - }; +type Document = { + country: { value: string }; + firstIssue: Date; + licenseNumber: string; + number: { + confidenceCategory: string; + value: string; + sources: string[]; }; - - type Person = { - address: { - confidenceCategory: string; - value: string; - components: Record; - sources: string[]; - }; - dateOfBirth: { - confidenceCategory: string; - value: string; - sources: string[]; - }; - employer: string; - extraNames: string; - firstName: { - confidenceCategory: string; - value: string; - sources: string[]; - }; - foreignerStatus: string; - gender: { - confidenceCategory: string; - value: string; - sources: string[]; - }; - idNumber: { - confidenceCategory: string; - value: string; - sources: string[]; - }; - lastName: { - confidenceCategory: string; - value: string; - sources: string[]; - }; - nationality: { - confidenceCategory: string; - value: string; - sources: string[]; - }; - occupation: string; - placeOfBirth: string; + placeOfIssue: string; + processNumber: string; + residencePermitType: string; + type: { value: string }; + validFrom: { + confidenceCategory: string; + value: string; + sources: string[]; }; + validUntil: { + confidenceCategory: string; + value: string; + sources: string[]; + }; +}; - let globalState: GlobalState | undefined = $state(undefined); - let showVeriffModal = $state(false); - let person: Person; - let document: Document; - let loading = $state(false); - let keyManager: KeyManager | null = $state(null); - let websocketData: { w3id?: string } | null = $state(null); // Store websocket data for duplicate case - let hardwareKeySupported = $state(false); - let hardwareKeyCheckComplete = $state(false); - - async function handleVerification() { - const { data } = await axios.post( - new URL("/verification", PUBLIC_PROVISIONER_URL).toString(), - ); - verificaitonId.set(data.id); - showVeriffModal = true; - watchEventStream(data.id); - } +type Person = { + address: { + confidenceCategory: string; + value: string; + components: Record; + sources: string[]; + }; + dateOfBirth: { + confidenceCategory: string; + value: string; + sources: string[]; + }; + employer: string; + extraNames: string; + firstName: { + confidenceCategory: string; + value: string; + sources: string[]; + }; + foreignerStatus: string; + gender: { + confidenceCategory: string; + value: string; + sources: string[]; + }; + idNumber: { + confidenceCategory: string; + value: string; + sources: string[]; + }; + lastName: { + confidenceCategory: string; + value: string; + sources: string[]; + }; + nationality: { + confidenceCategory: string; + value: string; + sources: string[]; + }; + occupation: string; + placeOfBirth: string; +}; - function watchEventStream(id: string) { - const sseUrl = new URL( - `/verification/sessions/${id}`, - PUBLIC_PROVISIONER_URL, - ).toString(); - const eventSource = new EventSource(sseUrl); +let globalState: GlobalState | undefined = $state(undefined); +let showVeriffModal = $state(false); +let person: Person; +let document: Document; +let loading = $state(false); +let keyManager: KeyManager | null = $state(null); +let websocketData: { w3id?: string } | null = $state(null); // Store websocket data for duplicate case +let hardwareKeySupported = $state(false); +let hardwareKeyCheckComplete = $state(false); - eventSource.onopen = () => { - console.log("Successfully connected."); - }; +async function handleVerification() { + const { data } = await axios.post( + new URL("/verification", PUBLIC_PROVISIONER_URL).toString(), + ); + verificaitonId.set(data.id); + showVeriffModal = true; + watchEventStream(data.id); +} - eventSource.onmessage = (e) => { - const data = JSON.parse(e.data as string); - if (!data.status) console.log(data); - console.log("STATUS", data); - status.set(data.status); - reason.set(data.reason); - person = data.person; - document = data.document; - websocketData = data; // Store the full websocket data - if (data.status === "resubmission_requested") { - DocFront.set(null); - DocBack.set(null); - SelfiePic.set(null); - } - verifStep.set(3); - }; - } +function watchEventStream(id: string) { + const sseUrl = new URL( + `/verification/sessions/${id}`, + PUBLIC_PROVISIONER_URL, + ).toString(); + const eventSource = new EventSource(sseUrl); - // Check if hardware key is supported on this device - async function checkHardwareKeySupport() { - try { - const hardwareKeyManager = - await KeyManagerFactory.getKeyManagerForContext( - "default", - "verification", - ); + eventSource.onopen = () => { + console.log("Successfully connected."); + }; - // Try to generate a test key to see if hardware is available - await hardwareKeyManager.generate("test-hardware-check"); - hardwareKeySupported = true; - console.log("Hardware key is supported on this device"); - } catch (error) { - hardwareKeySupported = false; - console.log("Hardware key is NOT supported on this device:", error); - } finally { - hardwareKeyCheckComplete = true; + eventSource.onmessage = (e) => { + const data = JSON.parse(e.data as string); + if (!data.status) console.log(data); + console.log("STATUS", data); + status.set(data.status); + reason.set(data.reason); + person = data.person; + document = data.document; + websocketData = data; // Store the full websocket data + if (data.status === "resubmission_requested") { + DocFront.set(null); + DocBack.set(null); + SelfiePic.set(null); } - } + verifStep.set(3); + }; +} - // Initialize key manager for verification context - async function initializeKeyManager() { - try { - keyManager = await KeyManagerFactory.getKeyManagerForContext( +// Check if hardware key is supported on this device +async function checkHardwareKeySupport() { + try { + const hardwareKeyManager = + await KeyManagerFactory.getKeyManagerForContext( "default", "verification", ); - console.log(`Key manager initialized: ${keyManager.getType()}`); - return keyManager; - } catch (error) { - console.error("Failed to initialize key manager:", error); - throw error; - } + + // Try to generate a test key to see if hardware is available + await hardwareKeyManager.generate("test-hardware-check"); + hardwareKeySupported = true; + console.log("Hardware key is supported on this device"); + } catch (error) { + hardwareKeySupported = false; + console.log("Hardware key is NOT supported on this device:", error); + } finally { + hardwareKeyCheckComplete = true; } +} - async function generateApplicationKeyPair() { - if (!keyManager) { - await initializeKeyManager(); - } +// Initialize key manager for verification context +async function initializeKeyManager() { + try { + keyManager = await KeyManagerFactory.getKeyManagerForContext( + "default", + "verification", + ); + console.log(`Key manager initialized: ${keyManager.getType()}`); + return keyManager; + } catch (error) { + console.error("Failed to initialize key manager:", error); + throw error; + } +} - if (!keyManager) { - throw new Error("Key manager not initialized"); - } +async function generateApplicationKeyPair() { + if (!keyManager) { + await initializeKeyManager(); + } - try { - const res = await keyManager.generate("default"); - console.log("Key generation result:", res); - return res; - } catch (e) { - console.error("Key generation failed:", e); - throw e; - } + if (!keyManager) { + throw new Error("Key manager not initialized"); } - async function getApplicationPublicKey() { - if (!keyManager) { - await initializeKeyManager(); - } + try { + const res = await keyManager.generate("default"); + console.log("Key generation result:", res); + return res; + } catch (e) { + console.error("Key generation failed:", e); + throw e; + } +} - if (!keyManager) { - throw new Error("Key manager not initialized"); - } +async function getApplicationPublicKey() { + if (!keyManager) { + await initializeKeyManager(); + } - try { - const res = await keyManager.getPublicKey("default"); - console.log("Public key retrieved:", res); - return res; - } catch (e) { - console.error("Public key retrieval failed:", e); - throw e; - } + if (!keyManager) { + throw new Error("Key manager not initialized"); } - let handleContinue: () => Promise = $state(async () => {}); + try { + const res = await keyManager.getPublicKey("default"); + console.log("Public key retrieved:", res); + return res; + } catch (e) { + console.error("Public key retrieval failed:", e); + throw e; + } +} - onMount(async () => { - globalState = getContext<() => GlobalState>("globalState")(); - // handle verification logic + sec user data in the store +let handleContinue: () => Promise = $state(async () => {}); - // Check hardware key support first - await checkHardwareKeySupport(); +onMount(async () => { + globalState = getContext<() => GlobalState>("globalState")(); + // handle verification logic + sec user data in the store - // Initialize key manager and check if default key pair exists - await initializeKeyManager(); - if (keyManager) { - const keyExists = await keyManager.exists("default"); - if (!keyExists) { - await generateApplicationKeyPair(); - } + // Check hardware key support first + await checkHardwareKeySupport(); + + // Initialize key manager and check if default key pair exists + await initializeKeyManager(); + if (keyManager) { + const keyExists = await keyManager.exists("default"); + if (!keyExists) { + await generateApplicationKeyPair(); } + } - handleContinue = async () => { - if ($status !== "approved" && $status !== "duplicate") - return verifStep.set(0); - if (!globalState) throw new Error("Global state is not defined"); + handleContinue = async () => { + if ($status !== "approved" && $status !== "duplicate") + return verifStep.set(0); + if (!globalState) throw new Error("Global state is not defined"); - loading = true; - globalState.userController.user = { - name: capitalize( - `${person.firstName.value} ${person.lastName.value ?? ""}`, - ), - "Date of Birth": new Date( - person.dateOfBirth.value, - ).toDateString(), - "ID submitted": - document.type.value === "passport" - ? `Passport - ${document.country.value}` - : document.type.value === "drivers_license" - ? `Driving License - ${document.country.value}` - : `ID Card - ${document.country.value}`, - "Document Number": document.number.value, - }; - globalState.userController.document = { - "Valid From": new Date(document.validFrom.value).toDateString(), - "Valid Until": new Date( - document.validUntil.value, - ).toDateString(), - "Verified On": new Date().toDateString(), - }; - globalState.userController.isFake = false; + loading = true; + globalState.userController.user = { + name: capitalize( + `${person.firstName.value} ${person.lastName.value ?? ""}`, + ), + "Date of Birth": new Date(person.dateOfBirth.value).toDateString(), + "ID submitted": + document.type.value === "passport" + ? `Passport - ${document.country.value}` + : document.type.value === "drivers_license" + ? `Driving License - ${document.country.value}` + : `ID Card - ${document.country.value}`, + "Document Number": document.number.value, + }; + globalState.userController.document = { + "Valid From": new Date(document.validFrom.value).toDateString(), + "Valid Until": new Date(document.validUntil.value).toDateString(), + "Verified On": new Date().toDateString(), + }; + globalState.userController.isFake = false; - if ($status === "duplicate") { - // For duplicate case, skip provision and resolve the existing eVault URI - // The w3id should be provided in the websocket data - const existingW3id = websocketData?.w3id; // This should come from the websocket data - if (!existingW3id) { - throw new Error("No w3id provided for duplicate eVault"); - } + if ($status === "duplicate") { + // For duplicate case, skip provision and resolve the existing eVault URI + // The w3id should be provided in the websocket data + const existingW3id = websocketData?.w3id; // This should come from the websocket data + if (!existingW3id) { + throw new Error("No w3id provided for duplicate eVault"); + } - // Resolve the eVault URI from the registry - const response = await axios.get( - new URL( - `resolve?w3id=${existingW3id}`, - PUBLIC_REGISTRY_URL, - ).toString(), - ); - // Skip profile creation for duplicates by setting status directly - globalState.vaultController.profileCreationStatus = "success"; - // For duplicates, just set the vault without triggering profile creation - // since the eVault already exists with a profile + // Resolve the eVault URI from the registry + const response = await axios.get( + new URL( + `resolve?w3id=${existingW3id}`, + PUBLIC_REGISTRY_URL, + ).toString(), + ); + // Skip profile creation for duplicates by setting status directly + globalState.vaultController.profileCreationStatus = "success"; + // For duplicates, just set the vault without triggering profile creation + // since the eVault already exists with a profile + globalState.vaultController.vault = { + uri: response.data.uri, + ename: existingW3id, + }; + } else { + // Normal flow for approved status + const { + data: { token: registryEntropy }, + } = await axios.get( + new URL("/entropy", PUBLIC_REGISTRY_URL).toString(), + ); + const { data } = await axios.post( + new URL("/provision", PUBLIC_PROVISIONER_URL).toString(), + { + registryEntropy, + namespace: uuidv4(), + verificationId: $verificaitonId, + publicKey: await getApplicationPublicKey(), + }, + ); + if (data.success === true) { + // Set vault in controller - this will trigger profile creation with retry logic globalState.vaultController.vault = { - uri: response.data.uri, - ename: existingW3id, + uri: data.uri, + ename: data.w3id, }; - } else { - // Normal flow for approved status - const { - data: { token: registryEntropy }, - } = await axios.get( - new URL("/entropy", PUBLIC_REGISTRY_URL).toString(), - ); - const { data } = await axios.post( - new URL("/provision", PUBLIC_PROVISIONER_URL).toString(), - { - registryEntropy, - namespace: uuidv4(), - verificationId: $verificaitonId, - publicKey: await getApplicationPublicKey(), - }, - ); - if (data.success === true) { - // Set vault in controller - this will trigger profile creation with retry logic - globalState.vaultController.vault = { - uri: data.uri, - ename: data.w3id, - }; - } } + } - setTimeout(() => { - goto("/register"); - }, 10_000); - }; - }); + setTimeout(() => { + goto("/register"); + }, 10_000); + }; +});
(); export const verificaitonId = writable(); export const status = writable(); export const reason = writable(); -export const documentType = writable<"passport" | "id" | "permit" | "dl" | null>(null); +export const documentType = writable< + "passport" | "id" | "permit" | "dl" | null +>(null); diff --git a/infrastructure/eid-wallet/src/routes/+layout.svelte b/infrastructure/eid-wallet/src/routes/+layout.svelte index 071b3c43..58bbc65e 100644 --- a/infrastructure/eid-wallet/src/routes/+layout.svelte +++ b/infrastructure/eid-wallet/src/routes/+layout.svelte @@ -1,441 +1,425 @@ {#if showSplashScreen} diff --git a/infrastructure/web3-adapter/src/db/index.d.ts b/infrastructure/web3-adapter/src/db/index.d.ts index 2b35a798..9b726973 100644 --- a/infrastructure/web3-adapter/src/db/index.d.ts +++ b/infrastructure/web3-adapter/src/db/index.d.ts @@ -1,2 +1,2 @@ export * from "./mapping.db"; -//# sourceMappingURL=index.d.ts.map \ No newline at end of file +//# sourceMappingURL=index.d.ts.map diff --git a/infrastructure/web3-adapter/src/db/index.js b/infrastructure/web3-adapter/src/db/index.js index c8fda5d5..1b7d0ad0 100644 --- a/infrastructure/web3-adapter/src/db/index.js +++ b/infrastructure/web3-adapter/src/db/index.js @@ -1,18 +1,39 @@ "use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __exportStar = (this && this.__exportStar) || function(m, exports) { - for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); -}; +var __createBinding = + (this && this.__createBinding) || + (Object.create + ? function (o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if ( + !desc || + ("get" in desc + ? !m.__esModule + : desc.writable || desc.configurable) + ) { + desc = { + enumerable: true, + get: function () { + return m[k]; + }, + }; + } + Object.defineProperty(o, k2, desc); + } + : function (o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; + }); +var __exportStar = + (this && this.__exportStar) || + function (m, exports) { + for (var p in m) + if ( + p !== "default" && + !Object.prototype.hasOwnProperty.call(exports, p) + ) + __createBinding(exports, m, p); + }; Object.defineProperty(exports, "__esModule", { value: true }); __exportStar(require("./mapping.db"), exports); -//# sourceMappingURL=index.js.map \ No newline at end of file +//# sourceMappingURL=index.js.map diff --git a/infrastructure/web3-adapter/src/db/mapping.db.d.ts b/infrastructure/web3-adapter/src/db/mapping.db.d.ts index 2e9d5073..1db51207 100644 --- a/infrastructure/web3-adapter/src/db/mapping.db.d.ts +++ b/infrastructure/web3-adapter/src/db/mapping.db.d.ts @@ -27,13 +27,15 @@ export declare class MappingDatabase { /** * Get all mappings */ - getAllMappings(): Promise>; + getAllMappings(): Promise< + Array<{ + localId: string; + globalId: string; + }> + >; /** * Close the database connection */ close(): void; } -//# sourceMappingURL=mapping.db.d.ts.map \ No newline at end of file +//# sourceMappingURL=mapping.db.d.ts.map diff --git a/infrastructure/web3-adapter/src/db/mapping.db.js b/infrastructure/web3-adapter/src/db/mapping.db.js index 74e34eca..3866ae0a 100644 --- a/infrastructure/web3-adapter/src/db/mapping.db.js +++ b/infrastructure/web3-adapter/src/db/mapping.db.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.MappingDatabase = void 0; const node_path_1 = require("node:path"); @@ -38,7 +40,9 @@ class MappingDatabase { async storeMapping(params) { // Validate inputs if (!params.localId || !params.globalId) { - throw new Error("Invalid mapping parameters: all fields are required"); + throw new Error( + "Invalid mapping parameters: all fields are required", + ); } console.log("storing mapping g:l", params.globalId, params.localId); // Check if mapping already exists @@ -46,8 +50,11 @@ class MappingDatabase { if (existingMapping) { return; } - await this.runAsync(`INSERT INTO id_mappings (local_id, global_id) - VALUES (?, ?)`, [params.localId, params.globalId]); + await this.runAsync( + `INSERT INTO id_mappings (local_id, global_id) + VALUES (?, ?)`, + [params.localId, params.globalId], + ); const storedMapping = await this.getGlobalId(params.localId); if (storedMapping !== params.globalId) { console.log("storedMappingError", storedMapping, params.globalId); @@ -63,12 +70,14 @@ class MappingDatabase { return null; } try { - const result = await this.getAsync(`SELECT global_id + const result = await this.getAsync( + `SELECT global_id FROM id_mappings - WHERE local_id = ?`, [localId]); + WHERE local_id = ?`, + [localId], + ); return result?.global_id ?? null; - } - catch (error) { + } catch (error) { console.error("Error getting global ID:", error); return null; } @@ -81,12 +90,14 @@ class MappingDatabase { return null; } try { - const result = await this.getAsync(`SELECT local_id + const result = await this.getAsync( + `SELECT local_id FROM id_mappings - WHERE global_id = ?`, [globalId]); + WHERE global_id = ?`, + [globalId], + ); return result?.local_id ?? null; - } - catch (error) { + } catch (error) { return null; } } @@ -97,8 +108,11 @@ class MappingDatabase { if (!localId) { return; } - await this.runAsync(`DELETE FROM id_mappings - WHERE local_id = ?`, [localId]); + await this.runAsync( + `DELETE FROM id_mappings + WHERE local_id = ?`, + [localId], + ); } /** * Get all mappings @@ -111,8 +125,7 @@ class MappingDatabase { localId: local_id, globalId: global_id, })); - } - catch (error) { + } catch (error) { return []; } } @@ -122,11 +135,10 @@ class MappingDatabase { close() { try { this.db.close(); - } - catch (error) { + } catch (error) { console.error("Error closing database connection:", error); } } } exports.MappingDatabase = MappingDatabase; -//# sourceMappingURL=mapping.db.js.map \ No newline at end of file +//# sourceMappingURL=mapping.db.js.map diff --git a/infrastructure/web3-adapter/src/evault/evault.d.ts b/infrastructure/web3-adapter/src/evault/evault.d.ts index b21ef228..2083d855 100644 --- a/infrastructure/web3-adapter/src/evault/evault.d.ts +++ b/infrastructure/web3-adapter/src/evault/evault.d.ts @@ -58,12 +58,15 @@ export declare class EVaultClient { /** * Get health status for all cached endpoints */ - getHealthStatus(): Record; + getHealthStatus(): Record< + string, + { + endpoint: string; + failures: number; + lastCheck: number; + isHealthy: boolean; + } + >; /** * Clear all cached clients (useful for testing or forcing fresh connections) */ @@ -73,4 +76,4 @@ export declare class EVaultClient { fetchMetaEnvelope(id: string, w3id: string): Promise; updateMetaEnvelopeById(id: string, envelope: MetaEnvelope): Promise; } -//# sourceMappingURL=evault.d.ts.map \ No newline at end of file +//# sourceMappingURL=evault.d.ts.map diff --git a/infrastructure/web3-adapter/src/evault/evault.js b/infrastructure/web3-adapter/src/evault/evault.js index 7ec57a7a..034b30e4 100644 --- a/infrastructure/web3-adapter/src/evault/evault.js +++ b/infrastructure/web3-adapter/src/evault/evault.js @@ -68,8 +68,7 @@ class EVaultClient { * Cleanup method to properly dispose of resources */ dispose() { - if (this.isDisposed) - return; + if (this.isDisposed) return; this.isDisposed = true; this.clients.clear(); this.endpoints.clear(); @@ -85,19 +84,18 @@ class EVaultClient { for (let attempt = 0; attempt <= maxRetries; attempt++) { try { return await operation(); - } - catch (error) { + } catch (error) { lastError = error; // Don't retry on the last attempt - if (attempt === maxRetries) - break; + if (attempt === maxRetries) break; // Don't retry on certain errors if (error instanceof Error) { - const isRetryable = !(error.message.includes("401") || + const isRetryable = !( + error.message.includes("401") || error.message.includes("403") || - error.message.includes("404")); - if (!isRetryable) - break; + error.message.includes("404") + ); + if (!isRetryable) break; } // Exponential backoff const delay = CONFIG.RETRY_DELAY * 2 ** attempt; @@ -113,17 +111,23 @@ class EVaultClient { */ async requestPlatformToken() { try { - const response = await fetch(new URL("/platforms/certification", this.registryUrl).toString(), { - method: "POST", - headers: { - "Content-Type": "application/json", + const response = await fetch( + new URL( + "/platforms/certification", + this.registryUrl, + ).toString(), + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ platform: this.platform }), }, - body: JSON.stringify({ platform: this.platform }), - }); + ); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } - const data = (await response.json()); + const data = await response.json(); const now = Date.now(); const expiresAt = data.expiresAt || now + 3600000; // Default 1 hour return { @@ -131,8 +135,7 @@ class EVaultClient { expiresAt, obtainedAt: now, }; - } - catch (error) { + } catch (error) { console.error("Error requesting platform token:", error); throw new Error("Failed to request platform token"); } @@ -141,8 +144,7 @@ class EVaultClient { * Checks if token needs refresh */ isTokenExpired() { - if (!this.tokenInfo) - return true; + if (!this.tokenInfo) return true; const now = Date.now(); const timeUntilExpiry = this.tokenInfo.expiresAt - now; return timeUntilExpiry <= CONFIG.TOKEN_REFRESH_THRESHOLD; @@ -161,14 +163,18 @@ class EVaultClient { try { const enrichedW3id = w3id.startsWith("@") ? w3id : `@${w3id}`; console.log("fetching endpoint for :", enrichedW3id); - const response = await fetch(new URL(`/resolve?w3id=${enrichedW3id}`, this.registryUrl).toString()); + const response = await fetch( + new URL( + `/resolve?w3id=${enrichedW3id}`, + this.registryUrl, + ).toString(), + ); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); return new URL("/graphql", data.uri).toString(); - } - catch (error) { + } catch (error) { console.error("Error resolving eVault endpoint:", error); throw new Error("Failed to resolve eVault endpoint"); } @@ -183,18 +189,24 @@ class EVaultClient { const endpoint = this.endpoints.get(w3id); // Check if the cached endpoint is still healthy if (await this.isEndpointHealthy(w3id, endpoint)) { - console.log('reusing existing client for w3id:', w3id, 'endpoint:', endpoint); + console.log( + "reusing existing client for w3id:", + w3id, + "endpoint:", + endpoint, + ); return client; - } - else { - console.log('cached endpoint is unhealthy, removing and re-resolving for w3id:', w3id); + } else { + console.log( + "cached endpoint is unhealthy, removing and re-resolving for w3id:", + w3id, + ); this.removeCachedClient(w3id); } } // Resolve endpoint for this specific w3id const endpoint = await this.resolveEndpoint(w3id).catch(() => null); - if (!endpoint) - throw new Error("Failed to resolve endpoint"); + if (!endpoint) throw new Error("Failed to resolve endpoint"); // Get platform token and create client with authorization header const token = await this.ensurePlatformToken(); const client = new graphql_request_1.GraphQLClient(endpoint, { @@ -208,7 +220,12 @@ class EVaultClient { // Initialize health check tracking this.healthCheckFailures.set(w3id, 0); this.lastHealthCheck.set(w3id, Date.now()); - console.log('created new client for w3id:', w3id, 'endpoint:', endpoint); + console.log( + "created new client for w3id:", + w3id, + "endpoint:", + endpoint, + ); return client; } /** @@ -217,7 +234,7 @@ class EVaultClient { async isEndpointHealthy(w3id, endpoint) { try { // Extract base URL from GraphQL endpoint - const baseUrl = endpoint.replace('/graphql', ''); + const baseUrl = endpoint.replace("/graphql", ""); // Check if we should perform health check (avoid too frequent checks) const now = Date.now(); const lastCheck = this.lastHealthCheck.get(w3id) || 0; @@ -228,11 +245,16 @@ class EVaultClient { } // Perform health check on the whois endpoint const healthCheckUrl = `${baseUrl}/whois`; - console.log(`Health checking endpoint for ${w3id}: ${healthCheckUrl}`); + console.log( + `Health checking endpoint for ${w3id}: ${healthCheckUrl}`, + ); const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), CONFIG.HEALTH_CHECK_TIMEOUT); + const timeoutId = setTimeout( + () => controller.abort(), + CONFIG.HEALTH_CHECK_TIMEOUT, + ); const response = await fetch(healthCheckUrl, { - method: 'HEAD', + method: "HEAD", signal: controller.signal, }); clearTimeout(timeoutId); @@ -241,13 +263,16 @@ class EVaultClient { this.healthCheckFailures.set(w3id, 0); this.lastHealthCheck.set(w3id, now); return true; + } else { + throw new Error( + `Health check failed with status: ${response.status}`, + ); } - else { - throw new Error(`Health check failed with status: ${response.status}`); - } - } - catch (error) { - console.log(`Health check failed for ${w3id}:`, error instanceof Error ? error.message : 'Unknown error'); + } catch (error) { + console.log( + `Health check failed for ${w3id}:`, + error instanceof Error ? error.message : "Unknown error", + ); // Increment failure count const currentFailures = this.healthCheckFailures.get(w3id) || 0; const newFailures = currentFailures + 1; @@ -255,7 +280,9 @@ class EVaultClient { this.lastHealthCheck.set(w3id, Date.now()); // If we've had too many consecutive failures, mark as unhealthy if (newFailures >= CONFIG.MAX_HEALTH_CHECK_FAILURES) { - console.log(`Endpoint for ${w3id} marked as unhealthy after ${newFailures} consecutive failures`); + console.log( + `Endpoint for ${w3id} marked as unhealthy after ${newFailures} consecutive failures`, + ); return false; } // Still allow some failures before marking as unhealthy @@ -279,18 +306,21 @@ class EVaultClient { const controller = new AbortController(); const timeoutId = setTimeout(() => { controller.abort(); - console.log(`GraphQL request timeout for ${w3id}, marking endpoint as unhealthy`); + console.log( + `GraphQL request timeout for ${w3id}, marking endpoint as unhealthy`, + ); this.removeCachedClient(w3id); }, CONFIG.GRAPHQL_TIMEOUT); try { const result = await operation(); clearTimeout(timeoutId); return result; - } - catch (error) { + } catch (error) { clearTimeout(timeoutId); - if (error instanceof Error && error.name === 'AbortError') { - throw new Error(`Request timeout after ${CONFIG.GRAPHQL_TIMEOUT}ms`); + if (error instanceof Error && error.name === "AbortError") { + throw new Error( + `Request timeout after ${CONFIG.GRAPHQL_TIMEOUT}ms`, + ); } throw error; } @@ -313,7 +343,9 @@ class EVaultClient { this.lastHealthCheck.set(w3id, 0); const isHealthy = await this.isEndpointHealthy(w3id, endpoint); if (!isHealthy) { - console.log(`Forced health check failed for ${w3id}, removing cached client`); + console.log( + `Forced health check failed for ${w3id}, removing cached client`, + ); this.removeCachedClient(w3id); } return isHealthy; @@ -340,7 +372,7 @@ class EVaultClient { * Clear all cached clients (useful for testing or forcing fresh connections) */ clearCache() { - console.log('Clearing all cached clients and endpoints'); + console.log("Clearing all cached clients and endpoints"); this.clients.clear(); this.endpoints.clear(); this.healthCheckFailures.clear(); @@ -351,19 +383,19 @@ class EVaultClient { const client = await this.ensureClient(envelope.w3id).catch(() => { return null; }); - if (!client) - return (0, uuid_1.v4)(); + if (!client) return (0, uuid_1.v4)(); console.log("sending to eVault: ", envelope.w3id); console.log("sending payload", envelope); - const response = await this.withTimeout(envelope.w3id, () => client.request(STORE_META_ENVELOPE, { - input: { - ontology: envelope.schemaId, - payload: envelope.data, - acl: ["*"], - }, - })).catch(() => null); - if (!response) - return (0, uuid_1.v4)(); + const response = await this.withTimeout(envelope.w3id, () => + client.request(STORE_META_ENVELOPE, { + input: { + ontology: envelope.schemaId, + payload: envelope.data, + acl: ["*"], + }, + }), + ).catch(() => null); + if (!response) return (0, uuid_1.v4)(); return response.storeMetaEnvelope.metaEnvelope.id; }); } @@ -372,14 +404,14 @@ class EVaultClient { const client = await this.ensureClient(w3id); const response = await client .request(STORE_META_ENVELOPE, { - input: { - ontology: "reference", - payload: { - _by_reference: referenceId, + input: { + ontology: "reference", + payload: { + _by_reference: referenceId, + }, + acl: ["*"], }, - acl: ["*"], - }, - }) + }) .catch(() => null); if (!response) { console.error("Failed to store reference"); @@ -396,8 +428,7 @@ class EVaultClient { w3id, }); return response.metaEnvelope; - } - catch (error) { + } catch (error) { console.error("Error fetching meta envelope:", error); throw error; } @@ -406,7 +437,9 @@ class EVaultClient { async updateMetaEnvelopeById(id, envelope) { return this.withRetry(async () => { console.log("sending to eVault", envelope.w3id); - const client = await this.ensureClient(envelope.w3id).catch(() => null); + const client = await this.ensureClient(envelope.w3id).catch( + () => null, + ); if (!client) throw new Error("Failed to establish client connection"); try { @@ -418,9 +451,11 @@ class EVaultClient { acl: ["*"], }, }; - const response = await client.request(UPDATE_META_ENVELOPE, variables); - } - catch (error) { + const response = await client.request( + UPDATE_META_ENVELOPE, + variables, + ); + } catch (error) { console.error("Error updating meta envelope:", error); throw error; } @@ -428,4 +463,4 @@ class EVaultClient { } } exports.EVaultClient = EVaultClient; -//# sourceMappingURL=evault.js.map \ No newline at end of file +//# sourceMappingURL=evault.js.map diff --git a/infrastructure/web3-adapter/src/evault/evault.ts b/infrastructure/web3-adapter/src/evault/evault.ts index 7a350da1..854683b2 100644 --- a/infrastructure/web3-adapter/src/evault/evault.ts +++ b/infrastructure/web3-adapter/src/evault/evault.ts @@ -129,7 +129,7 @@ export class EVaultClient { private endpoints: Map = new Map(); private tokenInfo: TokenInfo | null = null; private isDisposed = false; - + // Health check tracking private healthCheckFailures: Map = new Map(); private lastHealthCheck: Map = new Map(); @@ -256,10 +256,13 @@ export class EVaultClient { private async resolveEndpoint(w3id: string): Promise { try { - const enrichedW3id = w3id.startsWith("@") ? w3id : `@${w3id}` - console.log("fetching endpoint for :", enrichedW3id) + const enrichedW3id = w3id.startsWith("@") ? w3id : `@${w3id}`; + console.log("fetching endpoint for :", enrichedW3id); const response = await fetch( - new URL(`/resolve?w3id=${enrichedW3id}`, this.registryUrl).toString(), + new URL( + `/resolve?w3id=${enrichedW3id}`, + this.registryUrl, + ).toString(), ); if (!response.ok) { @@ -283,13 +286,21 @@ export class EVaultClient { if (this.clients.has(w3id)) { const client = this.clients.get(w3id)!; const endpoint = this.endpoints.get(w3id)!; - + // Check if the cached endpoint is still healthy if (await this.isEndpointHealthy(w3id, endpoint)) { - console.log('reusing existing client for w3id:', w3id, 'endpoint:', endpoint); + console.log( + "reusing existing client for w3id:", + w3id, + "endpoint:", + endpoint, + ); return client; } else { - console.log('cached endpoint is unhealthy, removing and re-resolving for w3id:', w3id); + console.log( + "cached endpoint is unhealthy, removing and re-resolving for w3id:", + w3id, + ); this.removeCachedClient(w3id); } } @@ -309,71 +320,90 @@ export class EVaultClient { // Cache the client and endpoint for this specific w3id this.clients.set(w3id, client); this.endpoints.set(w3id, endpoint); - + // Initialize health check tracking this.healthCheckFailures.set(w3id, 0); this.lastHealthCheck.set(w3id, Date.now()); - - console.log('created new client for w3id:', w3id, 'endpoint:', endpoint); + + console.log( + "created new client for w3id:", + w3id, + "endpoint:", + endpoint, + ); return client; } /** * Check if a cached endpoint is still healthy */ - private async isEndpointHealthy(w3id: string, endpoint: string): Promise { + private async isEndpointHealthy( + w3id: string, + endpoint: string, + ): Promise { try { // Extract base URL from GraphQL endpoint - const baseUrl = endpoint.replace('/graphql', ''); - + const baseUrl = endpoint.replace("/graphql", ""); + // Check if we should perform health check (avoid too frequent checks) const now = Date.now(); const lastCheck = this.lastHealthCheck.get(w3id) || 0; const timeSinceLastCheck = now - lastCheck; - + // Only check every 30 seconds to avoid performance impact if (timeSinceLastCheck < 30000) { return true; // Assume healthy if checked recently } - + // Perform health check on the whois endpoint const healthCheckUrl = `${baseUrl}/whois`; - console.log(`Health checking endpoint for ${w3id}: ${healthCheckUrl}`); - + console.log( + `Health checking endpoint for ${w3id}: ${healthCheckUrl}`, + ); + const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), CONFIG.HEALTH_CHECK_TIMEOUT); - + const timeoutId = setTimeout( + () => controller.abort(), + CONFIG.HEALTH_CHECK_TIMEOUT, + ); + const response = await fetch(healthCheckUrl, { - method: 'HEAD', + method: "HEAD", signal: controller.signal, }); - + clearTimeout(timeoutId); - + if (response.ok) { // Reset failure count on success this.healthCheckFailures.set(w3id, 0); this.lastHealthCheck.set(w3id, now); return true; } else { - throw new Error(`Health check failed with status: ${response.status}`); + throw new Error( + `Health check failed with status: ${response.status}`, + ); } - } catch (error) { - console.log(`Health check failed for ${w3id}:`, error instanceof Error ? error.message : 'Unknown error'); - + console.log( + `Health check failed for ${w3id}:`, + error instanceof Error ? error.message : "Unknown error", + ); + // Increment failure count const currentFailures = this.healthCheckFailures.get(w3id) || 0; const newFailures = currentFailures + 1; this.healthCheckFailures.set(w3id, newFailures); this.lastHealthCheck.set(w3id, Date.now()); - + // If we've had too many consecutive failures, mark as unhealthy if (newFailures >= CONFIG.MAX_HEALTH_CHECK_FAILURES) { - console.log(`Endpoint for ${w3id} marked as unhealthy after ${newFailures} consecutive failures`); + console.log( + `Endpoint for ${w3id} marked as unhealthy after ${newFailures} consecutive failures`, + ); return false; } - + // Still allow some failures before marking as unhealthy return true; } @@ -395,12 +425,14 @@ export class EVaultClient { */ private async withTimeout( w3id: string, - operation: () => Promise + operation: () => Promise, ): Promise { const controller = new AbortController(); const timeoutId = setTimeout(() => { controller.abort(); - console.log(`GraphQL request timeout for ${w3id}, marking endpoint as unhealthy`); + console.log( + `GraphQL request timeout for ${w3id}, marking endpoint as unhealthy`, + ); this.removeCachedClient(w3id); }, CONFIG.GRAPHQL_TIMEOUT); @@ -410,11 +442,13 @@ export class EVaultClient { return result; } catch (error) { clearTimeout(timeoutId); - - if (error instanceof Error && error.name === 'AbortError') { - throw new Error(`Request timeout after ${CONFIG.GRAPHQL_TIMEOUT}ms`); + + if (error instanceof Error && error.name === "AbortError") { + throw new Error( + `Request timeout after ${CONFIG.GRAPHQL_TIMEOUT}ms`, + ); } - + throw error; } } @@ -437,33 +471,38 @@ export class EVaultClient { // Force health check by clearing last check time this.lastHealthCheck.set(w3id, 0); - + const isHealthy = await this.isEndpointHealthy(w3id, endpoint); - + if (!isHealthy) { - console.log(`Forced health check failed for ${w3id}, removing cached client`); + console.log( + `Forced health check failed for ${w3id}, removing cached client`, + ); this.removeCachedClient(w3id); } - + return isHealthy; } /** * Get health status for all cached endpoints */ - public getHealthStatus(): Record { + public getHealthStatus(): Record< + string, + { + endpoint: string; + failures: number; + lastCheck: number; + isHealthy: boolean; + } + > { const status: Record = {}; - + for (const [w3id, endpoint] of this.endpoints) { const failures = this.healthCheckFailures.get(w3id) || 0; const lastCheck = this.lastHealthCheck.get(w3id) || 0; const isHealthy = failures < CONFIG.MAX_HEALTH_CHECK_FAILURES; - + status[w3id] = { endpoint, failures, @@ -471,7 +510,7 @@ export class EVaultClient { isHealthy, }; } - + return status; } @@ -479,7 +518,7 @@ export class EVaultClient { * Clear all cached clients (useful for testing or forcing fresh connections) */ public clearCache(): void { - console.log('Clearing all cached clients and endpoints'); + console.log("Clearing all cached clients and endpoints"); this.clients.clear(); this.endpoints.clear(); this.healthCheckFailures.clear(); @@ -493,7 +532,7 @@ export class EVaultClient { }); if (!client) return v4(); - console.log("sending to eVault: ", envelope.w3id) + console.log("sending to eVault: ", envelope.w3id); console.log("sending payload", envelope); const response = await this.withTimeout(envelope.w3id, () => @@ -503,7 +542,7 @@ export class EVaultClient { payload: envelope.data, acl: ["*"], }, - }) + }), ).catch(() => null); if (!response) return v4(); diff --git a/infrastructure/web3-adapter/src/index.d.ts b/infrastructure/web3-adapter/src/index.d.ts index c0878637..5e21dc14 100644 --- a/infrastructure/web3-adapter/src/index.d.ts +++ b/infrastructure/web3-adapter/src/index.d.ts @@ -8,7 +8,11 @@ import type { IMapping } from "./mapper/mapper.types"; * @param verificationCode - Optional verification code, defaults to demo code * @returns Promise with eVault details (w3id, uri) */ -export declare function spinUpEVault(registryUrl: string, provisionerUrl: string, verificationCode?: string): Promise<{ +export declare function spinUpEVault( + registryUrl: string, + provisionerUrl: string, + verificationCode?: string, +): Promise<{ w3id: string; uri: string; }>; @@ -20,15 +24,20 @@ export declare function spinUpEVault(registryUrl: string, provisionerUrl: string * @param verificationCode - Optional verification code, defaults to demo code * @returns Promise with eVault details (w3id, uri, manifestId) */ -export declare function createGroupEVault(registryUrl: string, provisionerUrl: string, groupData: { - name: string; - avatar?: string; - description?: string; - members: string[]; - admins: string[]; - owner: string; - charter?: string; -}, verificationCode?: string): Promise<{ +export declare function createGroupEVault( + registryUrl: string, + provisionerUrl: string, + groupData: { + name: string; + avatar?: string; + description?: string; + members: string[]; + admins: string[]; + owner: string; + charter?: string; + }, + verificationCode?: string, +): Promise<{ w3id: string; uri: string; manifestId: string; @@ -53,28 +62,37 @@ export declare class Web3Adapter { data: Record; tableName: string; participants?: string[]; - }): Promise<{ - id: string; - w3id: string; - schemaId: string; - data?: undefined; - } | { - id: string; - w3id: string; - data: Record; - schemaId: string; - } | undefined>; + }): Promise< + | { + id: string; + w3id: string; + schemaId: string; + data?: undefined; + } + | { + id: string; + w3id: string; + data: Record; + schemaId: string; + } + | undefined + >; fromGlobal(props: { data: Record; mapping: IMapping; - }): Promise>; + }): Promise< + Omit + >; /** * Spins up an eVault by getting entropy from registry and provisioning it * @param verificationCode - Optional verification code, defaults to demo code * @param provisionerUrl - Optional provisioner URL, defaults to config * @returns Promise with eVault details (w3id, uri) */ - spinUpEVault(verificationCode?: string, provisionerUrl?: string): Promise<{ + spinUpEVault( + verificationCode?: string, + provisionerUrl?: string, + ): Promise<{ w3id: string; uri: string; }>; @@ -85,18 +103,22 @@ export declare class Web3Adapter { * @param provisionerUrl - Optional provisioner URL, defaults to config * @returns Promise with eVault details (w3id, uri, manifestId) */ - createGroupEVault(groupData: { - name: string; - avatar?: string; - description?: string; - members: string[]; - admins: string[]; - owner: string; - charter?: string; - }, verificationCode?: string, provisionerUrl?: string): Promise<{ + createGroupEVault( + groupData: { + name: string; + avatar?: string; + description?: string; + members: string[]; + admins: string[]; + owner: string; + charter?: string; + }, + verificationCode?: string, + provisionerUrl?: string, + ): Promise<{ w3id: string; uri: string; manifestId: string; }>; } -//# sourceMappingURL=index.d.ts.map \ No newline at end of file +//# sourceMappingURL=index.d.ts.map diff --git a/infrastructure/web3-adapter/src/index.js b/infrastructure/web3-adapter/src/index.js index bc2b6ad6..bc0148a7 100644 --- a/infrastructure/web3-adapter/src/index.js +++ b/infrastructure/web3-adapter/src/index.js @@ -1,40 +1,71 @@ "use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || (function () { - var ownKeys = function(o) { - ownKeys = Object.getOwnPropertyNames || function (o) { - var ar = []; - for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; - return ar; +var __createBinding = + (this && this.__createBinding) || + (Object.create + ? function (o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if ( + !desc || + ("get" in desc + ? !m.__esModule + : desc.writable || desc.configurable) + ) { + desc = { + enumerable: true, + get: function () { + return m[k]; + }, + }; + } + Object.defineProperty(o, k2, desc); + } + : function (o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; + }); +var __setModuleDefault = + (this && this.__setModuleDefault) || + (Object.create + ? function (o, v) { + Object.defineProperty(o, "default", { + enumerable: true, + value: v, + }); + } + : function (o, v) { + o["default"] = v; + }); +var __importStar = + (this && this.__importStar) || + (function () { + var ownKeys = function (o) { + ownKeys = + Object.getOwnPropertyNames || + function (o) { + var ar = []; + for (var k in o) + if (Object.prototype.hasOwnProperty.call(o, k)) + ar[ar.length] = k; + return ar; + }; + return ownKeys(o); }; - return ownKeys(o); - }; - return function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); - __setModuleDefault(result, mod); - return result; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) + for (var k = ownKeys(mod), i = 0; i < k.length; i++) + if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; + })(); +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; }; -})(); -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; Object.defineProperty(exports, "__esModule", { value: true }); exports.Web3Adapter = void 0; exports.spinUpEVault = spinUpEVault; @@ -58,28 +89,38 @@ async function spinUpEVault(registryUrl, provisionerUrl, verificationCode) { const DEMO_CODE_W3DS = "d66b7138-538a-465f-a6ce-f6985854c3f4"; const finalVerificationCode = verificationCode || DEMO_CODE_W3DS; try { - const entropyResponse = await axios_1.default.get(new URL("/entropy", registryUrl).toString()); + const entropyResponse = await axios_1.default.get( + new URL("/entropy", registryUrl).toString(), + ); const registryEntropy = entropyResponse.data.token; const namespace = (0, uuid_1.v4)(); - const provisionResponse = await axios_1.default.post(new URL("/provision", provisionerUrl).toString(), { - registryEntropy, - namespace, - verificationId: finalVerificationCode, - publicKey: "0x0000000000000000000000000000000000000000" - }); + const provisionResponse = await axios_1.default.post( + new URL("/provision", provisionerUrl).toString(), + { + registryEntropy, + namespace, + verificationId: finalVerificationCode, + publicKey: "0x0000000000000000000000000000000000000000", + }, + ); if (!provisionResponse.data.success) { - throw new Error(`Failed to provision eVault: ${provisionResponse.data.message || "Unknown error"}`); + throw new Error( + `Failed to provision eVault: ${provisionResponse.data.message || "Unknown error"}`, + ); } return { w3id: provisionResponse.data.w3id, uri: provisionResponse.data.uri, }; - } - catch (error) { + } catch (error) { if (axios_1.default.isAxiosError(error)) { - throw new Error(`Failed to spin up eVault: ${error.response?.data?.message || error.message}`); + throw new Error( + `Failed to spin up eVault: ${error.response?.data?.message || error.message}`, + ); } - throw new Error(`Failed to spin up eVault: ${error instanceof Error ? error.message : "Unknown error"}`); + throw new Error( + `Failed to spin up eVault: ${error instanceof Error ? error.message : "Unknown error"}`, + ); } } /** @@ -90,31 +131,52 @@ async function spinUpEVault(registryUrl, provisionerUrl, verificationCode) { * @param verificationCode - Optional verification code, defaults to demo code * @returns Promise with eVault details (w3id, uri, manifestId) */ -async function createGroupEVault(registryUrl, provisionerUrl, groupData, verificationCode) { +async function createGroupEVault( + registryUrl, + provisionerUrl, + groupData, + verificationCode, +) { const DEMO_CODE_W3DS = "d66b7138-538a-465f-a6ce-f6985854c3f4"; const finalVerificationCode = verificationCode || DEMO_CODE_W3DS; try { // Step 1: Spin up the eVault - const evault = await spinUpEVault(registryUrl, provisionerUrl, finalVerificationCode); + const evault = await spinUpEVault( + registryUrl, + provisionerUrl, + finalVerificationCode, + ); // Step 2: Create GroupManifest with exponential backoff - const manifestId = await createGroupManifestWithRetry(registryUrl, evault.w3id, groupData); + const manifestId = await createGroupManifestWithRetry( + registryUrl, + evault.w3id, + groupData, + ); return { w3id: evault.w3id, uri: evault.uri, manifestId, }; - } - catch (error) { + } catch (error) { if (axios_1.default.isAxiosError(error)) { - throw new Error(`Failed to create group eVault: ${error.response?.data?.message || error.message}`); + throw new Error( + `Failed to create group eVault: ${error.response?.data?.message || error.message}`, + ); } - throw new Error(`Failed to create group eVault: ${error instanceof Error ? error.message : "Unknown error"}`); + throw new Error( + `Failed to create group eVault: ${error instanceof Error ? error.message : "Unknown error"}`, + ); } } /** * Create GroupManifest in eVault with exponential backoff retry mechanism */ -async function createGroupManifestWithRetry(registryUrl, w3id, groupData, maxRetries = 10) { +async function createGroupManifestWithRetry( + registryUrl, + w3id, + groupData, + maxRetries = 10, +) { const now = new Date().toISOString(); const groupManifest = { eName: w3id, @@ -130,10 +192,16 @@ async function createGroupManifestWithRetry(registryUrl, w3id, groupData, maxRet }; for (let attempt = 1; attempt <= maxRetries; attempt++) { try { - console.log(`Attempting to create GroupManifest in eVault (attempt ${attempt}/${maxRetries})`); - const response = await axios_1.default.get(new URL(`resolve?w3id=${w3id}`, registryUrl).toString()); + console.log( + `Attempting to create GroupManifest in eVault (attempt ${attempt}/${maxRetries})`, + ); + const response = await axios_1.default.get( + new URL(`resolve?w3id=${w3id}`, registryUrl).toString(), + ); const endpoint = new URL("/graphql", response.data.uri).toString(); - const { GraphQLClient } = await Promise.resolve().then(() => __importStar(require("graphql-request"))); + const { GraphQLClient } = await Promise.resolve().then(() => + __importStar(require("graphql-request")), + ); const client = new GraphQLClient(endpoint); const STORE_META_ENVELOPE = ` mutation StoreMetaEnvelope($input: MetaEnvelopeInput!) { @@ -154,13 +222,20 @@ async function createGroupManifestWithRetry(registryUrl, w3id, groupData, maxRet }, }); const manifestId = result.storeMetaEnvelope.metaEnvelope.id; - console.log("GroupManifest created successfully in eVault:", manifestId); + console.log( + "GroupManifest created successfully in eVault:", + manifestId, + ); return manifestId; - } - catch (error) { - console.error(`Failed to create GroupManifest in eVault (attempt ${attempt}/${maxRetries}):`, error); + } catch (error) { + console.error( + `Failed to create GroupManifest in eVault (attempt ${attempt}/${maxRetries}):`, + error, + ); if (attempt === maxRetries) { - console.error("Max retries reached, giving up on GroupManifest creation"); + console.error( + "Max retries reached, giving up on GroupManifest creation", + ); throw error; } // Wait before retrying (exponential backoff) @@ -178,14 +253,19 @@ class Web3Adapter { this.lockedIds = []; this.readPaths(); this.mappingDb = new db_1.MappingDatabase(config.dbPath); - this.evaultClient = new evault_1.EVaultClient(config.registryUrl, config.platform); + this.evaultClient = new evault_1.EVaultClient( + config.registryUrl, + config.platform, + ); this.platform = config.platform; } async readPaths() { const allRawFiles = await fs.readdir(this.config.schemasPath); const mappingFiles = allRawFiles.filter((p) => p.endsWith(".json")); for (const mappingFile of mappingFiles) { - const mappingFileContent = await fs.readFile(node_path_1.default.join(this.config.schemasPath, mappingFile)); + const mappingFileContent = await fs.readFile( + node_path_1.default.join(this.config.schemasPath, mappingFile), + ); const mappingParsed = JSON.parse(mappingFileContent.toString()); this.mapping[mappingParsed.tableName] = mappingParsed; } @@ -200,16 +280,14 @@ class Web3Adapter { async handleChange(props) { const { data, tableName, participants } = props; const existingGlobalId = await this.mappingDb.getGlobalId(data.id); - if (!this.mapping[tableName]) - return; + if (!this.mapping[tableName]) return; if (this.mapping[tableName].readOnly) { - // early return on mappings which are readonly so as to not + // early return on mappings which are readonly so as to not // sync any update to the eVault which is not warranted return; } if (existingGlobalId) { - if (this.lockedIds.includes(existingGlobalId)) - return; + if (this.lockedIds.includes(existingGlobalId)) return; const global = await (0, mapper_1.toGlobal)({ data, mapping: this.mapping[tableName], @@ -217,11 +295,11 @@ class Web3Adapter { }); this.evaultClient .updateMetaEnvelopeById(existingGlobalId, { - id: existingGlobalId, - w3id: global.ownerEvault, - data: global.data, - schemaId: this.mapping[tableName].schemaId, - }) + id: existingGlobalId, + w3id: global.ownerEvault, + data: global.data, + schemaId: this.mapping[tableName].schemaId, + }) .catch(() => console.error("failed to sync update")); logging_1.logger.info({ tableName, @@ -249,8 +327,7 @@ class Web3Adapter { schemaId: this.mapping[tableName].schemaId, }); console.log("created new meta-env", globalId); - } - else { + } else { return; } // Store the mapping @@ -259,15 +336,20 @@ class Web3Adapter { globalId, }); // Handle references for other participants - const otherEvaults = (participants ?? []).filter((i) => i !== global.ownerEvault); + const otherEvaults = (participants ?? []).filter( + (i) => i !== global.ownerEvault, + ); for (const evault of otherEvaults) { - await this.evaultClient.storeReference(`${global.ownerEvault}/${globalId}`, evault); + await this.evaultClient.storeReference( + `${global.ownerEvault}/${globalId}`, + evault, + ); } logging_1.logger.info({ tableName, id: globalId, w3id: global.ownerEvault, - platform: this.platform + platform: this.platform, }); return { id: globalId, @@ -292,11 +374,18 @@ class Web3Adapter { * @returns Promise with eVault details (w3id, uri) */ async spinUpEVault(verificationCode, provisionerUrl) { - const finalProvisionerUrl = provisionerUrl || this.config.provisionerUrl; + const finalProvisionerUrl = + provisionerUrl || this.config.provisionerUrl; if (!finalProvisionerUrl) { - throw new Error("Provisioner URL is required. Please provide it in config or as parameter."); + throw new Error( + "Provisioner URL is required. Please provide it in config or as parameter.", + ); } - return spinUpEVault(this.config.registryUrl, finalProvisionerUrl, verificationCode); + return spinUpEVault( + this.config.registryUrl, + finalProvisionerUrl, + verificationCode, + ); } /** * Creates a group eVault with GroupManifest @@ -306,12 +395,20 @@ class Web3Adapter { * @returns Promise with eVault details (w3id, uri, manifestId) */ async createGroupEVault(groupData, verificationCode, provisionerUrl) { - const finalProvisionerUrl = provisionerUrl || this.config.provisionerUrl; + const finalProvisionerUrl = + provisionerUrl || this.config.provisionerUrl; if (!finalProvisionerUrl) { - throw new Error("Provisioner URL is required. Please provide it in config or as parameter."); + throw new Error( + "Provisioner URL is required. Please provide it in config or as parameter.", + ); } - return createGroupEVault(this.config.registryUrl, finalProvisionerUrl, groupData, verificationCode); + return createGroupEVault( + this.config.registryUrl, + finalProvisionerUrl, + groupData, + verificationCode, + ); } } exports.Web3Adapter = Web3Adapter; -//# sourceMappingURL=index.js.map \ No newline at end of file +//# sourceMappingURL=index.js.map diff --git a/infrastructure/web3-adapter/src/index.ts b/infrastructure/web3-adapter/src/index.ts index 9a37170b..dcb4dc73 100644 --- a/infrastructure/web3-adapter/src/index.ts +++ b/infrastructure/web3-adapter/src/index.ts @@ -37,7 +37,7 @@ export async function spinUpEVault( registryEntropy, namespace, verificationId: finalVerificationCode, - publicKey: "0x0000000000000000000000000000000000000000" + publicKey: "0x0000000000000000000000000000000000000000", }, ); @@ -306,15 +306,13 @@ export class Web3Adapter { ); if (!this.mapping[tableName]) return; - + if (this.mapping[tableName].readOnly) { - // early return on mappings which are readonly so as to not + // early return on mappings which are readonly so as to not // sync any update to the eVault which is not warranted return; } - - if (existingGlobalId) { if (this.lockedIds.includes(existingGlobalId)) return; const global = await toGlobal({ @@ -339,7 +337,6 @@ export class Web3Adapter { w3id: global.ownerEvault, }); - return { id: existingGlobalId, w3id: global.ownerEvault as string, @@ -387,7 +384,7 @@ export class Web3Adapter { tableName, id: globalId, w3id: global.ownerEvault, - platform: this.platform + platform: this.platform, }); return { diff --git a/infrastructure/web3-adapter/src/logging/index.d.ts b/infrastructure/web3-adapter/src/logging/index.d.ts index 7fec87f5..e57b158d 100644 --- a/infrastructure/web3-adapter/src/logging/index.d.ts +++ b/infrastructure/web3-adapter/src/logging/index.d.ts @@ -1,3 +1,3 @@ export * from "./transport"; export * from "./logger"; -//# sourceMappingURL=index.d.ts.map \ No newline at end of file +//# sourceMappingURL=index.d.ts.map diff --git a/infrastructure/web3-adapter/src/logging/index.js b/infrastructure/web3-adapter/src/logging/index.js index 06388ce6..d8aecc9b 100644 --- a/infrastructure/web3-adapter/src/logging/index.js +++ b/infrastructure/web3-adapter/src/logging/index.js @@ -1,19 +1,40 @@ "use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __exportStar = (this && this.__exportStar) || function(m, exports) { - for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); -}; +var __createBinding = + (this && this.__createBinding) || + (Object.create + ? function (o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if ( + !desc || + ("get" in desc + ? !m.__esModule + : desc.writable || desc.configurable) + ) { + desc = { + enumerable: true, + get: function () { + return m[k]; + }, + }; + } + Object.defineProperty(o, k2, desc); + } + : function (o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; + }); +var __exportStar = + (this && this.__exportStar) || + function (m, exports) { + for (var p in m) + if ( + p !== "default" && + !Object.prototype.hasOwnProperty.call(exports, p) + ) + __createBinding(exports, m, p); + }; Object.defineProperty(exports, "__esModule", { value: true }); __exportStar(require("./transport"), exports); __exportStar(require("./logger"), exports); -//# sourceMappingURL=index.js.map \ No newline at end of file +//# sourceMappingURL=index.js.map diff --git a/infrastructure/web3-adapter/src/logging/logger.d.ts b/infrastructure/web3-adapter/src/logging/logger.d.ts index 93d8f742..ad6eed0a 100644 --- a/infrastructure/web3-adapter/src/logging/logger.d.ts +++ b/infrastructure/web3-adapter/src/logging/logger.d.ts @@ -1,3 +1,3 @@ import pino from "pino"; export declare const logger: pino.Logger; -//# sourceMappingURL=logger.d.ts.map \ No newline at end of file +//# sourceMappingURL=logger.d.ts.map diff --git a/infrastructure/web3-adapter/src/logging/logger.js b/infrastructure/web3-adapter/src/logging/logger.js index 71ce7443..7f90651b 100644 --- a/infrastructure/web3-adapter/src/logging/logger.js +++ b/infrastructure/web3-adapter/src/logging/logger.js @@ -1,10 +1,12 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.logger = void 0; const pino_1 = __importDefault(require("pino")); const transport_1 = require("./transport"); exports.logger = (0, pino_1.default)(transport_1.transport); -//# sourceMappingURL=logger.js.map \ No newline at end of file +//# sourceMappingURL=logger.js.map diff --git a/infrastructure/web3-adapter/src/logging/transport.d.ts b/infrastructure/web3-adapter/src/logging/transport.d.ts index a84a6199..159a90d5 100644 --- a/infrastructure/web3-adapter/src/logging/transport.d.ts +++ b/infrastructure/web3-adapter/src/logging/transport.d.ts @@ -1,2 +1,2 @@ export declare const transport: any; -//# sourceMappingURL=transport.d.ts.map \ No newline at end of file +//# sourceMappingURL=transport.d.ts.map diff --git a/infrastructure/web3-adapter/src/logging/transport.js b/infrastructure/web3-adapter/src/logging/transport.js index f7d515ac..3ec044a1 100644 --- a/infrastructure/web3-adapter/src/logging/transport.js +++ b/infrastructure/web3-adapter/src/logging/transport.js @@ -1,7 +1,9 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; Object.defineProperty(exports, "__esModule", { value: true }); exports.transport = void 0; const pino_1 = require("pino"); @@ -22,4 +24,4 @@ exports.transport = (0, pino_1.transport)({ }, }, }); -//# sourceMappingURL=transport.js.map \ No newline at end of file +//# sourceMappingURL=transport.js.map diff --git a/infrastructure/web3-adapter/src/logging/transport.ts b/infrastructure/web3-adapter/src/logging/transport.ts index ad08e272..58180489 100644 --- a/infrastructure/web3-adapter/src/logging/transport.ts +++ b/infrastructure/web3-adapter/src/logging/transport.ts @@ -3,8 +3,8 @@ import type { LokiOptions } from "pino-loki"; import dotenv from "dotenv"; import path from "path"; -const envPath = path.resolve(__dirname, "../../../../.env") -dotenv.config({ path: envPath}) +const envPath = path.resolve(__dirname, "../../../../.env"); +dotenv.config({ path: envPath }); export const transport = pinoTransport({ target: "pino-loki", diff --git a/infrastructure/web3-adapter/src/mapper/mapper.d.ts b/infrastructure/web3-adapter/src/mapper/mapper.d.ts index 09a3f57b..03f1183f 100644 --- a/infrastructure/web3-adapter/src/mapper/mapper.d.ts +++ b/infrastructure/web3-adapter/src/mapper/mapper.d.ts @@ -1,5 +1,19 @@ -import type { IMapperResponse, IMappingConversionOptions } from "./mapper.types"; -export declare function getValueByPath(obj: Record, path: string): any; -export declare function fromGlobal({ data, mapping, mappingStore, }: IMappingConversionOptions): Promise>; -export declare function toGlobal({ data, mapping, mappingStore, }: IMappingConversionOptions): Promise; -//# sourceMappingURL=mapper.d.ts.map \ No newline at end of file +import type { + IMapperResponse, + IMappingConversionOptions, +} from "./mapper.types"; +export declare function getValueByPath( + obj: Record, + path: string, +): any; +export declare function fromGlobal({ + data, + mapping, + mappingStore, +}: IMappingConversionOptions): Promise>; +export declare function toGlobal({ + data, + mapping, + mappingStore, +}: IMappingConversionOptions): Promise; +//# sourceMappingURL=mapper.d.ts.map diff --git a/infrastructure/web3-adapter/src/mapper/mapper.js b/infrastructure/web3-adapter/src/mapper/mapper.js index 68e27cca..59592cbe 100644 --- a/infrastructure/web3-adapter/src/mapper/mapper.js +++ b/infrastructure/web3-adapter/src/mapper/mapper.js @@ -14,7 +14,9 @@ function getValueByPath(obj, path) { } // If there's a field path after [], map through the array if (fieldPath) { - return array.map((item) => getValueByPath(item, fieldPath.slice(1))); // Remove the leading dot + return array.map((item) => + getValueByPath(item, fieldPath.slice(1)), + ); // Remove the leading dot } return array; } @@ -22,8 +24,7 @@ function getValueByPath(obj, path) { const parts = path.split("."); // biome-ignore lint/suspicious/noExplicitAny: return parts.reduce((acc, part) => { - if (acc === null || acc === undefined) - return undefined; + if (acc === null || acc === undefined) return undefined; return acc[part]; }, obj); } @@ -45,22 +46,33 @@ async function extractOwnerEvault(data, ownerEnamePath) { } // Check if the path contains fallback operator (||) if (ownerEnamePath.includes("||")) { - const paths = ownerEnamePath.split("||").map(path => path.trim()).filter(path => path.length > 0); + const paths = ownerEnamePath + .split("||") + .map((path) => path.trim()) + .filter((path) => path.length > 0); if (paths.length < 2) { - console.warn("Invalid fallback path format. Expected 'path1||path2' but got:", ownerEnamePath); + console.warn( + "Invalid fallback path format. Expected 'path1||path2' but got:", + ownerEnamePath, + ); return null; } - console.log(`Processing fallback paths for owner eVault: [${paths.join(", ")}]`); + console.log( + `Processing fallback paths for owner eVault: [${paths.join(", ")}]`, + ); // Try each path in order until one succeeds for (let i = 0; i < paths.length; i++) { const path = paths[i]; - console.log(`Trying fallback path ${i + 1}/${paths.length}: ${path}`); + console.log( + `Trying fallback path ${i + 1}/${paths.length}: ${path}`, + ); const result = await extractOwnerEvaultSinglePath(data, path); if (result !== null) { - console.log(`✅ Owner eVault found using fallback path ${i + 1}: ${path}`); + console.log( + `✅ Owner eVault found using fallback path ${i + 1}: ${path}`, + ); return result; - } - else { + } else { console.log(`❌ Fallback path ${i + 1} failed: ${path}`); } } @@ -82,18 +94,23 @@ async function extractOwnerEvaultSinglePath(data, ownerEnamePath) { const [_, fieldPathRaw] = ownerEnamePath.split("("); const fieldPath = fieldPathRaw.replace(")", ""); let value = getValueByPath(data, fieldPath); - if (Array.isArray(value)) - return value[0]; + if (Array.isArray(value)) return value[0]; console.log("OWNER PATH", value); // Check if value is a string before calling .includes() - if (typeof value === "string" && value.includes("(") && value.includes(")")) { + if ( + typeof value === "string" && + value.includes("(") && + value.includes(")") + ) { value = value.split("(")[1].split(")")[0]; } return value || null; } -async function fromGlobal({ data, mapping, mappingStore, }) { +async function fromGlobal({ data, mapping, mappingStore }) { const result = {}; - for (const [localKey, globalPathRaw] of Object.entries(mapping.localToUniversalMap)) { + for (const [localKey, globalPathRaw] of Object.entries( + mapping.localToUniversalMap, + )) { let value; const targetKey = localKey; let tableRef = null; @@ -103,42 +120,43 @@ async function fromGlobal({ data, mapping, mappingStore, }) { if (outerFn === "date") { const calcMatch = innerExpr.match(/^calc\((.+)\)$/); if (calcMatch) { - const calcResult = evaluateCalcExpression(calcMatch[1], data); + const calcResult = evaluateCalcExpression( + calcMatch[1], + data, + ); value = calcResult !== undefined ? new Date(calcResult).toISOString() : undefined; - } - else { + } else { const rawVal = getValueByPath(data, innerExpr); if (typeof rawVal === "number") { value = new Date(rawVal).toISOString(); - } - else if (rawVal?._seconds) { + } else if (rawVal?._seconds) { // Handle Firebase v8 timestamp format value = new Date(rawVal._seconds * 1000).toISOString(); - } - else if (rawVal?.seconds) { + } else if (rawVal?.seconds) { // Handle Firebase v9+ timestamp format value = new Date(rawVal.seconds * 1000).toISOString(); - } - else if (rawVal?.toDate && typeof rawVal.toDate === 'function') { + } else if ( + rawVal?.toDate && + typeof rawVal.toDate === "function" + ) { // Handle Firebase Timestamp objects value = rawVal.toDate().toISOString(); - } - else if (rawVal instanceof Date) { + } else if (rawVal instanceof Date) { value = rawVal.toISOString(); - } - else if (typeof rawVal === 'string' && rawVal.includes('UTC')) { + } else if ( + typeof rawVal === "string" && + rawVal.includes("UTC") + ) { // Handle Firebase timestamp strings like "August 18, 2025 at 10:03:19 AM UTC+5:30" value = new Date(rawVal).toISOString(); - } - else { + } else { value = undefined; } } - } - else if (outerFn === "calc") { + } else if (outerFn === "calc") { value = evaluateCalcExpression(innerExpr, data); } result[targetKey] = value; @@ -154,12 +172,13 @@ async function fromGlobal({ data, mapping, mappingStore, }) { value = getValueByPath(data, pathRef); if (tableRef) { if (Array.isArray(value)) { - value = await Promise.all(value.map(async (v) => { - const localId = await mappingStore.getLocalId(v); - return localId ? `${tableRef}(${localId})` : null; - })); - } - else { + value = await Promise.all( + value.map(async (v) => { + const localId = await mappingStore.getLocalId(v); + return localId ? `${tableRef}(${localId})` : null; + }), + ); + } else { value = await mappingStore.getLocalId(value); value = value ? `${tableRef}(${value})` : null; } @@ -170,9 +189,11 @@ async function fromGlobal({ data, mapping, mappingStore, }) { data: result, }; } -function evaluateCalcExpression(expr, -// biome-ignore lint/suspicious/noExplicitAny: -context) { +function evaluateCalcExpression( + expr, + // biome-ignore lint/suspicious/noExplicitAny: + context, +) { const tokens = expr .split(/[^\w.]+/) .map((t) => t.trim()) @@ -181,19 +202,23 @@ context) { for (const token of tokens) { const value = getValueByPath(context, token); if (typeof value !== "undefined") { - resolvedExpr = resolvedExpr.replace(new RegExp(`\\b${token.replace(".", "\\.")}\\b`, "g"), value); + resolvedExpr = resolvedExpr.replace( + new RegExp(`\\b${token.replace(".", "\\.")}\\b`, "g"), + value, + ); } } try { return Function(`use strict"; return (${resolvedExpr})`)(); - } - catch { + } catch { return undefined; } } -async function toGlobal({ data, mapping, mappingStore, }) { +async function toGlobal({ data, mapping, mappingStore }) { const result = {}; - for (const [localKey, globalPathRaw] of Object.entries(mapping.localToUniversalMap)) { + for (const [localKey, globalPathRaw] of Object.entries( + mapping.localToUniversalMap, + )) { // biome-ignore lint/suspicious/noExplicitAny: let value; let targetKey = globalPathRaw; @@ -219,42 +244,43 @@ async function toGlobal({ data, mapping, mappingStore, }) { if (outerFn === "date") { const calcMatch = innerExpr.match(/^calc\((.+)\)$/); if (calcMatch) { - const calcResult = evaluateCalcExpression(calcMatch[1], data); + const calcResult = evaluateCalcExpression( + calcMatch[1], + data, + ); value = calcResult !== undefined ? new Date(calcResult).toISOString() : undefined; - } - else { + } else { const rawVal = getValueByPath(data, innerExpr); if (typeof rawVal === "number") { value = new Date(rawVal).toISOString(); - } - else if (rawVal?._seconds) { + } else if (rawVal?._seconds) { // Handle Firebase v8 timestamp format value = new Date(rawVal._seconds * 1000).toISOString(); - } - else if (rawVal?.seconds) { + } else if (rawVal?.seconds) { // Handle Firebase v9+ timestamp format value = new Date(rawVal.seconds * 1000).toISOString(); - } - else if (rawVal?.toDate && typeof rawVal.toDate === 'function') { + } else if ( + rawVal?.toDate && + typeof rawVal.toDate === "function" + ) { // Handle Firebase Timestamp objects value = rawVal.toDate().toISOString(); - } - else if (rawVal instanceof Date) { + } else if (rawVal instanceof Date) { value = rawVal.toISOString(); - } - else if (typeof rawVal === 'string' && rawVal.includes('UTC')) { + } else if ( + typeof rawVal === "string" && + rawVal.includes("UTC") + ) { // Handle Firebase timestamp strings like "August 18, 2025 at 10:03:19 AM UTC+5:30" value = new Date(rawVal).toISOString(); - } - else { + } else { value = undefined; } } - } - else if (outerFn === "calc") { + } else if (outerFn === "calc") { value = evaluateCalcExpression(innerExpr, data); } result[targetKey] = value; @@ -268,16 +294,13 @@ async function toGlobal({ data, mapping, mappingStore, }) { value = Array.isArray(refValue) ? refValue.map((v) => `@${v}`) : []; - } - else { + } else { value = refValue ? `@${refValue}` : undefined; } result[targetKey] = value; continue; } - let pathRef = globalPathRaw.includes(",") - ? globalPathRaw - : localKey; + let pathRef = globalPathRaw.includes(",") ? globalPathRaw : localKey; let tableRef = null; if (globalPathRaw.includes("(") && globalPathRaw.includes(")")) { pathRef = globalPathRaw.split("(")[1].split(")")[0]; @@ -289,9 +312,13 @@ async function toGlobal({ data, mapping, mappingStore, }) { value = getValueByPath(data, pathRef); if (tableRef) { if (Array.isArray(value)) { - value = await Promise.all(value.map(async (v) => (await mappingStore.getGlobalId(v)) ?? undefined)); - } - else { + value = await Promise.all( + value.map( + async (v) => + (await mappingStore.getGlobalId(v)) ?? undefined, + ), + ); + } else { value = (await mappingStore.getGlobalId(value)) ?? undefined; } } @@ -303,4 +330,4 @@ async function toGlobal({ data, mapping, mappingStore, }) { data: result, }; } -//# sourceMappingURL=mapper.js.map \ No newline at end of file +//# sourceMappingURL=mapper.js.map diff --git a/infrastructure/web3-adapter/src/mapper/mapper.ts b/infrastructure/web3-adapter/src/mapper/mapper.ts index 21c9f13f..94cc945e 100644 --- a/infrastructure/web3-adapter/src/mapper/mapper.ts +++ b/infrastructure/web3-adapter/src/mapper/mapper.ts @@ -36,7 +36,7 @@ export function getValueByPath(obj: Record, path: string): any { /** * Extracts the owner eVault from data using the specified path(s). * Supports fallback paths using the || operator. - * + * * @param data - The data object to extract from * @param ownerEnamePath - The path(s) to extract from. Can be: * - Single path: "owner.ename" @@ -55,29 +55,41 @@ async function extractOwnerEvault( // Check if the path contains fallback operator (||) if (ownerEnamePath.includes("||")) { - const paths = ownerEnamePath.split("||").map(path => path.trim()).filter(path => path.length > 0); - + const paths = ownerEnamePath + .split("||") + .map((path) => path.trim()) + .filter((path) => path.length > 0); + if (paths.length < 2) { - console.warn("Invalid fallback path format. Expected 'path1||path2' but got:", ownerEnamePath); + console.warn( + "Invalid fallback path format. Expected 'path1||path2' but got:", + ownerEnamePath, + ); return null; } - - console.log(`Processing fallback paths for owner eVault: [${paths.join(", ")}]`); - + + console.log( + `Processing fallback paths for owner eVault: [${paths.join(", ")}]`, + ); + // Try each path in order until one succeeds for (let i = 0; i < paths.length; i++) { const path = paths[i]; - console.log(`Trying fallback path ${i + 1}/${paths.length}: ${path}`); - + console.log( + `Trying fallback path ${i + 1}/${paths.length}: ${path}`, + ); + const result = await extractOwnerEvaultSinglePath(data, path); if (result !== null) { - console.log(`✅ Owner eVault found using fallback path ${i + 1}: ${path}`); + console.log( + `✅ Owner eVault found using fallback path ${i + 1}: ${path}`, + ); return result; } else { console.log(`❌ Fallback path ${i + 1} failed: ${path}`); } } - + // If all paths fail, return null console.log("❌ All fallback paths failed for owner eVault"); return null; @@ -104,12 +116,16 @@ async function extractOwnerEvaultSinglePath( let value = getValueByPath(data, fieldPath); if (Array.isArray(value)) return value[0]; console.log("OWNER PATH", value); - + // Check if value is a string before calling .includes() - if (typeof value === "string" && value.includes("(") && value.includes(")")) { + if ( + typeof value === "string" && + value.includes("(") && + value.includes(")") + ) { value = value.split("(")[1].split(")")[0]; } - + return (value as string) || null; } @@ -152,12 +168,18 @@ export async function fromGlobal({ } else if (rawVal?.seconds) { // Handle Firebase v9+ timestamp format value = new Date(rawVal.seconds * 1000).toISOString(); - } else if (rawVal?.toDate && typeof rawVal.toDate === 'function') { + } else if ( + rawVal?.toDate && + typeof rawVal.toDate === "function" + ) { // Handle Firebase Timestamp objects value = rawVal.toDate().toISOString(); } else if (rawVal instanceof Date) { value = rawVal.toISOString(); - } else if (typeof rawVal === 'string' && rawVal.includes('UTC')) { + } else if ( + typeof rawVal === "string" && + rawVal.includes("UTC") + ) { // Handle Firebase timestamp strings like "August 18, 2025 at 10:03:19 AM UTC+5:30" value = new Date(rawVal).toISOString(); } else { @@ -290,12 +312,18 @@ export async function toGlobal({ } else if (rawVal?.seconds) { // Handle Firebase v9+ timestamp format value = new Date(rawVal.seconds * 1000).toISOString(); - } else if (rawVal?.toDate && typeof rawVal.toDate === 'function') { + } else if ( + rawVal?.toDate && + typeof rawVal.toDate === "function" + ) { // Handle Firebase Timestamp objects value = rawVal.toDate().toISOString(); } else if (rawVal instanceof Date) { value = rawVal.toISOString(); - } else if (typeof rawVal === 'string' && rawVal.includes('UTC')) { + } else if ( + typeof rawVal === "string" && + rawVal.includes("UTC") + ) { // Handle Firebase timestamp strings like "August 18, 2025 at 10:03:19 AM UTC+5:30" value = new Date(rawVal).toISOString(); } else { diff --git a/infrastructure/web3-adapter/src/mapper/mapper.types.d.ts b/infrastructure/web3-adapter/src/mapper/mapper.types.d.ts index e233dd4c..f597d38b 100644 --- a/infrastructure/web3-adapter/src/mapper/mapper.types.d.ts +++ b/infrastructure/web3-adapter/src/mapper/mapper.types.d.ts @@ -44,4 +44,4 @@ export interface IMapperResponse { ownerEvault: string | null; data: Record; } -//# sourceMappingURL=mapper.types.d.ts.map \ No newline at end of file +//# sourceMappingURL=mapper.types.d.ts.map diff --git a/infrastructure/web3-adapter/src/mapper/mapper.types.js b/infrastructure/web3-adapter/src/mapper/mapper.types.js index ea11c714..5e6cd2c9 100644 --- a/infrastructure/web3-adapter/src/mapper/mapper.types.js +++ b/infrastructure/web3-adapter/src/mapper/mapper.types.js @@ -1,3 +1,3 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -//# sourceMappingURL=mapper.types.js.map \ No newline at end of file +//# sourceMappingURL=mapper.types.js.map diff --git a/mappings/.gitignore b/mappings/.gitignore new file mode 100644 index 00000000..73edf032 --- /dev/null +++ b/mappings/.gitignore @@ -0,0 +1,2 @@ +!.gitignore +* \ No newline at end of file diff --git a/evoting.compose.yml b/metastate.compose.yml similarity index 52% rename from evoting.compose.yml rename to metastate.compose.yml index 946a5bb4..8eb6688e 100644 --- a/evoting.compose.yml +++ b/metastate.compose.yml @@ -1,11 +1,11 @@ services: db: image: postgres:16 - container_name: dev-evoting-db + container_name: dev-metastate-db environment: - POSTGRES_USER: evoting - POSTGRES_PASSWORD: evoting - POSTGRES_DB: evoting + POSTGRES_USER: metastate + POSTGRES_PASSWORD: metastate + POSTGRES_DB: metastate volumes: - ./db/data:/var/lib/postgresql/data ports: diff --git a/package.json b/package.json index 529c98ee..8f7ac459 100644 --- a/package.json +++ b/package.json @@ -24,11 +24,13 @@ "pnpm": { "onlyBuiltDependencies": [ "@biomejs/biome", + "@parcel/watcher", "cpu-features", "es5-ext", "esbuild", "msw", "protobufjs", + "sqlite3", "ssh2", "svelte-preprocess" ] diff --git a/platforms/blabsy/next.config.js b/platforms/blabsy/next.config.js index 66a87635..6c82888a 100644 --- a/platforms/blabsy/next.config.js +++ b/platforms/blabsy/next.config.js @@ -1,13 +1,13 @@ /** @type {import('next').NextConfig} */ const nextConfig = { - reactStrictMode: true, - swcMinify: true, - images: { - unoptimized: true - }, - eslint: { - ignoreDuringBuilds: true, - } + reactStrictMode: true, + swcMinify: true, + images: { + unoptimized: true + }, + eslint: { + ignoreDuringBuilds: true + } }; module.exports = nextConfig; diff --git a/platforms/blabsy/package.json b/platforms/blabsy/package.json index c8f64ef8..c8e48980 100644 --- a/platforms/blabsy/package.json +++ b/platforms/blabsy/package.json @@ -22,7 +22,7 @@ "date-fns": "^4.1.0", "firebase": "^9.9.4", "firebase-admin": "^13.4.0", - "framer-motion": "^7.2.1", + "motion": "^12.0.0", "next": "^12.3.0", "react": "18.2.0", "react-dom": "18.2.0", @@ -37,8 +37,8 @@ "@testing-library/react": "^13.3.0", "@testing-library/user-event": "^13.5.0", "@types/node": "18.6.4", - "@types/react": "18.0.16", - "@types/react-dom": "18.0.6", + "@types/react": "18.2.79", + "@types/react-dom": "18.2.25", "@typescript-eslint/eslint-plugin": "^5.32.0", "@typescript-eslint/parser": "^5.32.0", "autoprefixer": "^10.4.8", @@ -56,7 +56,7 @@ "prettier-plugin-tailwindcss": "^0.1.13", "sass": "^1.54.4", "tailwindcss": "^3.2.4", - "typescript": "4.7.4" + "typescript": "5.0.4" }, "lint-staged": { "**/*": "prettier --write --ignore-unknown" diff --git a/platforms/blabsy/src/components/aside/aside-trends.tsx b/platforms/blabsy/src/components/aside/aside-trends.tsx index 95be8e0d..ff776d0d 100644 --- a/platforms/blabsy/src/components/aside/aside-trends.tsx +++ b/platforms/blabsy/src/components/aside/aside-trends.tsx @@ -1,6 +1,7 @@ import Link from 'next/link'; +import React from 'react'; import cn from 'clsx'; -import { motion } from 'framer-motion'; +import { motion } from 'motion/react'; import { formatNumber } from '@lib/date'; import { preventBubbling } from '@lib/utils'; import { useTrends } from '@lib/api/trends'; @@ -9,9 +10,8 @@ import { HeroIcon } from '@components/ui/hero-icon'; import { Button } from '@components/ui/button'; import { ToolTip } from '@components/ui/tooltip'; import { Loading } from '@components/ui/loading'; -import type { MotionProps } from 'framer-motion'; -export const variants: MotionProps = { +export const variants = { initial: { opacity: 0 }, animate: { opacity: 1 }, transition: { duration: 0.8 } @@ -21,7 +21,7 @@ type AsideTrendsProps = { inTrendsPage?: boolean; }; -export function AsideTrends({ inTrendsPage }: AsideTrendsProps): JSX.Element { +export function AsideTrends({ inTrendsPage }: AsideTrendsProps) { const { data, loading } = useTrends(1, inTrendsPage ? 100 : 10, { refreshInterval: 30000 }); @@ -39,11 +39,13 @@ export function AsideTrends({ inTrendsPage }: AsideTrendsProps): JSX.Element { ) : trends ? ( {!inTrendsPage && (

diff --git a/platforms/blabsy/src/components/aside/suggestions.tsx b/platforms/blabsy/src/components/aside/suggestions.tsx index 709bfe1d..3895d972 100644 --- a/platforms/blabsy/src/components/aside/suggestions.tsx +++ b/platforms/blabsy/src/components/aside/suggestions.tsx @@ -1,5 +1,5 @@ import Link from 'next/link'; -import { motion } from 'framer-motion'; +import { motion } from 'motion/react'; import { doc, limit, diff --git a/platforms/blabsy/src/components/chat/add-members.tsx b/platforms/blabsy/src/components/chat/add-members.tsx index 11216b0b..764c822c 100644 --- a/platforms/blabsy/src/components/chat/add-members.tsx +++ b/platforms/blabsy/src/components/chat/add-members.tsx @@ -183,7 +183,8 @@ export function AddMembers({ !selectedUsers.some( (selected) => selected.id === userData.id ) && // Exclude already selected - (newChat || !currentChat?.participants.includes(userData.id)) && // Only exclude existing participants if NOT creating new chat + (newChat || + !currentChat?.participants.includes(userData.id)) && // Only exclude existing participants if NOT creating new chat (userData.name ?.toLowerCase() .includes(query.toLowerCase()) || @@ -250,7 +251,7 @@ export function AddMembers({ let chatName: string | undefined; const isGroupChat = participantIds.length > 2; - + if (isGroupChat) { // For group chats, use the custom name or create from selected users chatName = @@ -264,10 +265,7 @@ export function AddMembers({ console.log('Is group chat:', isGroupChat); // Create the new chat - const chatId = await createNewChat( - participantIds, - chatName - ); + const chatId = await createNewChat(participantIds, chatName); console.log('Chat created with ID:', chatId); @@ -478,7 +476,8 @@ export function AddMembers({ (u) => u.id === userItem.id ); const isExistingMember = - !newChat && currentChat?.participants.includes(userItem.id); + !newChat && + currentChat?.participants.includes(userItem.id); return (