toJwt method

String toJwt({
  1. String? token,
  2. String? apiKey,
  3. DateTime? expiration,
})

Encodes as JWT (HS256). If token is null, tries compile-time env ('MESHAGENT_SECRET'). expiration adds 'exp' (seconds since epoch) to the payload. If apiKey is provided (or set via MESHAGENT_API_KEY), it determines the signing secret and ensures the payload contains the API key metadata.

Implementation

String toJwt({String? token, String? apiKey, DateTime? expiration}) {
  ApiKey? resolvedApiKey;

  var resolvedSecret = token;
  var providedApiKey = apiKey;
  providedApiKey ??= const String.fromEnvironment('MESHAGENT_API_KEY');

  if (providedApiKey.isNotEmpty) {
    resolvedApiKey = parseApiKey(providedApiKey);
    resolvedSecret = resolvedApiKey.secret;
  }

  final usingDefaultSecret = resolvedSecret == null;
  resolvedSecret ??= const String.fromEnvironment('MESHAGENT_SECRET');

  // Warn if missing ApiScope on newer versions (mirrors Python logger.warning)
  final hasApi = grants.any((g) => g.name == 'api');
  if (!hasApi && _semverCompare(version, '0.3.5') > 0) {
    // ignore: avoid_print
    print(
      'Warning: there is no ApiScope in the participant token; this participant will not be able to call the room API. Use addApiGrant to add an ApiScope.',
    );
  }

  final payload = Map<String, dynamic>.from(toJson());

  if (resolvedApiKey != null) {
    payload['kid'] = resolvedApiKey.id;
    payload['sub'] = resolvedApiKey.projectId;
  }

  // Match Python behavior: if exporting with default secret, drop kid.
  if (usingDefaultSecret && payload.containsKey('kid')) {
    payload.remove('kid');
  }

  // Merge extras
  final merged = <String, dynamic>{...payload, if (extra != null) ...extra!};

  if (expiration != null) {
    // 'exp' is a NumericDate (seconds since epoch)
    merged['exp'] = (expiration.millisecondsSinceEpoch / 1000).floor();
  }

  final jwt = JWT(merged);
  return jwt.sign(SecretKey(resolvedSecret), algorithm: JWTAlgorithm.HS256);
}