Initial project
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
import 'package:networks_sdk/src/domain/entities/socket_connection_state.dart';
|
||||
import 'package:networks_sdk/src/domain/entities/socket_error.dart';
|
||||
import 'package:networks_sdk/src/presentation/wiring/networks_sdk_wiring.dart';
|
||||
import 'package:networks_sdk/src/presentation/wiring/socket_config.dart';
|
||||
|
||||
/// Messaging API for real-time communication
|
||||
///
|
||||
/// This abstract class provides a technology-agnostic interface for
|
||||
/// real-time messaging. The actual implementation may use WebSocket
|
||||
/// or other transport mechanisms.
|
||||
///
|
||||
/// ## Usage
|
||||
///
|
||||
/// ```dart
|
||||
/// final messaging = NetworksMessagingApi();
|
||||
/// await messaging.initialize(SocketConfig(...));
|
||||
///
|
||||
/// // Connect to messaging server
|
||||
/// await messaging.connect('wss://api.example.com/ws', token: 'xxx');
|
||||
///
|
||||
/// // Listen for messages
|
||||
/// messaging.messageStream.listen((msg) => print(msg));
|
||||
///
|
||||
/// // Send messages
|
||||
/// await messaging.send({'type': 'chat', 'data': {...}});
|
||||
///
|
||||
/// // Handle connection state
|
||||
/// messaging.connectionStateStream.listen((state) => ...);
|
||||
///
|
||||
/// // Handle errors
|
||||
/// messaging.errorStream.listen((error) => ...);
|
||||
///
|
||||
/// // Lifecycle management
|
||||
/// messaging.onEnterForeground();
|
||||
/// messaging.onEnterBackground();
|
||||
///
|
||||
/// // Cleanup
|
||||
/// await messaging.disconnect();
|
||||
/// await messaging.dispose();
|
||||
/// ```
|
||||
abstract class NetworksMessagingApi
|
||||
{
|
||||
factory NetworksMessagingApi() => NetworksSdkWiring.buildMessagingApi();
|
||||
|
||||
/// Initialize the messaging service with configuration
|
||||
void initialize(SocketConfig config);
|
||||
|
||||
/// Connect to the messaging server
|
||||
///
|
||||
/// [url] - WebSocket URL (e.g., 'wss://api.example.com/ws')
|
||||
/// [token] - Optional authentication token
|
||||
Future<bool> connect(String url, {String? token});
|
||||
|
||||
/// Disconnect from the messaging server
|
||||
///
|
||||
/// Manual disconnect does not trigger auto-reconnect
|
||||
Future<void> disconnect();
|
||||
|
||||
/// Check if currently connected
|
||||
bool get isConnected;
|
||||
|
||||
/// Current connection state
|
||||
SocketConnectionState get connectionState;
|
||||
|
||||
/// Send a JSON message
|
||||
Future<bool> send(Map<String, dynamic> message);
|
||||
|
||||
/// Send a raw string message
|
||||
Future<bool> sendString(String message);
|
||||
|
||||
/// Stream of incoming parsed JSON messages
|
||||
Stream<Map<String, dynamic>> get messageStream;
|
||||
|
||||
/// Stream of raw string messages (including failed JSON parses)
|
||||
Stream<String> get rawMessageStream;
|
||||
|
||||
/// Stream of connection state changes
|
||||
Stream<SocketConnectionState> get connectionStateStream;
|
||||
|
||||
/// Stream of errors
|
||||
Stream<SocketError> get errorStream;
|
||||
|
||||
/// Called when app enters foreground
|
||||
void onEnterForeground();
|
||||
|
||||
/// Called when app enters background
|
||||
void onEnterBackground();
|
||||
|
||||
/// Dispose all resources
|
||||
Future<void> dispose();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
|
||||
|
||||
|
||||
import 'package:networks_sdk/src/data/dto/api_requestable.dart';
|
||||
import 'package:networks_sdk/src/presentation/wiring/api_config.dart';
|
||||
import 'package:networks_sdk/src/presentation/wiring/networks_sdk_wiring.dart';
|
||||
|
||||
|
||||
/// SDK API
|
||||
abstract class NetworksSdkApi
|
||||
{
|
||||
factory NetworksSdkApi() => NetworksSdkWiring.build();
|
||||
|
||||
Future<String?> platformVersion();
|
||||
|
||||
void initialize(ApiConfig aApiConfig);
|
||||
|
||||
Future<T?> executeRequest<T>(ApiRequestable<T> request);
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
|
||||
import 'network_callbacks.dart';
|
||||
|
||||
/// API 配置
|
||||
/// 非单例,由 App 层构造并注入到 ApiClient
|
||||
class ApiConfig {
|
||||
/// 基础 URL(来自 config.json → AppConfig.apiBaseUrl)
|
||||
String baseURL;
|
||||
|
||||
/// 当前 token(内存持有,App 层负责持久化)
|
||||
String? token;
|
||||
|
||||
/// 平台相关 headers(App 层注入:version、platform、channel 等)
|
||||
Map<String, String> platformHeaders;
|
||||
|
||||
/// Token 过期时的刷新回调
|
||||
final OnTokenRefresh? onTokenRefresh;
|
||||
|
||||
/// 需要强制登出时的回调
|
||||
final OnForceLogout? onForceLogout;
|
||||
|
||||
/// 日志输出回调(不设置则不输出日志)
|
||||
final OnLog? onLog;
|
||||
|
||||
/// 网络可用性查询(App 层注入,请求前调用)
|
||||
///
|
||||
/// 与 [SocketConfig.onCheckNetworkAvailable] 对称。
|
||||
/// 返回 true 表示网络可用,可以发起请求;
|
||||
/// 返回 false 则直接抛 [ApiError.noNetworkConnection],不走网络。
|
||||
final OnCheckNetworkAvailable? onCheckNetworkAvailable;
|
||||
|
||||
/// App 层定义的 Token 过期错误码集合
|
||||
final Set<int> tokenExpiredCodes;
|
||||
|
||||
/// App 层定义的强制登出错误码集合
|
||||
final Set<int> forceLogoutCodes;
|
||||
|
||||
/// 瞬态错误最大重试次数(5xx / 超时 / 连接失败)
|
||||
///
|
||||
/// 0 = 不重试(默认),设为 3 启用重试。
|
||||
/// 与 Token 刷新重试独立,两者可叠加。
|
||||
final int maxRetries;
|
||||
|
||||
/// 重试基础延迟(指数退避起点)
|
||||
///
|
||||
/// 实际延迟 = min(baseDelay * 2^attempt, 30s) + jitter
|
||||
final Duration retryBaseDelay;
|
||||
|
||||
ApiConfig({
|
||||
required this.baseURL,
|
||||
this.token,
|
||||
this.platformHeaders = const {},
|
||||
this.onTokenRefresh,
|
||||
this.onForceLogout,
|
||||
this.onLog,
|
||||
this.onCheckNetworkAvailable,
|
||||
this.tokenExpiredCodes = const {},
|
||||
this.forceLogoutCodes = const {},
|
||||
this.maxRetries = 0,
|
||||
this.retryBaseDelay = const Duration(seconds: 1),
|
||||
});
|
||||
|
||||
/// 构建默认 headers
|
||||
/// [includeToken] — 是否注入 token
|
||||
/// [customHeaders] — 单次请求自定义 header(优先级最高)
|
||||
Map<String, String> defaultHeaders({
|
||||
bool includeToken = true,
|
||||
Map<String, String>? customHeaders,
|
||||
}) {
|
||||
final headers = <String, String>{
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
'Accept': 'application/json',
|
||||
'Timestamp': '${DateTime.now().millisecondsSinceEpoch ~/ 1000}',
|
||||
'APP-Request-ID': _generateRequestId(),
|
||||
};
|
||||
|
||||
// 合并平台 headers(App 层注入的 version、platform 等)
|
||||
headers.addAll(platformHeaders);
|
||||
|
||||
// Token 注入
|
||||
if (includeToken && token != null && token!.isNotEmpty) {
|
||||
headers['token'] = token!;
|
||||
}
|
||||
|
||||
// 单次请求自定义 header(覆盖默认值)
|
||||
if (customHeaders != null) {
|
||||
headers.addAll(customHeaders);
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
/// 更新 token
|
||||
void updateToken(String? newToken) {
|
||||
token = newToken;
|
||||
}
|
||||
|
||||
/// 更新 base URL
|
||||
void updateBaseURL(String newBaseURL) {
|
||||
baseURL = newBaseURL;
|
||||
}
|
||||
|
||||
/// 生成请求 ID(用于幂等性)
|
||||
String _generateRequestId() {
|
||||
final now = DateTime.now().microsecondsSinceEpoch;
|
||||
return '$now${Object().hashCode}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/// 网络层回调类型定义,由 App 层注入 SDK,避免 SDK 直接依赖外部实现。
|
||||
library;
|
||||
|
||||
typedef OnTokenRefresh = Future<String?> Function();
|
||||
|
||||
/// 強制登出回調
|
||||
typedef OnForceLogout = void Function();
|
||||
|
||||
/// 日誌輸出回調
|
||||
typedef OnLog = void Function(String message, {String? tag});
|
||||
|
||||
/// 網路可用性查詢(App 層注入,SDK 在請求前調用)
|
||||
typedef OnCheckNetworkAvailable = Future<bool> Function();
|
||||
@@ -0,0 +1,106 @@
|
||||
import 'package:networks_sdk/src/data/repositories/networks_messaging_repository_impl.dart';
|
||||
import 'package:networks_sdk/src/domain/entities/socket_connection_state.dart';
|
||||
import 'package:networks_sdk/src/domain/entities/socket_error.dart';
|
||||
import 'package:networks_sdk/src/domain/repositories/networks_messaging_repository.dart';
|
||||
import 'package:networks_sdk/src/presentation/facade/networks_messaging_api.dart';
|
||||
import 'package:networks_sdk/src/presentation/wiring/socket_config.dart';
|
||||
|
||||
/// Implementation of [NetworksMessagingApi] using [NetworksMessagingRepository]
|
||||
class NetworksMessagingApiImpl implements NetworksMessagingApi {
|
||||
NetworksMessagingRepository? _repository;
|
||||
|
||||
@override
|
||||
void initialize(SocketConfig config) {
|
||||
_repository = NetworksMessagingRepositoryImpl();
|
||||
_repository!.initialize(config);
|
||||
}
|
||||
|
||||
void _checkInitialized() {
|
||||
if (_repository == null) {
|
||||
throw StateError(
|
||||
'NetworksMessagingApi not initialized. Call initialize() first.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> connect(String url, {String? token}) {
|
||||
_checkInitialized();
|
||||
return _repository!.connect(url, token: token);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> disconnect() {
|
||||
_checkInitialized();
|
||||
return _repository!.disconnect();
|
||||
}
|
||||
|
||||
@override
|
||||
bool get isConnected {
|
||||
_checkInitialized();
|
||||
return _repository!.isConnected;
|
||||
}
|
||||
|
||||
@override
|
||||
SocketConnectionState get connectionState {
|
||||
_checkInitialized();
|
||||
return _repository!.connectionState;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> send(Map<String, dynamic> message) {
|
||||
_checkInitialized();
|
||||
return _repository!.send(message);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> sendString(String message) {
|
||||
_checkInitialized();
|
||||
return _repository!.sendString(message);
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<Map<String, dynamic>> get messageStream {
|
||||
_checkInitialized();
|
||||
return _repository!.messageStream;
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<String> get rawMessageStream {
|
||||
_checkInitialized();
|
||||
return _repository!.rawMessageStream;
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<SocketConnectionState> get connectionStateStream {
|
||||
_checkInitialized();
|
||||
return _repository!.connectionStateStream;
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<SocketError> get errorStream {
|
||||
_checkInitialized();
|
||||
return _repository!.errorStream;
|
||||
}
|
||||
|
||||
@override
|
||||
void onEnterForeground() {
|
||||
_checkInitialized();
|
||||
_repository!.onEnterForeground();
|
||||
}
|
||||
|
||||
@override
|
||||
void onEnterBackground() {
|
||||
_checkInitialized();
|
||||
_repository!.onEnterBackground();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
if (_repository != null) {
|
||||
await _repository!.dispose();
|
||||
_repository = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import '../../../networks_sdk.dart';
|
||||
import 'networks_sdk_core.dart';
|
||||
|
||||
/// SDK API Implementation
|
||||
class NetworksSdkApiImpl implements NetworksSdkApi {
|
||||
final NetworksSdkCore _core;
|
||||
|
||||
NetworksSdkApiImpl({required NetworksSdkCore core}) : _core = core;
|
||||
|
||||
@override
|
||||
Future<String?> platformVersion() => _core.repo.platformVersion();
|
||||
|
||||
@override
|
||||
void initialize(ApiConfig apiConfig) => _core.repo.initialize(apiConfig);
|
||||
|
||||
@override
|
||||
Future<T?> executeRequest<T>(ApiRequestable<T> request) => _core.repo.executeRequest(request);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
|
||||
import '../../../networks_sdk_platform_interface.dart';
|
||||
import '../../domain/repositories/networks_sdk_repository.dart';
|
||||
|
||||
class NetworksSdkCore
|
||||
{
|
||||
final NetworksSdkPlatform platform;
|
||||
final NetworksSdkRepository repo;
|
||||
|
||||
NetworksSdkCore({
|
||||
required this.platform,
|
||||
required this.repo,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import '../../../networks_sdk.dart';
|
||||
import '../../../networks_sdk_method_channel.dart';
|
||||
import '../../../networks_sdk_platform_interface.dart';
|
||||
import '../../data/datasources/networks_sdk_method_channel_datasource.dart';
|
||||
import '../../data/repositories/networks_sdk_repository_impl.dart';
|
||||
import 'networks_sdk_core.dart';
|
||||
import 'networks_sdk_api_impl.dart';
|
||||
|
||||
/// SDK Wiring - builds all SDK components
|
||||
class NetworksSdkWiring
|
||||
{
|
||||
/// Builds the HTTP API
|
||||
static NetworksSdkApi buildApi() {
|
||||
|
||||
// platform instance(method channel)
|
||||
final platform = NetworksSdkPlatform.instance;
|
||||
if (platform is MethodChannelNetworksSdk) {
|
||||
// platform.init(); // or defer to NotificationApiImpl.init
|
||||
}
|
||||
|
||||
// data layer
|
||||
final ds = NetworksSdkMethodChannelDataSource(platform);
|
||||
final repo = NetworksSdkRepositoryImpl(ds);
|
||||
final core = NetworksSdkCore(platform: platform, repo: repo,);
|
||||
|
||||
return NetworksSdkApiImpl(core: core);
|
||||
}
|
||||
|
||||
/// Builds the messaging API (WebSocket)
|
||||
static NetworksMessagingApi buildMessagingApi() {
|
||||
return NetworksMessagingApiImpl();
|
||||
}
|
||||
|
||||
/// Builds the default SDK instance (HTTP API)
|
||||
/// Use [buildMessagingApi()] separately for messaging features
|
||||
static NetworksSdkApi build() => buildApi();
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/// WebSocket 配置
|
||||
/// 非单例,由 App 层构造并注入到 SocketClient
|
||||
///
|
||||
/// 与 [ApiConfig] 设计一致:SDK 不依赖 Flutter,
|
||||
/// 网络检测、生命周期等业务逻辑通过回调注入。
|
||||
class SocketConfig {
|
||||
/// 应用层心跳间隔(定时发送 "ping" 字符串)
|
||||
final Duration heartbeatInterval;
|
||||
|
||||
/// 底层 WebSocket ping 间隔(Dart WebSocket 自动管理)
|
||||
final Duration pingInterval;
|
||||
|
||||
/// Pong 超时(超过此时间未收到 pong 则判定连接断开)
|
||||
final Duration pongTimeout;
|
||||
|
||||
/// 连接超时
|
||||
final Duration connectTimeout;
|
||||
|
||||
/// 最大重连次数(0 = 不重连)
|
||||
final int maxReconnectAttempts;
|
||||
|
||||
/// 最大重连延迟(指数退避上限)
|
||||
final Duration maxReconnectDelay;
|
||||
|
||||
/// 是否自动重连
|
||||
final bool autoReconnect;
|
||||
|
||||
/// 日志输出回调(与 ApiConfig.onLog 同签名)
|
||||
final void Function(String message, {String? tag})? onLog;
|
||||
|
||||
/// 网络可用性查询(App 层注入,SDK 在重连前调用)
|
||||
/// 返回 true 表示网络可用,可以尝试重连
|
||||
final Future<bool> Function()? onCheckNetworkAvailable;
|
||||
|
||||
SocketConfig({
|
||||
this.heartbeatInterval = const Duration(seconds: 10),
|
||||
this.pingInterval = const Duration(seconds: 5),
|
||||
this.pongTimeout = const Duration(seconds: 10),
|
||||
this.connectTimeout = const Duration(seconds: 15),
|
||||
this.maxReconnectAttempts = 5,
|
||||
this.maxReconnectDelay = const Duration(seconds: 30),
|
||||
this.autoReconnect = true,
|
||||
this.onLog,
|
||||
this.onCheckNetworkAvailable,
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user