Rebrand CommGate to gsmnode and adopt new design system

Rename the whole project from CommGate to gsmnode and re-skin every
surface onto the new signal-green + ink design system (Space Grotesk /
IBM Plex Sans / JetBrains Mono, flat surfaces, two-arrow routing mark,
lowercase gsm+node wordmark).

- Web App + API panel: rewrite token layer, gn-* utilities, data-gsm-theme,
  new logo/favicon assets; both builds verified.
- Phone App (Flutter): green theme, new mark/wordmark widgets, adaptive
  launcher icons; rename Android applicationId/package to app.gsmnode.phone;
  bump AGP to 8.6.0 and google_fonts to 8.1.0; debug APK build verified.
- API Server (Go): panel routes, User-Agent, health service id, branding.
- Home Assistant Plugin: rename integration domain sms_gateway to gsmnode
  (folder, DOMAIN, services, entity ids, classes); py_compile verified.
- Update all READMEs; add .gitignore (excludes Design/, build artifacts).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
tajniak81
2026-07-06 08:53:14 +02:00
commit d6956395c8
166 changed files with 12605 additions and 0 deletions
+130
View File
@@ -0,0 +1,130 @@
import 'dart:convert';
import 'package:http/http.dart' as http;
import '../models/message.dart';
import 'storage.dart';
class ApiException implements Exception {
final int status;
final String message;
ApiException(this.status, this.message);
@override
String toString() => 'ApiException($status): $message';
}
/// Client for the API Server. Uses the user JWT for login/registration and the
/// opaque device token for the mobile gateway endpoints.
class ApiClient {
final Storage storage;
final http.Client _http;
ApiClient(this.storage, {http.Client? client})
: _http = client ?? http.Client();
String get _base => (storage.apiBase ?? '').replaceAll(RegExp(r'/$'), '');
Uri _uri(String path) => Uri.parse('$_base$path');
Map<String, String> _headers(String? token) => {
'Content-Type': 'application/json',
if (token != null && token.isNotEmpty) 'Authorization': 'Bearer $token',
};
dynamic _decode(http.Response res) {
final body = res.body.isNotEmpty ? jsonDecode(res.body) : null;
if (res.statusCode < 200 || res.statusCode >= 300) {
final msg = (body is Map && body['error'] != null)
? body['error'].toString()
: 'HTTP ${res.statusCode}';
throw ApiException(res.statusCode, msg);
}
return body;
}
/// Authenticates a user and stores the JWT. Returns the user email.
Future<String> login(String email, String password) async {
final res = await _http.post(
_uri('/api/auth/login'),
headers: _headers(null),
body: jsonEncode({'email': email, 'password': password}),
);
final data = _decode(res) as Map<String, dynamic>;
storage.jwt = data['access_token'] as String?;
final user = data['user'] as Map<String, dynamic>?;
storage.userEmail = user?['email'] as String?;
return storage.userEmail ?? email;
}
/// Registers this device and stores the returned device token.
Future<void> registerDevice({
required String deviceId,
required String name,
String platform = 'android',
String appVersion = '1.0.0',
String? pushToken,
}) async {
final res = await _http.post(
_uri('/api/mobile/v1/device'),
headers: _headers(storage.jwt),
body: jsonEncode({
'device_id': deviceId,
'name': name,
'platform': platform,
'app_version': appVersion,
if (pushToken != null) 'push_token': pushToken,
}),
);
final data = _decode(res) as Map<String, dynamic>;
storage.deviceId = deviceId;
storage.deviceName = name;
storage.deviceToken = data['auth_token'] as String?;
}
/// Pulls pending messages for this device (the server marks them Processed).
Future<List<GatewayMessage>> pullMessages() async {
final res = await _http.get(
_uri('/api/mobile/v1/messages'),
headers: _headers(storage.deviceToken),
);
final data = _decode(res) as Map<String, dynamic>;
final items = (data['items'] as List<dynamic>? ?? []);
return items
.map((e) => GatewayMessage.fromJson(e as Map<String, dynamic>))
.toList();
}
/// Reports a message's delivery state back to the server.
Future<void> reportMessage(String id, String status, {String? error}) async {
final res = await _http.patch(
_uri('/api/mobile/v1/messages/$id'),
headers: _headers(storage.deviceToken),
body: jsonEncode({'status': status, if (error != null) 'error': error}),
);
_decode(res);
}
/// Posts an incoming SMS to the server's inbox.
Future<void> postInbox(String phoneNumber, String message,
{DateTime? receivedAt}) async {
final res = await _http.post(
_uri('/api/mobile/v1/inbox'),
headers: _headers(storage.deviceToken),
body: jsonEncode({
'phone_number': phoneNumber,
'message': message,
if (receivedAt != null)
'received_at': receivedAt.toUtc().toIso8601String(),
}),
);
_decode(res);
}
/// Sends a heartbeat ping.
Future<void> ping() async {
final res = await _http.post(
_uri('/api/mobile/v1/ping'),
headers: _headers(storage.deviceToken),
);
_decode(res);
}
}