package service import ( "errors" "fmt" "time" "browser.local/platform/domain" "browser.local/platform/repo" "browser.local/platform/validator" ) const ( defaultHeartbeatIntervalSeconds = 15 ) func (svc *CoreService) RegisterRunHello(hello domain.RunControlHello) (domain.RunControlHelloResult, error) { hello = domain.CopyRunControlHello(hello) if err := validator.ValidateRunControlHello(hello); err != nil { return domain.RunControlHelloResult{}, err } if hasComponentAuthIdentity(hello) { auth, err := svc.AuthenticateComponent(domain.ComponentAuthenticationRequest{ ServerInstanceID: hello.ServerInstanceID, ComponentKind: hello.ComponentKind, ComponentKey: hello.ComponentKey, Generation: hello.KeyGeneration, Key: hello.RegistrationToken, }) if err != nil { return domain.RunControlHelloResult{}, err } if !auth.Allowed { return domain.CopyRunControlHelloResult(domain.RunControlHelloResult{ Accepted: false, RunEndpointID: hello.RunEndpointID, ServerTime: svc.now(), HeartbeatIntervalSeconds: defaultHeartbeatIntervalSeconds, FeatureFlags: []string{"runtime-key.auth.denied"}, }), nil } } stamp := svc.now() endpoint := domain.RunEndpoint{ ID: hello.RunEndpointID, DisplayName: hello.DisplayName, Version: hello.Version, Status: domain.RunEndpointStatusOnline, Capabilities: domain.CopyStringSlice(hello.CapabilityReport.Capabilities), Capacity: hello.Capacity, LastHeartbeatAt: stamp, } if err := validator.ValidateRunEndpoint(endpoint); err != nil { return domain.RunControlHelloResult{}, err } svc.controlMu.Lock() defer svc.controlMu.Unlock() if err := svc.upsertRunEndpoint(endpoint); err != nil { return domain.RunControlHelloResult{}, err } sessionToken := svc.nextSessionToken(hello.RunEndpointID, stamp) svc.runSessions[hello.RunEndpointID] = domain.RunControlSession{ RunEndpointID: hello.RunEndpointID, SessionToken: sessionToken, CapabilityFingerprint: hello.CapabilityReport.Fingerprint, HeartbeatIntervalSeconds: defaultHeartbeatIntervalSeconds, CreatedAt: stamp, UpdatedAt: stamp, } return domain.CopyRunControlHelloResult(domain.RunControlHelloResult{ Accepted: true, RunEndpointID: hello.RunEndpointID, SessionToken: sessionToken, ServerTime: stamp, HeartbeatIntervalSeconds: defaultHeartbeatIntervalSeconds, FeatureFlags: []string{"control.hello", "control.heartbeat"}, }), nil } func hasComponentAuthIdentity(hello domain.RunControlHello) bool { return hello.ServerInstanceID != "" || hello.PluginID != "" || hello.ComponentKind != "" || hello.ComponentKey != "" || hello.KeyGeneration != 0 } func (svc *CoreService) AcceptRunHeartbeat(heartbeat domain.RunControlHeartbeat) (domain.RunControlHeartbeatResult, error) { heartbeat = domain.CopyRunControlHeartbeat(heartbeat) if err := validator.ValidateRunControlHeartbeat(heartbeat); err != nil { return domain.RunControlHeartbeatResult{}, err } stamp := svc.now() svc.controlMu.Lock() defer svc.controlMu.Unlock() session, exists := svc.runSessions[heartbeat.RunEndpointID] if !exists || session.SessionToken != heartbeat.SessionToken { return domain.RunControlHeartbeatResult{}, validationError("sessionToken is invalid") } endpoint, err := svc.store.RunEndpoints().Get(heartbeat.RunEndpointID) if err != nil { return domain.RunControlHeartbeatResult{}, err } endpoint.Version = heartbeat.Version endpoint.Status = heartbeat.Status endpoint.Capacity = heartbeat.Capacity endpoint.LastHeartbeatAt = stamp if err := validator.ValidateRunEndpoint(endpoint); err != nil { return domain.RunControlHeartbeatResult{}, err } if err := svc.store.RunEndpoints().Update(endpoint); err != nil { return domain.RunControlHeartbeatResult{}, err } refreshCapabilities := session.CapabilityFingerprint != heartbeat.CapabilityFingerprint session.CapabilityFingerprint = heartbeat.CapabilityFingerprint session.UpdatedAt = stamp svc.runSessions[heartbeat.RunEndpointID] = session return domain.CopyRunControlHeartbeatResult(domain.RunControlHeartbeatResult{ Accepted: true, RunEndpointID: heartbeat.RunEndpointID, NextHeartbeatSeconds: session.HeartbeatIntervalSeconds, RefreshCapabilities: refreshCapabilities, ServerTime: stamp, }), nil } func (svc *CoreService) upsertRunEndpoint(endpoint domain.RunEndpoint) error { if _, err := svc.store.RunEndpoints().Get(endpoint.ID); err != nil { if errors.Is(err, repo.ErrNotFound) { return svc.store.RunEndpoints().Create(endpoint) } return err } return svc.store.RunEndpoints().Update(endpoint) } func (svc *CoreService) nextSessionToken(runEndpointID string, stamp time.Time) string { svc.runSessionSeq++ return fmt.Sprintf("session:%s:%d:%d", runEndpointID, stamp.UnixNano(), svc.runSessionSeq) }