emitSignal method

void emitSignal(
  1. String signalName, {
  2. dynamic data,
  3. String? sourceModuleId,
  4. ModuleIdentityToken? identityToken,
})

Emit a named signal with identity verification and rate limiting.

Implementation

void emitSignal(
  String signalName, {
  dynamic data,
  String? sourceModuleId,
  ModuleIdentityToken? identityToken,
}) {
  final effectiveSourceId =
      sourceModuleId ?? identityToken?.moduleId ?? 'unknown';

  // Verify identity if token is provided
  if (identityToken != null && sourceModuleId != null) {
    if (!identityToken.verify(sourceModuleId)) {
      AirLogger.security(
        'Identity verification failed for signal emission',
        context: {'expected': sourceModuleId, 'got': identityToken.moduleId},
      );
      AirAudit().log(
        type: AuditType.securityViolation,
        action: 'identity_spoofing_attempt',
        moduleId: identityToken.moduleId,
        context: {'attemptedId': sourceModuleId, 'signal': signalName},
        severity: AuditSeverity.critical,
        success: false,
      );
      return;
    }
  }

  // Check rate limit
  final rateLimitKey = '$effectiveSourceId:signal:$signalName';
  if (!_checkRateLimit(rateLimitKey)) {
    AirAudit().log(
      type: AuditType.securityViolation,
      action: 'signal_rate_limit_exceeded',
      moduleId: effectiveSourceId,
      context: {'signal': signalName},
      severity: AuditSeverity.medium,
      success: false,
    );
    return;
  }

  // Check permission to emit
  if (!PermissionChecker().checkPermission(
    effectiveSourceId,
    Permission.eventEmit,
    resource: signalName,
  )) {
    return;
  }

  AirLogger.debug(
    'Emitting signal "$signalName"',
    context: {'source': effectiveSourceId},
  );

  // Verify module identity and log the interaction for auditing.
  AirAudit().log(
    type: AuditType.moduleInteraction,
    action: 'signal_emitted',
    moduleId: effectiveSourceId,
    context: {'signal': signalName, 'hasData': data != null},
  );

  // Add to history
  _signalHistory.add(
    SignalHistoryEntry(
      name: signalName,
      data: data,
      sourceModuleId: sourceModuleId,
    ),
  );
  if (_signalHistory.length > _maxHistorySize) {
    _signalHistory.removeAt(0);
  }

  final subs = _signalSubscriptions[signalName];
  if (subs == null || subs.isEmpty) {
    AirLogger.debug('No subscribers for signal "$signalName"');
    return;
  }

  // Create copy of active subscribers to iterate safely, then clean up cancelled/expired
  final activeSubs = subs
      .where((s) => !s.isCancelled && !s.isExpired)
      .toList();
  subs.removeWhere((s) => s.isCancelled || s.isExpired);

  for (final subscription in activeSubs) {
    if (subscription.subscriberModuleId != null) {
      // Record interaction. If source is unknown, we use the target itself to create a "bloom" effect
      SecureServiceRegistry().recordInteraction(
        ModuleInteraction(
          sourceId: sourceModuleId ?? subscription.subscriberModuleId!,
          targetId: subscription.subscriberModuleId!,
          type: InteractionType.event,
          detail: signalName,
        ),
      );
    }
    try {
      (subscription.callback as void Function(dynamic))(data);
    } catch (e) {
      AirLogger.error(
        'Error in signal subscriber ${subscription.id}',
        error: e,
      );
    }
  }

  notifyListeners();
}