mirror of
https://github.com/lifegpc/eh_downloader_flutter.git
synced 2026-06-06 05:49:03 +08:00
add task manager
This commit is contained in:
@@ -271,6 +271,19 @@ class EHApi extends __EHApi {
|
||||
return newUri.toString();
|
||||
}
|
||||
|
||||
Uri getTaskUrl() {
|
||||
final uri = Uri.parse(_combineBaseUrls(_dio.options.baseUrl, baseUrl));
|
||||
final nuri = uri.resolve("task");
|
||||
return Uri(
|
||||
scheme: uri.scheme == "https" ? "wss" : "ws",
|
||||
userInfo: nuri.userInfo,
|
||||
host: nuri.host,
|
||||
port: nuri.port,
|
||||
path: nuri.path,
|
||||
query: nuri.query,
|
||||
);
|
||||
}
|
||||
|
||||
String getThumbnailUrl(int id,
|
||||
{int? max,
|
||||
int? width,
|
||||
|
||||
@@ -138,11 +138,11 @@ class TaskProgress {
|
||||
const TaskProgress({
|
||||
required this.type,
|
||||
required this.taskId,
|
||||
required this.progress,
|
||||
required this.detail,
|
||||
});
|
||||
final TaskType type;
|
||||
final int taskId;
|
||||
final TaskProgressBasicType progress;
|
||||
final TaskProgressBasicType detail;
|
||||
factory TaskProgress.fromJson(Map<String, dynamic> json) {
|
||||
final type = json['type'] as int;
|
||||
final taskId = json['task_id'] as int;
|
||||
@@ -151,29 +151,29 @@ class TaskProgress {
|
||||
return TaskProgress(
|
||||
type: TaskType.download,
|
||||
taskId: taskId,
|
||||
progress: TaskDownloadProgess.fromJson(
|
||||
json['progress'] as Map<String, dynamic>),
|
||||
detail: TaskDownloadProgess.fromJson(
|
||||
json['detail'] as Map<String, dynamic>),
|
||||
);
|
||||
case 1:
|
||||
return TaskProgress(
|
||||
type: TaskType.exportZip,
|
||||
taskId: taskId,
|
||||
progress: TaskExportZipProgress.fromJson(
|
||||
json['progress'] as Map<String, dynamic>),
|
||||
detail: TaskExportZipProgress.fromJson(
|
||||
json['detail'] as Map<String, dynamic>),
|
||||
);
|
||||
case 2:
|
||||
return TaskProgress(
|
||||
type: TaskType.updateMeiliSearchData,
|
||||
taskId: taskId,
|
||||
progress: TaskUpdateMeiliSearchDataProgress.fromJson(
|
||||
json['progress'] as Map<String, dynamic>),
|
||||
detail: TaskUpdateMeiliSearchDataProgress.fromJson(
|
||||
json['detail'] as Map<String, dynamic>),
|
||||
);
|
||||
case 3:
|
||||
return TaskProgress(
|
||||
type: TaskType.fixGalleryPage,
|
||||
taskId: taskId,
|
||||
progress: TaskFixGalleryPageProgress.fromJson(
|
||||
json['progress'] as Map<String, dynamic>),
|
||||
detail: TaskFixGalleryPageProgress.fromJson(
|
||||
json['detail'] as Map<String, dynamic>),
|
||||
);
|
||||
default:
|
||||
throw ArgumentError.value(type, 'type', 'Invalid task type');
|
||||
@@ -200,9 +200,37 @@ class TaskDetail {
|
||||
this.error,
|
||||
this.fataled,
|
||||
});
|
||||
final Task base;
|
||||
Task base;
|
||||
TaskProgressBasicType? progress;
|
||||
TaskStatus status;
|
||||
String? error;
|
||||
bool? fataled;
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
class TaskList {
|
||||
const TaskList({
|
||||
required this.tasks,
|
||||
required this.running,
|
||||
});
|
||||
final List<Task> tasks;
|
||||
final List<int> running;
|
||||
factory TaskList.fromJson(Map<String, dynamic> json) =>
|
||||
_$TaskListFromJson(json);
|
||||
Map<String, dynamic> toJson() => _$TaskListToJson(this);
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
class TaskError {
|
||||
const TaskError({
|
||||
required this.task,
|
||||
required this.error,
|
||||
required this.fatal,
|
||||
});
|
||||
final Task task;
|
||||
final String error;
|
||||
final bool fatal;
|
||||
factory TaskError.fromJson(Map<String, dynamic> json) =>
|
||||
_$TaskErrorFromJson(json);
|
||||
Map<String, dynamic> toJson() => _$TaskErrorToJson(this);
|
||||
}
|
||||
|
||||
@@ -120,3 +120,27 @@ Map<String, dynamic> _$TaskFixGalleryPageProgressToJson(
|
||||
'total_gallery': instance.totalGallery,
|
||||
'checked_gallery': instance.checkedGallery,
|
||||
};
|
||||
|
||||
TaskList _$TaskListFromJson(Map<String, dynamic> json) => TaskList(
|
||||
tasks: (json['tasks'] as List<dynamic>)
|
||||
.map((e) => Task.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
running: (json['running'] as List<dynamic>).map((e) => e as int).toList(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$TaskListToJson(TaskList instance) => <String, dynamic>{
|
||||
'tasks': instance.tasks,
|
||||
'running': instance.running,
|
||||
};
|
||||
|
||||
TaskError _$TaskErrorFromJson(Map<String, dynamic> json) => TaskError(
|
||||
task: Task.fromJson(json['task'] as Map<String, dynamic>),
|
||||
error: json['error'] as String,
|
||||
fatal: json['fatal'] as bool,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$TaskErrorToJson(TaskError instance) => <String, dynamic>{
|
||||
'task': instance.task,
|
||||
'error': instance.error,
|
||||
'fatal': instance.fatal,
|
||||
};
|
||||
|
||||
@@ -88,6 +88,9 @@ class AuthInfo {
|
||||
_log.info(
|
||||
"Logged in as ${u.username} (${u.id}). isAdmin: ${u.isAdmin}. permissions: ${u.permissions}");
|
||||
await checkSessionInfo();
|
||||
if (canManageTasks == true) {
|
||||
await tasks.connect();
|
||||
}
|
||||
} else if (re.status == 401 || re.status == 1 || re.status == 404) {
|
||||
_user = null;
|
||||
} else {
|
||||
|
||||
@@ -37,9 +37,11 @@ final dio = Dio()
|
||||
..options.extra['withCredentials'] = true;
|
||||
Config? _prefs;
|
||||
EHApi? _api;
|
||||
PersistCookieJar? _jar;
|
||||
|
||||
Future<void> prepareJar() async {
|
||||
final jar = PersistCookieJar(storage: FileStorage(await getJarPath()));
|
||||
_jar = jar;
|
||||
dio.interceptors.add(CookieManager(jar));
|
||||
}
|
||||
|
||||
@@ -104,6 +106,10 @@ EHApi get api {
|
||||
return _api!;
|
||||
}
|
||||
|
||||
PersistCookieJar? get cookieJar {
|
||||
return _jar;
|
||||
}
|
||||
|
||||
final AuthInfo auth = AuthInfo();
|
||||
final Clipboard platformClipboard = Clipboard();
|
||||
final Display platformDisplay = Display();
|
||||
@@ -121,6 +127,7 @@ enum MoreVertSettings {
|
||||
markAsNsfw,
|
||||
markAsSfw,
|
||||
serverSettings,
|
||||
taskManager,
|
||||
}
|
||||
|
||||
void onMoreVertSettingsSelected(BuildContext context, MoreVertSettings value) {
|
||||
@@ -143,6 +150,9 @@ void onMoreVertSettingsSelected(BuildContext context, MoreVertSettings value) {
|
||||
case MoreVertSettings.serverSettings:
|
||||
context.push("/server_settings");
|
||||
break;
|
||||
case MoreVertSettings.taskManager:
|
||||
context.push("/task_manager");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -176,6 +186,11 @@ List<PopupMenuEntry<MoreVertSettings>> buildMoreVertSettings(
|
||||
value: MoreVertSettings.serverSettings,
|
||||
child: Text(AppLocalizations.of(context)!.serverSettings)));
|
||||
}
|
||||
if (path != "/task_manager" && auth.canManageTasks == true) {
|
||||
list.add(PopupMenuItem(
|
||||
value: MoreVertSettings.taskManager,
|
||||
child: Text(AppLocalizations.of(context)!.taskManager)));
|
||||
}
|
||||
var showNsfw = prefs.getBool("showNsfw") ?? false;
|
||||
list.add(PopupMenuItem(
|
||||
child: StatefulBuilder(
|
||||
|
||||
@@ -42,6 +42,16 @@ class HomeDrawer extends StatelessWidget {
|
||||
},
|
||||
)
|
||||
: Container(),
|
||||
auth.canManageTasks == true
|
||||
? ListTile(
|
||||
leading: const Icon(Icons.task),
|
||||
title: Text(AppLocalizations.of(context)!.taskManager),
|
||||
onTap: () {
|
||||
Scaffold.of(context).closeDrawer();
|
||||
context.push("/task_manager");
|
||||
},
|
||||
)
|
||||
: Container(),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.settings),
|
||||
title: Text(AppLocalizations.of(context)!.settings),
|
||||
|
||||
@@ -143,5 +143,11 @@
|
||||
"redirectToFlutter": "Redirect to Flutter frontend when accessing the root URL.",
|
||||
"downloadTimeoutCheckInterval": "The interval of checking download timeout",
|
||||
"downloadTimeoutCheckIntervalHelp": "The smaller the value, the more accurate the timeout detection, but the higher CPU usage.",
|
||||
"dockerHelper": "The server is running in a Docker container. Unless you know what you are doing, do not change this setting."
|
||||
"dockerHelper": "The server is running in a Docker container. Unless you know what you are doing, do not change this setting.",
|
||||
"taskManager": "Task Manager",
|
||||
"waiting": "Waiting",
|
||||
"running": "Running",
|
||||
"finished": "Finished",
|
||||
"failed": "Failed",
|
||||
"allTasks": "All Tasks"
|
||||
}
|
||||
|
||||
@@ -143,5 +143,11 @@
|
||||
"redirectToFlutter": "访问根 URL 时重定向到 flutter 前端。",
|
||||
"downloadTimeoutCheckInterval": "下载超时检测间隔",
|
||||
"downloadTimeoutCheckIntervalHelp": "值越小,检测准确性越高,但是消耗更多的 CPU。",
|
||||
"dockerHelper": "服务器运行在 Docker 容器中。除非你知道你在做什么,否则不要修改这个设置。"
|
||||
"dockerHelper": "服务器运行在 Docker 容器中。除非你知道你在做什么,否则不要修改这个设置。",
|
||||
"taskManager": "任务管理器",
|
||||
"waiting": "等待中",
|
||||
"running": "运行中",
|
||||
"finished": "已完成",
|
||||
"failed": "已失败",
|
||||
"allTasks": "所有任务"
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import 'logs/file.dart';
|
||||
import 'server_settings.dart';
|
||||
import 'set_server.dart';
|
||||
import 'settings.dart';
|
||||
import 'task_manager.dart';
|
||||
import 'utils.dart';
|
||||
import 'viewer/single.dart';
|
||||
|
||||
@@ -159,8 +160,12 @@ final _router = GoRouter(
|
||||
}),
|
||||
GoRoute(
|
||||
path: ServerSettingsPage.routeName,
|
||||
builder: (context, state) => const ServerSettingsPage(),
|
||||
)
|
||||
builder: (context, state) => ServerSettingsPage(key: state.pageKey),
|
||||
),
|
||||
GoRoute(
|
||||
path: TaskManagerPage.routeName,
|
||||
builder: (context, state) => TaskManagerPage(key: state.pageKey),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -225,13 +230,15 @@ class MainApp extends StatefulWidget {
|
||||
context.findAncestorStateOfType<_MainApp>()!;
|
||||
}
|
||||
|
||||
class _MainApp extends State<MainApp> {
|
||||
class _MainApp extends State<MainApp> with WidgetsBindingObserver {
|
||||
ThemeMode _themeMode = ThemeMode.system;
|
||||
ThemeData _themeData = ThemeData(useMaterial3: true);
|
||||
ThemeData _darkThemeData = ThemeData.dark(useMaterial3: true);
|
||||
ThemeMode get themeMode => _themeMode;
|
||||
Lang _lang = Lang.system;
|
||||
Lang get lang => _lang;
|
||||
AppLifecycleState? _lifecycleState;
|
||||
AppLifecycleState? get lifecycleState => _lifecycleState;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -250,6 +257,23 @@ class _MainApp extends State<MainApp> {
|
||||
_themeData = _themeData.useSystemChineseFont(Brightness.light);
|
||||
_darkThemeData = _darkThemeData.useSystemChineseFont(Brightness.dark);
|
||||
}
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
if (WidgetsBinding.instance.lifecycleState != null) {
|
||||
_lifecycleState = WidgetsBinding.instance.lifecycleState;
|
||||
listener.tryEmit("lifecycle", _lifecycleState);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
_lifecycleState = state;
|
||||
listener.tryEmit("lifecycle", _lifecycleState);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
119
lib/task.dart
119
lib/task.dart
@@ -1,8 +1,127 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:web_socket_channel/web_socket_channel.dart';
|
||||
import 'api/task.dart';
|
||||
import 'globals.dart';
|
||||
import 'utils/websocket.dart';
|
||||
|
||||
final _log = Logger("TaskManager");
|
||||
|
||||
class TaskManager {
|
||||
Map<int, TaskDetail> tasks = {};
|
||||
WebSocketChannel? _channel;
|
||||
bool _closed = false;
|
||||
bool _allowReconnect = true;
|
||||
Timer? _reconnectTimer;
|
||||
void clear() {
|
||||
tasks.clear();
|
||||
_channel?.stream.drain();
|
||||
_channel?.sink.close();
|
||||
_closed = true;
|
||||
}
|
||||
|
||||
Future<void> connect() async {
|
||||
if (auth.canManageTasks != true) return;
|
||||
try {
|
||||
_channel = await connectWebSocket(api.getTaskUrl());
|
||||
_channel!.stream.listen((event) {
|
||||
try {
|
||||
final data = jsonDecode(event) as Map<String, dynamic>;
|
||||
final type = data["type"] as String;
|
||||
if (type == "tasks") {
|
||||
final list = TaskList.fromJson(data);
|
||||
for (var task in list.tasks) {
|
||||
tasks[task.id] = TaskDetail(
|
||||
base: task,
|
||||
status: list.running.contains(task.id)
|
||||
? TaskStatus.running
|
||||
: TaskStatus.wait,
|
||||
);
|
||||
}
|
||||
} else if (type == "new_task") {
|
||||
final task = Task.fromJson(data["detail"] as Map<String, dynamic>);
|
||||
tasks[task.id] = TaskDetail(
|
||||
base: task,
|
||||
status: TaskStatus.wait,
|
||||
);
|
||||
} else if (type == "task_started") {
|
||||
final task = Task.fromJson(data["detail"] as Map<String, dynamic>);
|
||||
tasks.update(task.id, (value) {
|
||||
value.status = TaskStatus.running;
|
||||
return value;
|
||||
},
|
||||
ifAbsent: () => TaskDetail(
|
||||
base: task,
|
||||
status: TaskStatus.running,
|
||||
));
|
||||
} else if (type == "task_finished") {
|
||||
final task = Task.fromJson(data["detail"] as Map<String, dynamic>);
|
||||
if (tasks.containsKey(task.id)) {
|
||||
tasks.update(task.id, (value) {
|
||||
value.status = TaskStatus.finished;
|
||||
return value;
|
||||
});
|
||||
}
|
||||
} else if (type == "task_progress") {
|
||||
final task =
|
||||
TaskProgress.fromJson(data["detail"] as Map<String, dynamic>);
|
||||
if (tasks.containsKey(task.taskId)) {
|
||||
tasks.update(task.taskId, (value) {
|
||||
value.progress = task.detail;
|
||||
return value;
|
||||
});
|
||||
}
|
||||
} else if (type == "task_updated") {
|
||||
final task = Task.fromJson(data["detail"] as Map<String, dynamic>);
|
||||
if (tasks.containsKey(task.id)) {
|
||||
tasks.update(task.id, (value) {
|
||||
value.base = task;
|
||||
return value;
|
||||
});
|
||||
}
|
||||
} else if (type == "task_error") {
|
||||
final info =
|
||||
TaskError.fromJson(data["detail"] as Map<String, dynamic>);
|
||||
if (tasks.containsKey(info.task.id)) {
|
||||
tasks.update(info.task.id, (value) {
|
||||
value.status = TaskStatus.failed;
|
||||
value.error = info.error;
|
||||
value.fataled = info.fatal;
|
||||
return value;
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
_log.warning("Error processing task message: $e");
|
||||
}
|
||||
}, onError: (e) {
|
||||
_log.warning("Task websocket error: $e");
|
||||
if (_allowReconnect) {
|
||||
_log.info("Reconnecting to task server in 5 seconds");
|
||||
_reconnectTimer = Timer(const Duration(seconds: 5), () {
|
||||
_reconnectTimer = null;
|
||||
connect();
|
||||
});
|
||||
}
|
||||
}, cancelOnError: true);
|
||||
await _channel!.ready;
|
||||
_closed = false;
|
||||
sendTaskList();
|
||||
} catch (e) {
|
||||
_channel = null;
|
||||
_log.warning("Failed to connect to task server: $e");
|
||||
if (_allowReconnect) {
|
||||
_log.info("Reconnecting to task server in 5 seconds");
|
||||
_reconnectTimer = Timer(const Duration(seconds: 5), () {
|
||||
_reconnectTimer = null;
|
||||
connect();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void sendTaskList() {
|
||||
_channel?.sink.add("{\"type\":\"task_list\"}");
|
||||
}
|
||||
}
|
||||
|
||||
129
lib/task_manager.dart
Normal file
129
lib/task_manager.dart
Normal file
@@ -0,0 +1,129 @@
|
||||
import 'package:enum_flag/enum_flag.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'globals.dart';
|
||||
|
||||
enum TaskStatusFilterFlag with EnumFlag {
|
||||
wait,
|
||||
running,
|
||||
finished,
|
||||
failed;
|
||||
|
||||
String localText(BuildContext context) {
|
||||
final i18n = AppLocalizations.of(context)!;
|
||||
switch (this) {
|
||||
case TaskStatusFilterFlag.wait:
|
||||
return i18n.waiting;
|
||||
case TaskStatusFilterFlag.running:
|
||||
return i18n.running;
|
||||
case TaskStatusFilterFlag.finished:
|
||||
return i18n.finished;
|
||||
case TaskStatusFilterFlag.failed:
|
||||
return i18n.failed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const taskStatusFilterFlagAll = 15;
|
||||
|
||||
class TaskStatusFilter {
|
||||
TaskStatusFilter({this.code = taskStatusFilterFlagAll});
|
||||
int code;
|
||||
bool has(TaskStatusFilterFlag flag) => code.hasFlag(flag);
|
||||
bool get isAll => code == taskStatusFilterFlagAll;
|
||||
void add(TaskStatusFilterFlag flag) {
|
||||
code |= flag.value;
|
||||
}
|
||||
|
||||
void remove(TaskStatusFilterFlag flag) {
|
||||
code &= ~flag.value;
|
||||
}
|
||||
}
|
||||
|
||||
class TaskManagerPage extends StatefulWidget {
|
||||
const TaskManagerPage({super.key});
|
||||
|
||||
static const String routeName = '/task_manager';
|
||||
|
||||
@override
|
||||
State<TaskManagerPage> createState() => _TaskManagerPage();
|
||||
}
|
||||
|
||||
class _TaskManagerPage extends State<TaskManagerPage>
|
||||
with ThemeModeWidget, IsTopWidget2 {
|
||||
late TaskStatusFilter _filter;
|
||||
@override
|
||||
void initState() {
|
||||
_filter = TaskStatusFilter();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
Widget _buildChips() {
|
||||
final i18n = AppLocalizations.of(context)!;
|
||||
var list = <FilterChip>[
|
||||
FilterChip(
|
||||
label: Text(i18n.allTasks),
|
||||
selected: _filter.isAll,
|
||||
onSelected: (bool value) {
|
||||
setState(() {
|
||||
if (value) {
|
||||
_filter.code = taskStatusFilterFlagAll;
|
||||
} else {
|
||||
_filter.code = 0;
|
||||
}
|
||||
});
|
||||
},
|
||||
)
|
||||
];
|
||||
for (var flag in TaskStatusFilterFlag.values) {
|
||||
list.add(FilterChip(
|
||||
label: Text(flag.localText(context)),
|
||||
selected: _filter.has(flag),
|
||||
onSelected: (bool value) {
|
||||
setState(() {
|
||||
if (value) {
|
||||
_filter.add(flag);
|
||||
} else {
|
||||
_filter.remove(flag);
|
||||
}
|
||||
});
|
||||
},
|
||||
));
|
||||
}
|
||||
return SliverToBoxAdapter(
|
||||
child: Wrap(
|
||||
spacing: 5.0,
|
||||
children: list,
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final i18n = AppLocalizations.of(context)!;
|
||||
if (isTop(context)) {
|
||||
setCurrentTitle(i18n.taskManager, Theme.of(context).primaryColor.value);
|
||||
}
|
||||
return Scaffold(
|
||||
body: CustomScrollView(
|
||||
slivers: <Widget>[
|
||||
SliverAppBar(
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
onPressed: () {
|
||||
context.canPop() ? context.pop() : context.go("/");
|
||||
},
|
||||
),
|
||||
title: Text(i18n.taskManager),
|
||||
actions: [
|
||||
buildThemeModeIcon(context),
|
||||
buildMoreVertSettingsButon(context),
|
||||
],
|
||||
floating: true,
|
||||
),
|
||||
_buildChips(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
1
lib/utils/websocket.dart
Normal file
1
lib/utils/websocket.dart
Normal file
@@ -0,0 +1 @@
|
||||
export './websocket_io.dart' if (dart.library.html) './websocket_web.dart';
|
||||
25
lib/utils/websocket_io.dart
Normal file
25
lib/utils/websocket_io.dart
Normal file
@@ -0,0 +1,25 @@
|
||||
import 'package:web_socket_channel/io.dart';
|
||||
import 'package:web_socket_channel/web_socket_channel.dart';
|
||||
import '../globals.dart';
|
||||
|
||||
Future<WebSocketChannel> connectWebSocket(Uri uri) async {
|
||||
final Map<String, String> headers = {};
|
||||
final jar = cookieJar;
|
||||
if (jar != null) {
|
||||
final nuri =Uri(
|
||||
scheme: uri.scheme == "wss" ? "https" : "http",
|
||||
userInfo: uri.userInfo,
|
||||
host: uri.host,
|
||||
port: uri.port,
|
||||
path: uri.path,
|
||||
query: uri.query,
|
||||
);
|
||||
final cookies = await jar.loadForRequest(nuri);
|
||||
final list = <String>[];
|
||||
for (var cookie in cookies) {
|
||||
list.add('${cookie.name}=${cookie.value}');
|
||||
}
|
||||
headers['cookie'] = list.join('; ');
|
||||
}
|
||||
return IOWebSocketChannel.connect(uri, headers: headers);
|
||||
}
|
||||
6
lib/utils/websocket_web.dart
Normal file
6
lib/utils/websocket_web.dart
Normal file
@@ -0,0 +1,6 @@
|
||||
import 'package:web_socket_channel/html.dart';
|
||||
import 'package:web_socket_channel/web_socket_channel.dart';
|
||||
|
||||
Future<WebSocketChannel> connectWebSocket(Uri uri) async {
|
||||
return HtmlWebSocketChannel.connect(uri, binaryType: BinaryType.list);
|
||||
}
|
||||
@@ -61,12 +61,12 @@ SPEC CHECKSUMS:
|
||||
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
|
||||
irondash_engine_context: da62996ee25616d2f01bbeb85dc115d813359478
|
||||
package_info_plus: 02d7a575e80f194102bef286361c6c326e4c29ce
|
||||
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
|
||||
path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
|
||||
screen_retriever: 59634572a57080243dd1bf715e55b6c54f241a38
|
||||
shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126
|
||||
shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695
|
||||
super_native_extensions: 85efee3a7495b46b04befcfc86ed12069264ebf3
|
||||
window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8
|
||||
|
||||
PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367
|
||||
|
||||
COCOAPODS: 1.15.0
|
||||
COCOAPODS: 1.15.2
|
||||
|
||||
@@ -258,7 +258,7 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 0920;
|
||||
LastUpgradeCheck = 1430;
|
||||
LastUpgradeCheck = 1510;
|
||||
ORGANIZATIONNAME = "";
|
||||
TargetAttributes = {
|
||||
331C80D4294CF70F00263BE5 = {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1430"
|
||||
LastUpgradeVersion = "1510"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
Reference in New Issue
Block a user