meteor_detection_system/meteor-frontend/src/contexts/device-registration-context.tsx
grabbit 13ce6ae442 feat: implement complete edge device registration system
- Add hardware fingerprinting with cross-platform support
- Implement secure device registration flow with X.509 certificates
- Add WebSocket real-time communication for device status
- Create comprehensive device management dashboard
- Establish zero-trust security architecture with multi-layer protection
- Add database migrations for device registration entities
- Implement Rust edge client with hardware identification
- Add certificate management and automated provisioning system

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-13 08:46:25 +08:00

382 lines
12 KiB
TypeScript

"use client"
import React, { createContext, useContext, useReducer, useEffect, useCallback } from 'react'
import {
DeviceRegistrationSession,
RegistrationStatus,
DeviceDto,
InitiateRegistrationRequest,
WebSocketDeviceEvent
} from '../types/device'
import { devicesApi } from '../services/devices'
import { deviceWebSocketService, WebSocketEventHandlers } from '../services/websocket'
import { useAuth } from './auth-context'
// State interface
interface DeviceRegistrationState {
currentSession: DeviceRegistrationSession | null
registeredDevices: DeviceDto[]
isLoading: boolean
error: string | null
wsConnected: boolean
qrCodeUrl: string | null
}
// Action types
type DeviceRegistrationAction =
| { type: 'SET_LOADING'; payload: boolean }
| { type: 'SET_ERROR'; payload: string | null }
| { type: 'SET_CURRENT_SESSION'; payload: DeviceRegistrationSession | null }
| { type: 'SET_REGISTERED_DEVICES'; payload: DeviceDto[] }
| { type: 'ADD_DEVICE'; payload: DeviceDto }
| { type: 'UPDATE_DEVICE'; payload: DeviceDto }
| { type: 'REMOVE_DEVICE'; payload: string }
| { type: 'SET_WS_CONNECTED'; payload: boolean }
| { type: 'SET_QR_CODE_URL'; payload: string | null }
| { type: 'UPDATE_SESSION_STATUS'; payload: RegistrationStatus }
| { type: 'RESET_STATE' }
// Initial state
const initialState: DeviceRegistrationState = {
currentSession: null,
registeredDevices: [],
isLoading: false,
error: null,
wsConnected: false,
qrCodeUrl: null,
}
// Reducer
function deviceRegistrationReducer(
state: DeviceRegistrationState,
action: DeviceRegistrationAction
): DeviceRegistrationState {
switch (action.type) {
case 'SET_LOADING':
return { ...state, isLoading: action.payload }
case 'SET_ERROR':
return { ...state, error: action.payload, isLoading: false }
case 'SET_CURRENT_SESSION':
return { ...state, currentSession: action.payload }
case 'SET_REGISTERED_DEVICES':
return { ...state, registeredDevices: action.payload }
case 'ADD_DEVICE':
return {
...state,
registeredDevices: [...state.registeredDevices, action.payload]
}
case 'UPDATE_DEVICE':
return {
...state,
registeredDevices: state.registeredDevices.map(device =>
device.id === action.payload.id ? action.payload : device
)
}
case 'REMOVE_DEVICE':
return {
...state,
registeredDevices: state.registeredDevices.filter(
device => device.id !== action.payload
)
}
case 'SET_WS_CONNECTED':
return { ...state, wsConnected: action.payload }
case 'SET_QR_CODE_URL':
return { ...state, qrCodeUrl: action.payload }
case 'UPDATE_SESSION_STATUS':
return {
...state,
currentSession: state.currentSession
? { ...state.currentSession, status: action.payload }
: null
}
case 'RESET_STATE':
return initialState
default:
return state
}
}
// Context interface
interface DeviceRegistrationContextType {
state: DeviceRegistrationState
// Registration actions
initiateRegistration: (request: InitiateRegistrationRequest) => Promise<void>
cancelRegistration: () => Promise<void>
refreshSession: () => Promise<void>
// Device management actions
refreshDevices: () => Promise<void>
updateDevice: (deviceId: string, updates: Partial<DeviceDto>) => Promise<void>
deleteDevice: (deviceId: string) => Promise<void>
// WebSocket actions
connectWebSocket: () => Promise<void>
disconnectWebSocket: () => void
// Utility actions
clearError: () => void
resetState: () => void
}
const DeviceRegistrationContext = createContext<DeviceRegistrationContextType | undefined>(undefined)
// Provider component
interface DeviceRegistrationProviderProps {
children: React.ReactNode
}
export function DeviceRegistrationProvider({ children }: DeviceRegistrationProviderProps) {
const [state, dispatch] = useReducer(deviceRegistrationReducer, initialState)
const { user, isAuthenticated } = useAuth()
// WebSocket event handlers
const wsEventHandlers: WebSocketEventHandlers = {
onConnect: () => {
dispatch({ type: 'SET_WS_CONNECTED', payload: true })
},
onDisconnect: () => {
dispatch({ type: 'SET_WS_CONNECTED', payload: false })
},
onError: (error) => {
console.error('WebSocket error:', error)
dispatch({ type: 'SET_ERROR', payload: error.message })
},
onDeviceConnected: (data) => {
if (state.currentSession && data.sessionId === state.currentSession.id) {
dispatch({ type: 'UPDATE_SESSION_STATUS', payload: RegistrationStatus.DEVICE_CONNECTED })
}
},
onRegistrationCompleted: (data) => {
if (state.currentSession && data.sessionId === state.currentSession.id) {
dispatch({ type: 'UPDATE_SESSION_STATUS', payload: RegistrationStatus.COMPLETED })
// Add the new device to the list
if (data.device) {
dispatch({ type: 'ADD_DEVICE', payload: data.device })
}
// Clear current session after a delay
setTimeout(() => {
dispatch({ type: 'SET_CURRENT_SESSION', payload: null })
dispatch({ type: 'SET_QR_CODE_URL', payload: null })
}, 2000)
}
},
onRegistrationFailed: (data) => {
if (state.currentSession && data.sessionId === state.currentSession.id) {
dispatch({ type: 'UPDATE_SESSION_STATUS', payload: RegistrationStatus.FAILED })
dispatch({ type: 'SET_ERROR', payload: data.error || 'Registration failed' })
}
},
onDeviceStatusChanged: (data) => {
// Update device status in the list
dispatch({ type: 'UPDATE_DEVICE', payload: data.device })
},
}
// Initialize WebSocket event handlers
useEffect(() => {
deviceWebSocketService.setEventHandlers(wsEventHandlers)
}, [state.currentSession?.id])
// Connect WebSocket when authenticated
useEffect(() => {
if (isAuthenticated && !state.wsConnected) {
connectWebSocket()
} else if (!isAuthenticated && state.wsConnected) {
disconnectWebSocket()
}
}, [isAuthenticated])
// Load devices on mount when authenticated
useEffect(() => {
if (isAuthenticated) {
refreshDevices()
}
}, [isAuthenticated])
// Cleanup on unmount
useEffect(() => {
return () => {
disconnectWebSocket()
}
}, [])
// Actions implementation
const initiateRegistration = useCallback(async (request: InitiateRegistrationRequest) => {
dispatch({ type: 'SET_LOADING', payload: true })
dispatch({ type: 'SET_ERROR', payload: null })
try {
const response = await devicesApi.initiateRegistration(request)
// Transform the response into a session object
const session: DeviceRegistrationSession = {
id: response.claim_id,
userProfileId: user?.id || '',
registrationCode: response.claim_token,
pinCode: response.fallback_pin,
status: RegistrationStatus.WAITING_FOR_DEVICE,
expiresAt: response.expires_at,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
}
dispatch({ type: 'SET_CURRENT_SESSION', payload: session })
dispatch({ type: 'SET_QR_CODE_URL', payload: response.qr_code_url })
// Join the registration session room
if (state.wsConnected) {
deviceWebSocketService.joinRegistrationSession(response.claim_id)
}
} catch (error) {
dispatch({ type: 'SET_ERROR', payload: error instanceof Error ? error.message : 'Failed to initiate registration' })
} finally {
dispatch({ type: 'SET_LOADING', payload: false })
}
}, [state.wsConnected, user?.id])
const cancelRegistration = useCallback(async () => {
if (!state.currentSession) return
dispatch({ type: 'SET_LOADING', payload: true })
try {
await devicesApi.cancelRegistration(state.currentSession.id)
// Leave the registration session room
if (state.wsConnected) {
deviceWebSocketService.leaveRegistrationSession(state.currentSession.id)
}
dispatch({ type: 'SET_CURRENT_SESSION', payload: null })
dispatch({ type: 'SET_QR_CODE_URL', payload: null })
} catch (error) {
dispatch({ type: 'SET_ERROR', payload: error instanceof Error ? error.message : 'Failed to cancel registration' })
} finally {
dispatch({ type: 'SET_LOADING', payload: false })
}
}, [state.currentSession?.id, state.wsConnected])
const refreshSession = useCallback(async () => {
if (!state.currentSession) return
try {
const session = await devicesApi.getRegistrationSession(state.currentSession.id)
dispatch({ type: 'SET_CURRENT_SESSION', payload: session })
} catch (error) {
dispatch({ type: 'SET_ERROR', payload: error instanceof Error ? error.message : 'Failed to refresh session' })
}
}, [state.currentSession?.id])
const refreshDevices = useCallback(async () => {
if (!isAuthenticated) return
try {
const response = await devicesApi.getDevices()
dispatch({ type: 'SET_REGISTERED_DEVICES', payload: response.devices })
} catch (error) {
dispatch({ type: 'SET_ERROR', payload: error instanceof Error ? error.message : 'Failed to load devices' })
}
}, [isAuthenticated])
const updateDevice = useCallback(async (deviceId: string, updates: Partial<DeviceDto>) => {
dispatch({ type: 'SET_LOADING', payload: true })
try {
const updatedDevice = await devicesApi.updateDevice(deviceId, updates)
dispatch({ type: 'UPDATE_DEVICE', payload: updatedDevice })
} catch (error) {
dispatch({ type: 'SET_ERROR', payload: error instanceof Error ? error.message : 'Failed to update device' })
} finally {
dispatch({ type: 'SET_LOADING', payload: false })
}
}, [])
const deleteDevice = useCallback(async (deviceId: string) => {
dispatch({ type: 'SET_LOADING', payload: true })
try {
await devicesApi.deleteDevice(deviceId)
dispatch({ type: 'REMOVE_DEVICE', payload: deviceId })
} catch (error) {
dispatch({ type: 'SET_ERROR', payload: error instanceof Error ? error.message : 'Failed to delete device' })
} finally {
dispatch({ type: 'SET_LOADING', payload: false })
}
}, [])
const connectWebSocket = useCallback(async () => {
try {
const token = localStorage.getItem('accessToken')
if (!token) throw new Error('No authentication token')
await deviceWebSocketService.connect(token)
} catch (error) {
dispatch({ type: 'SET_ERROR', payload: error instanceof Error ? error.message : 'Failed to connect to real-time updates' })
}
}, [])
const disconnectWebSocket = useCallback(() => {
deviceWebSocketService.disconnect()
dispatch({ type: 'SET_WS_CONNECTED', payload: false })
}, [])
const clearError = useCallback(() => {
dispatch({ type: 'SET_ERROR', payload: null })
}, [])
const resetState = useCallback(() => {
dispatch({ type: 'RESET_STATE' })
}, [])
const contextValue: DeviceRegistrationContextType = {
state,
initiateRegistration,
cancelRegistration,
refreshSession,
refreshDevices,
updateDevice,
deleteDevice,
connectWebSocket,
disconnectWebSocket,
clearError,
resetState,
}
return (
<DeviceRegistrationContext.Provider value={contextValue}>
{children}
</DeviceRegistrationContext.Provider>
)
}
// Hook for using the context
export function useDeviceRegistration() {
const context = useContext(DeviceRegistrationContext)
if (context === undefined) {
throw new Error('useDeviceRegistration must be used within a DeviceRegistrationProvider')
}
return context
}