99 lines
3.6 KiB
Dart
99 lines
3.6 KiB
Dart
// lib/utils/asset_server.dart
|
|
import 'dart:async';
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
import 'package:flutter/services.dart' show rootBundle;
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/widgets.dart';
|
|
|
|
class AssetServer {
|
|
AssetServer._internal();
|
|
static final AssetServer _instance = AssetServer._internal();
|
|
factory AssetServer() => _instance;
|
|
|
|
HttpServer? _server;
|
|
int? _port;
|
|
bool get running => _server != null;
|
|
|
|
Future<Uri> start() async {
|
|
if (_server != null && _port != null) {
|
|
return Uri.parse('http://127.0.0.1:$_port');
|
|
}
|
|
|
|
// Bind to ephemeral port
|
|
_server = await HttpServer.bind(InternetAddress.loopbackIPv4, 0);
|
|
_port = _server!.port;
|
|
|
|
_server!.listen((HttpRequest request) async {
|
|
try {
|
|
final path = request.uri.path.isEmpty ? '/map.html' : request.uri.path;
|
|
debugPrint('[AssetServer] -> Request: $path');
|
|
|
|
// map to assets/map/<path>
|
|
final assetPath = 'assets/map$path';
|
|
|
|
// simple content-type detection
|
|
final contentType = _contentTypeFromPath(path);
|
|
|
|
if (assetPath.endsWith('.html') || assetPath.endsWith('.htm')) {
|
|
// For HTML, we prefer to read as text
|
|
final html = await rootBundle.loadString('assets/map/map.html');
|
|
request.response.headers.contentType = ContentType('text', 'html', charset: 'utf-8');
|
|
request.response.add(utf8.encode(html));
|
|
await request.response.close();
|
|
return;
|
|
}
|
|
|
|
// Other assets as bytes
|
|
try {
|
|
final bd = await rootBundle.load(assetPath);
|
|
final bytes = bd.buffer.asUint8List();
|
|
if (contentType != null) request.response.headers.contentType = contentType;
|
|
request.response.add(bytes);
|
|
await request.response.close();
|
|
} catch (e) {
|
|
debugPrint('[AssetServer] asset not found: $assetPath -> $e');
|
|
request.response.statusCode = HttpStatus.notFound;
|
|
request.response.write('Not Found');
|
|
await request.response.close();
|
|
}
|
|
} catch (e) {
|
|
debugPrint('[AssetServer] internal error: $e');
|
|
try {
|
|
request.response.statusCode = HttpStatus.internalServerError;
|
|
request.response.write('Internal Server Error');
|
|
await request.response.close();
|
|
} catch (_) {}
|
|
}
|
|
});
|
|
|
|
debugPrint('[AssetServer] started at http://127.0.0.1:$_port');
|
|
return Uri.parse('http://127.0.0.1:$_port');
|
|
}
|
|
|
|
Future<void> stop() async {
|
|
try {
|
|
await _server?.close(force: true);
|
|
_server = null;
|
|
_port = null;
|
|
debugPrint('[AssetServer] stopped');
|
|
} catch (e) {
|
|
debugPrint('[AssetServer] stop error: $e');
|
|
}
|
|
}
|
|
|
|
ContentType? _contentTypeFromPath(String path) {
|
|
final lower = path.toLowerCase();
|
|
if (lower.endsWith('.js')) return ContentType('application', 'javascript', charset: 'utf-8');
|
|
if (lower.endsWith('.css')) return ContentType('text', 'css', charset: 'utf-8');
|
|
if (lower.endsWith('.html') || lower.endsWith('.htm')) return ContentType('text', 'html', charset: 'utf-8');
|
|
if (lower.endsWith('.json')) return ContentType('application', 'json', charset: 'utf-8');
|
|
if (lower.endsWith('.png')) return ContentType('image', 'png');
|
|
if (lower.endsWith('.jpg') || lower.endsWith('.jpeg')) return ContentType('image', 'jpeg');
|
|
if (lower.endsWith('.svg')) return ContentType('image', 'svg+xml');
|
|
if (lower.endsWith('.gif')) return ContentType('image', 'gif');
|
|
if (lower.endsWith('.webp')) return ContentType('image', 'webp');
|
|
return ContentType('application', 'octet-stream');
|
|
}
|
|
}
|