feat: add web receive
CI / format (push) Has been cancelled
CI / test (push) Has been cancelled
CI / packaging (push) Has been cancelled

This commit is contained in:
Tien Do Nam
2026-08-03 04:33:21 +02:00
parent 62d5ca9687
commit 73ff2fe6e2
37 changed files with 1247 additions and 217 deletions
@@ -7,7 +7,8 @@ import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
import 'package:localsend_isolates/rust/api/model.dart';
import 'package:localsend_isolates/rust/frb_generated.dart';
// These functions are ignored because they are not marked as `pub`: `rs_stored_device`
// These functions are ignored because they are not marked as `pub`: `rs_stored_device`, `stop`
// These types are ignored because they are neither used by any `pub` functions nor (for structs and enums) marked `#[frb(unignore)]`: `DiscoveryInstance`
/// Starts the discovery: binds the UDP multicast sockets on all usable
/// network interfaces, answers announcements of other devices with an HTTP
@@ -88,6 +89,9 @@ abstract class RsDiscovery implements RustOpaqueInterface {
/// Emits a [RsStoredDevice] for every device confirmation until the
/// discovery is stopped. Can only be listened to once.
///
/// Also returns when the Dart side of the stream is gone (e.g. after a
/// hot restart), so this call does not keep the discovery alive forever.
Stream<RsStoredDevice> listen();
/// The reason the multicast sockets could not be bound, when they could
@@ -10,13 +10,15 @@ import 'package:localsend_isolates/rust/frb_generated.dart';
part 'server.freezed.dart';
// These functions are ignored because they are not marked as `pub`: `handle_server_event`, `handle_web_event`, `recv_opt`, `resolve_file_content`, `resolve_upload_target`
// These functions are ignored because they are not marked as `pub`: `handle_server_event`, `handle_web_event`, `recv_opt`, `resolve_file_content`, `resolve_upload_target`, `stop`
// These types are ignored because they are neither used by any `pub` functions nor (for structs and enums) marked `#[frb(unignore)]`: `ServerInstance`
/// Starts the HTTP server on the given port (IPv4 and IPv6).
/// The server runs until [RsHttpServer::stop] is called.
///
/// Passing [web_send] additionally enables the web send (download API) so that
/// web browsers can download the offered files.
/// Passing [web] additionally serves the web pages: the download page when
/// [WebParams::send] is set (so web browsers can download the offered files)
/// or the upload page when [WebParams::upload] is enabled.
///
/// Passing [show_token] enables the internal `show` endpoint that lets another
/// application instance request this one to show itself (emitted as
@@ -32,7 +34,7 @@ Future<RsHttpServer> startServer({
DeviceType? deviceType,
required String fingerprint,
String? pin,
WebSendParams? webSend,
WebParams? web,
String? showToken,
}) => RustLib.instance.api.crateApiServerStartServer(
port: port,
@@ -43,7 +45,7 @@ Future<RsHttpServer> startServer({
deviceType: deviceType,
fingerprint: fingerprint,
pin: pin,
webSend: webSend,
web: web,
showToken: showToken,
);
@@ -77,6 +79,9 @@ abstract class RsHttpServer implements RustOpaqueInterface {
///
/// The v2 protocol, the web send (download API), and the internal endpoint
/// events are all emitted on the same stream.
///
/// Also returns when the Dart side of the stream is gone (e.g. after a
/// hot restart), so this call does not keep the server alive forever.
Stream<RsServerEvent> listen();
/// Answers the pending [RsServerEvent::WebFileDownload] event with the source
@@ -266,22 +271,26 @@ class TlsConfig {
identical(this, other) || other is TlsConfig && runtimeType == other.runtimeType && cert == other.cert && privateKey == other.privateKey;
}
class WebSendI18n {
class WebI18n {
final String waiting;
final String enterPin;
final String invalidPin;
final String tooManyAttempts;
final String rejected;
final String uploadRejected;
final String busy;
final String files;
final String fileName;
final String size;
const WebSendI18n({
const WebI18n({
required this.waiting,
required this.enterPin,
required this.invalidPin,
required this.tooManyAttempts,
required this.rejected,
required this.uploadRejected,
required this.busy,
required this.files,
required this.fileName,
required this.size,
@@ -294,6 +303,8 @@ class WebSendI18n {
invalidPin.hashCode ^
tooManyAttempts.hashCode ^
rejected.hashCode ^
uploadRejected.hashCode ^
busy.hashCode ^
files.hashCode ^
fileName.hashCode ^
size.hashCode;
@@ -301,22 +312,53 @@ class WebSendI18n {
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is WebSendI18n &&
other is WebI18n &&
runtimeType == other.runtimeType &&
waiting == other.waiting &&
enterPin == other.enterPin &&
invalidPin == other.invalidPin &&
tooManyAttempts == other.tooManyAttempts &&
rejected == other.rejected &&
uploadRejected == other.uploadRejected &&
busy == other.busy &&
files == other.files &&
fileName == other.fileName &&
size == other.size;
}
/// Configuration for the web pages served to browsers. When omitted, the web
/// pages respond with 403 and only the v2 endpoints run.
class WebParams {
/// Enables web send (the download page): files offered for download by web
/// browsers. `null` disables the download page and the download API.
final WebSendParams? send;
/// Serves the upload page so web browsers can upload files via the v2
/// `prepare-upload`/`upload` endpoints. Ignored when [WebParams::send] is
/// set: the download page takes precedence at `/`.
final bool upload;
/// Translations for the web pages, served via `/i18n.json`.
final WebI18n i18N;
const WebParams({
this.send,
required this.upload,
required this.i18N,
});
@override
int get hashCode => send.hashCode ^ upload.hashCode ^ i18N.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is WebParams && runtimeType == other.runtimeType && send == other.send && upload == other.upload && i18N == other.i18N;
}
/// Configuration for web send: files offered for download by web browsers.
///
/// Web send can be enabled independently of the v2 protocol endpoints. When
/// omitted, the download API responds with 403 and only the v2 endpoints run.
/// Web send can be enabled independently of the v2 protocol endpoints.
class WebSendParams {
/// The metadata of the files offered for download, mapped by file ID.
/// The content is requested per download via [RsServerEvent::WebFileDownload].
@@ -325,20 +367,15 @@ class WebSendParams {
/// Optional PIN that web clients must provide via the `pin` query parameter.
final String? pin;
/// Translations for the web page, served via `/i18n.json`.
final WebSendI18n i18N;
const WebSendParams({
required this.files,
this.pin,
required this.i18N,
});
@override
int get hashCode => files.hashCode ^ pin.hashCode ^ i18N.hashCode;
int get hashCode => files.hashCode ^ pin.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is WebSendParams && runtimeType == other.runtimeType && files == other.files && pin == other.pin && i18N == other.i18N;
identical(this, other) || other is WebSendParams && runtimeType == other.runtimeType && files == other.files && pin == other.pin;
}
@@ -302,7 +302,7 @@ abstract class RustLibApi extends BaseApi {
DeviceType? deviceType,
required String fingerprint,
String? pin,
WebSendParams? webSend,
WebParams? web,
String? showToken,
});
@@ -2043,7 +2043,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
DeviceType? deviceType,
required String fingerprint,
String? pin,
WebSendParams? webSend,
WebParams? web,
String? showToken,
}) {
return handler.executeNormal(
@@ -2058,7 +2058,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_opt_box_autoadd_device_type(deviceType, serializer);
sse_encode_String(fingerprint, serializer);
sse_encode_opt_String(pin, serializer);
sse_encode_opt_box_autoadd_web_send_params(webSend, serializer);
sse_encode_opt_box_autoadd_web_params(web, serializer);
sse_encode_opt_String(showToken, serializer);
pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 55, port: port_);
},
@@ -2067,7 +2067,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
decodeErrorData: sse_decode_AnyhowException,
),
constMeta: kCrateApiServerStartServerConstMeta,
argValues: [port, tls, alias, version, deviceModel, deviceType, fingerprint, pin, webSend, showToken],
argValues: [port, tls, alias, version, deviceModel, deviceType, fingerprint, pin, web, showToken],
apiImpl: this,
),
);
@@ -2075,7 +2075,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
TaskConstMeta get kCrateApiServerStartServerConstMeta => const TaskConstMeta(
debugName: 'start_server',
argNames: ['port', 'tls', 'alias', 'version', 'deviceModel', 'deviceType', 'fingerprint', 'pin', 'webSend', 'showToken'],
argNames: ['port', 'tls', 'alias', 'version', 'deviceModel', 'deviceType', 'fingerprint', 'pin', 'web', 'showToken'],
);
@override
@@ -2627,6 +2627,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return raw as int;
}
@protected
WebParams dco_decode_box_autoadd_web_params(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
return dco_decode_web_params(raw);
}
@protected
WebSendParams dco_decode_box_autoadd_web_send_params(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
@@ -2864,6 +2870,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return raw == null ? null : dco_decode_box_autoadd_u_32(raw);
}
@protected
WebParams? dco_decode_opt_box_autoadd_web_params(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
return raw == null ? null : dco_decode_box_autoadd_web_params(raw);
}
@protected
WebSendParams? dco_decode_opt_box_autoadd_web_send_params(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
@@ -3315,19 +3327,33 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
}
@protected
WebSendI18n dco_decode_web_send_i_18_n(dynamic raw) {
WebI18n dco_decode_web_i_18_n(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
final arr = raw as List<dynamic>;
if (arr.length != 8) throw Exception('unexpected arr length: expect 8 but see ${arr.length}');
return WebSendI18n(
if (arr.length != 10) throw Exception('unexpected arr length: expect 10 but see ${arr.length}');
return WebI18n(
waiting: dco_decode_String(arr[0]),
enterPin: dco_decode_String(arr[1]),
invalidPin: dco_decode_String(arr[2]),
tooManyAttempts: dco_decode_String(arr[3]),
rejected: dco_decode_String(arr[4]),
files: dco_decode_String(arr[5]),
fileName: dco_decode_String(arr[6]),
size: dco_decode_String(arr[7]),
uploadRejected: dco_decode_String(arr[5]),
busy: dco_decode_String(arr[6]),
files: dco_decode_String(arr[7]),
fileName: dco_decode_String(arr[8]),
size: dco_decode_String(arr[9]),
);
}
@protected
WebParams dco_decode_web_params(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
final arr = raw as List<dynamic>;
if (arr.length != 3) throw Exception('unexpected arr length: expect 3 but see ${arr.length}');
return WebParams(
send: dco_decode_opt_box_autoadd_web_send_params(arr[0]),
upload: dco_decode_bool(arr[1]),
i18N: dco_decode_web_i_18_n(arr[2]),
);
}
@@ -3335,11 +3361,10 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
WebSendParams dco_decode_web_send_params(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
final arr = raw as List<dynamic>;
if (arr.length != 3) throw Exception('unexpected arr length: expect 3 but see ${arr.length}');
if (arr.length != 2) throw Exception('unexpected arr length: expect 2 but see ${arr.length}');
return WebSendParams(
files: dco_decode_Map_String_file_dto_None(arr[0]),
pin: dco_decode_opt_String(arr[1]),
i18N: dco_decode_web_send_i_18_n(arr[2]),
);
}
@@ -3844,6 +3869,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return (sse_decode_u_32(deserializer));
}
@protected
WebParams sse_decode_box_autoadd_web_params(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
return (sse_decode_web_params(deserializer));
}
@protected
WebSendParams sse_decode_box_autoadd_web_send_params(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
@@ -4166,6 +4197,17 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
}
}
@protected
WebParams? sse_decode_opt_box_autoadd_web_params(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
if (sse_decode_bool(deserializer)) {
return (sse_decode_box_autoadd_web_params(deserializer));
} else {
return null;
}
}
@protected
WebSendParams? sse_decode_opt_box_autoadd_web_send_params(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
@@ -4610,35 +4652,47 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
}
@protected
WebSendI18n sse_decode_web_send_i_18_n(SseDeserializer deserializer) {
WebI18n sse_decode_web_i_18_n(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
var var_waiting = sse_decode_String(deserializer);
var var_enterPin = sse_decode_String(deserializer);
var var_invalidPin = sse_decode_String(deserializer);
var var_tooManyAttempts = sse_decode_String(deserializer);
var var_rejected = sse_decode_String(deserializer);
var var_uploadRejected = sse_decode_String(deserializer);
var var_busy = sse_decode_String(deserializer);
var var_files = sse_decode_String(deserializer);
var var_fileName = sse_decode_String(deserializer);
var var_size = sse_decode_String(deserializer);
return WebSendI18n(
return WebI18n(
waiting: var_waiting,
enterPin: var_enterPin,
invalidPin: var_invalidPin,
tooManyAttempts: var_tooManyAttempts,
rejected: var_rejected,
uploadRejected: var_uploadRejected,
busy: var_busy,
files: var_files,
fileName: var_fileName,
size: var_size,
);
}
@protected
WebParams sse_decode_web_params(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
var var_send = sse_decode_opt_box_autoadd_web_send_params(deserializer);
var var_upload = sse_decode_bool(deserializer);
var var_i18N = sse_decode_web_i_18_n(deserializer);
return WebParams(send: var_send, upload: var_upload, i18N: var_i18N);
}
@protected
WebSendParams sse_decode_web_send_params(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
var var_files = sse_decode_Map_String_file_dto_None(deserializer);
var var_pin = sse_decode_opt_String(deserializer);
var var_i18N = sse_decode_web_send_i_18_n(deserializer);
return WebSendParams(files: var_files, pin: var_pin, i18N: var_i18N);
return WebSendParams(files: var_files, pin: var_pin);
}
@protected
@@ -5253,6 +5307,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_u_32(self, serializer);
}
@protected
void sse_encode_box_autoadd_web_params(WebParams self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
sse_encode_web_params(self, serializer);
}
@protected
void sse_encode_box_autoadd_web_send_params(WebSendParams self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
@@ -5530,6 +5590,16 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
}
}
@protected
void sse_encode_opt_box_autoadd_web_params(WebParams? self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
sse_encode_bool(self != null, serializer);
if (self != null) {
sse_encode_box_autoadd_web_params(self, serializer);
}
}
@protected
void sse_encode_opt_box_autoadd_web_send_params(WebSendParams? self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
@@ -5892,24 +5962,33 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
}
@protected
void sse_encode_web_send_i_18_n(WebSendI18n self, SseSerializer serializer) {
void sse_encode_web_i_18_n(WebI18n self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
sse_encode_String(self.waiting, serializer);
sse_encode_String(self.enterPin, serializer);
sse_encode_String(self.invalidPin, serializer);
sse_encode_String(self.tooManyAttempts, serializer);
sse_encode_String(self.rejected, serializer);
sse_encode_String(self.uploadRejected, serializer);
sse_encode_String(self.busy, serializer);
sse_encode_String(self.files, serializer);
sse_encode_String(self.fileName, serializer);
sse_encode_String(self.size, serializer);
}
@protected
void sse_encode_web_params(WebParams self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
sse_encode_opt_box_autoadd_web_send_params(self.send, serializer);
sse_encode_bool(self.upload, serializer);
sse_encode_web_i_18_n(self.i18N, serializer);
}
@protected
void sse_encode_web_send_params(WebSendParams self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
sse_encode_Map_String_file_dto_None(self.files, serializer);
sse_encode_opt_String(self.pin, serializer);
sse_encode_web_send_i_18_n(self.i18N, serializer);
}
@protected
@@ -6104,6 +6183,9 @@ class RsDiscoveryImpl extends RustOpaque implements RsDiscovery {
/// Emits a [RsStoredDevice] for every device confirmation until the
/// discovery is stopped. Can only be listened to once.
///
/// Also returns when the Dart side of the stream is gone (e.g. after a
/// hot restart), so this call does not keep the discovery alive forever.
Stream<RsStoredDevice> listen() => RustLib.instance.api.crateApiDiscoveryRsDiscoveryListen(
that: this,
);
@@ -6259,6 +6341,9 @@ class RsHttpServerImpl extends RustOpaque implements RsHttpServer {
///
/// The v2 protocol, the web send (download API), and the internal endpoint
/// events are all emitted on the same stream.
///
/// Also returns when the Dart side of the stream is gone (e.g. after a
/// hot restart), so this call does not keep the server alive forever.
Stream<RsServerEvent> listen() => RustLib.instance.api.crateApiServerRsHttpServerListen(
that: this,
);
@@ -279,6 +279,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
int dco_decode_box_autoadd_u_32(dynamic raw);
@protected
WebParams dco_decode_box_autoadd_web_params(dynamic raw);
@protected
WebSendParams dco_decode_box_autoadd_web_send_params(dynamic raw);
@@ -376,6 +379,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
int? dco_decode_opt_box_autoadd_u_32(dynamic raw);
@protected
WebParams? dco_decode_opt_box_autoadd_web_params(dynamic raw);
@protected
WebSendParams? dco_decode_opt_box_autoadd_web_send_params(dynamic raw);
@@ -482,7 +488,10 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
BigInt dco_decode_usize(dynamic raw);
@protected
WebSendI18n dco_decode_web_send_i_18_n(dynamic raw);
WebI18n dco_decode_web_i_18_n(dynamic raw);
@protected
WebParams dco_decode_web_params(dynamic raw);
@protected
WebSendParams dco_decode_web_send_params(dynamic raw);
@@ -728,6 +737,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
int sse_decode_box_autoadd_u_32(SseDeserializer deserializer);
@protected
WebParams sse_decode_box_autoadd_web_params(SseDeserializer deserializer);
@protected
WebSendParams sse_decode_box_autoadd_web_send_params(SseDeserializer deserializer);
@@ -827,6 +839,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
int? sse_decode_opt_box_autoadd_u_32(SseDeserializer deserializer);
@protected
WebParams? sse_decode_opt_box_autoadd_web_params(SseDeserializer deserializer);
@protected
WebSendParams? sse_decode_opt_box_autoadd_web_send_params(SseDeserializer deserializer);
@@ -933,7 +948,10 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
BigInt sse_decode_usize(SseDeserializer deserializer);
@protected
WebSendI18n sse_decode_web_send_i_18_n(SseDeserializer deserializer);
WebI18n sse_decode_web_i_18_n(SseDeserializer deserializer);
@protected
WebParams sse_decode_web_params(SseDeserializer deserializer);
@protected
WebSendParams sse_decode_web_send_params(SseDeserializer deserializer);
@@ -1224,6 +1242,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
void sse_encode_box_autoadd_u_32(int self, SseSerializer serializer);
@protected
void sse_encode_box_autoadd_web_params(WebParams self, SseSerializer serializer);
@protected
void sse_encode_box_autoadd_web_send_params(WebSendParams self, SseSerializer serializer);
@@ -1323,6 +1344,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
void sse_encode_opt_box_autoadd_u_32(int? self, SseSerializer serializer);
@protected
void sse_encode_opt_box_autoadd_web_params(WebParams? self, SseSerializer serializer);
@protected
void sse_encode_opt_box_autoadd_web_send_params(WebSendParams? self, SseSerializer serializer);
@@ -1430,7 +1454,10 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
void sse_encode_usize(BigInt self, SseSerializer serializer);
@protected
void sse_encode_web_send_i_18_n(WebSendI18n self, SseSerializer serializer);
void sse_encode_web_i_18_n(WebI18n self, SseSerializer serializer);
@protected
void sse_encode_web_params(WebParams self, SseSerializer serializer);
@protected
void sse_encode_web_send_params(WebSendParams self, SseSerializer serializer);
@@ -281,6 +281,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
int dco_decode_box_autoadd_u_32(dynamic raw);
@protected
WebParams dco_decode_box_autoadd_web_params(dynamic raw);
@protected
WebSendParams dco_decode_box_autoadd_web_send_params(dynamic raw);
@@ -378,6 +381,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
int? dco_decode_opt_box_autoadd_u_32(dynamic raw);
@protected
WebParams? dco_decode_opt_box_autoadd_web_params(dynamic raw);
@protected
WebSendParams? dco_decode_opt_box_autoadd_web_send_params(dynamic raw);
@@ -484,7 +490,10 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
BigInt dco_decode_usize(dynamic raw);
@protected
WebSendI18n dco_decode_web_send_i_18_n(dynamic raw);
WebI18n dco_decode_web_i_18_n(dynamic raw);
@protected
WebParams dco_decode_web_params(dynamic raw);
@protected
WebSendParams dco_decode_web_send_params(dynamic raw);
@@ -730,6 +739,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
int sse_decode_box_autoadd_u_32(SseDeserializer deserializer);
@protected
WebParams sse_decode_box_autoadd_web_params(SseDeserializer deserializer);
@protected
WebSendParams sse_decode_box_autoadd_web_send_params(SseDeserializer deserializer);
@@ -829,6 +841,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
int? sse_decode_opt_box_autoadd_u_32(SseDeserializer deserializer);
@protected
WebParams? sse_decode_opt_box_autoadd_web_params(SseDeserializer deserializer);
@protected
WebSendParams? sse_decode_opt_box_autoadd_web_send_params(SseDeserializer deserializer);
@@ -935,7 +950,10 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
BigInt sse_decode_usize(SseDeserializer deserializer);
@protected
WebSendI18n sse_decode_web_send_i_18_n(SseDeserializer deserializer);
WebI18n sse_decode_web_i_18_n(SseDeserializer deserializer);
@protected
WebParams sse_decode_web_params(SseDeserializer deserializer);
@protected
WebSendParams sse_decode_web_send_params(SseDeserializer deserializer);
@@ -1226,6 +1244,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
void sse_encode_box_autoadd_u_32(int self, SseSerializer serializer);
@protected
void sse_encode_box_autoadd_web_params(WebParams self, SseSerializer serializer);
@protected
void sse_encode_box_autoadd_web_send_params(WebSendParams self, SseSerializer serializer);
@@ -1325,6 +1346,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
void sse_encode_opt_box_autoadd_u_32(int? self, SseSerializer serializer);
@protected
void sse_encode_opt_box_autoadd_web_params(WebParams? self, SseSerializer serializer);
@protected
void sse_encode_opt_box_autoadd_web_send_params(WebSendParams? self, SseSerializer serializer);
@@ -1432,7 +1456,10 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
void sse_encode_usize(BigInt self, SseSerializer serializer);
@protected
void sse_encode_web_send_i_18_n(WebSendI18n self, SseSerializer serializer);
void sse_encode_web_i_18_n(WebI18n self, SseSerializer serializer);
@protected
void sse_encode_web_params(WebParams self, SseSerializer serializer);
@protected
void sse_encode_web_send_params(WebSendParams self, SseSerializer serializer);
@@ -30,9 +30,9 @@ class HttpServerStartTask implements BaseHttpServerTask {
/// Optional PIN that senders must provide to start an upload session.
final String? pin;
/// Enables web send (download API) so web browsers can download the offered files.
/// `null` disables web send.
final WebSendParams? webSend;
/// Serves the web pages: the download page (web send) and/or the upload page.
/// `null` disables the web pages.
final WebParams? web;
/// Enables the internal `show` endpoint, guarded by this token, that lets another
/// application instance request this one to show itself. `null` disables it.
@@ -40,7 +40,7 @@ class HttpServerStartTask implements BaseHttpServerTask {
HttpServerStartTask({
required this.pin,
required this.webSend,
required this.web,
required this.showToken,
});
}
@@ -398,7 +398,7 @@ Future<void> setupHttpServerIsolate(
deviceType: syncState.deviceInfo.deviceType.toRust(),
fingerprint: syncState.securityContext.certificateHash,
pin: startTask.pin,
webSend: startTask.webSend,
web: startTask.web,
showToken: startTask.showToken,
);
} catch (e) {
@@ -1,7 +1,7 @@
import 'dart:async';
import 'package:localsend_isolates/model/device.dart';
import 'package:localsend_isolates/rust/api/server.dart' show WebSendParams;
import 'package:localsend_isolates/rust/api/server.dart' show WebParams;
import 'package:localsend_isolates/src/isolate/child/discovery_isolate.dart';
import 'package:localsend_isolates/src/isolate/child/server_isolate.dart';
import 'package:localsend_isolates/src/isolate/child/upload_isolate.dart';
@@ -256,9 +256,9 @@ class IsolateHttpUploadCancelAction extends ReduxAction<IsolateController, Paren
class IsolateHttpServerStartAction extends ReduxActionWithResult<IsolateController, ParentIsolateState, Stream<HttpServerEvent>> {
final String? pin;
/// Enables web send (download API) so web browsers can download the offered files.
/// `null` disables web send.
final WebSendParams? webSend;
/// Serves the web pages: the download page (web send) and/or the upload page.
/// `null` disables the web pages.
final WebParams? web;
/// Enables the internal `show` endpoint, guarded by this token, that lets another
/// application instance request this one to show itself. `null` disables it.
@@ -266,7 +266,7 @@ class IsolateHttpServerStartAction extends ReduxActionWithResult<IsolateControll
IsolateHttpServerStartAction({
required this.pin,
required this.webSend,
required this.web,
required this.showToken,
});
@@ -282,7 +282,7 @@ class IsolateHttpServerStartAction extends ReduxActionWithResult<IsolateControll
connection.sendWrappedTaskAndListenStream(
task: HttpServerStartTask(
pin: pin,
webSend: webSend,
web: web,
showToken: showToken,
),
),
@@ -22,7 +22,7 @@ class HttpServerService {
required DeviceType? deviceType,
required String fingerprint,
required String? pin,
required WebSendParams? webSend,
required WebParams? web,
required String? showToken,
}) async {
if (_server != null) {
@@ -38,7 +38,7 @@ class HttpServerService {
deviceType: deviceType,
fingerprint: fingerprint,
pin: pin,
webSend: webSend,
web: web,
showToken: showToken,
);
_server = server;
+1 -1
View File
@@ -21,7 +21,7 @@ dependencies:
mime: 2.0.0
path: 1.9.1
pool: 1.5.2
refena_flutter: 3.2.1
refena_flutter: ^3.4.0
rust_lib_localsend_app:
path: rust_builder
typed_isolates:
@@ -7,8 +7,8 @@ use localsend::http::server::common::save::FileUploadTarget;
use localsend::http::server::internal::{InternalConfig, InternalEvent};
pub use localsend::http::server::v2::SessionEndReasonV2;
use localsend::http::server::v2::{PrepareUploadDecisionV2, ServerEventV2};
pub use localsend::http::server::web::WebSendI18n;
use localsend::http::server::web::{WebSendConfig, WebSendEvent};
pub use localsend::http::server::web::WebI18n;
use localsend::http::server::web::{WebConfig, WebSendConfig, WebSendEvent};
use localsend::http::state::ClientInfo;
use localsend::model::discovery::DeviceType;
use localsend::model::discovery::ProtocolType;
@@ -134,10 +134,25 @@ impl ServerInstance {
/// binding again.
static RUNNING_SERVER: Mutex<Option<Arc<ServerInstance>>> = Mutex::const_new(None);
/// Configuration for the web pages served to browsers. When omitted, the web
/// pages respond with 403 and only the v2 endpoints run.
pub struct WebParams {
/// Enables web send (the download page): files offered for download by web
/// browsers. `null` disables the download page and the download API.
pub send: Option<WebSendParams>,
/// Serves the upload page so web browsers can upload files via the v2
/// `prepare-upload`/`upload` endpoints. Ignored when [WebParams::send] is
/// set: the download page takes precedence at `/`.
pub upload: bool,
/// Translations for the web pages, served via `/i18n.json`.
pub i18n: WebI18n,
}
/// Configuration for web send: files offered for download by web browsers.
///
/// Web send can be enabled independently of the v2 protocol endpoints. When
/// omitted, the download API responds with 403 and only the v2 endpoints run.
/// Web send can be enabled independently of the v2 protocol endpoints.
pub struct WebSendParams {
/// The metadata of the files offered for download, mapped by file ID.
/// The content is requested per download via [RsServerEvent::WebFileDownload].
@@ -145,16 +160,14 @@ pub struct WebSendParams {
/// Optional PIN that web clients must provide via the `pin` query parameter.
pub pin: Option<String>,
/// Translations for the web page, served via `/i18n.json`.
pub i18n: WebSendI18n,
}
/// Starts the HTTP server on the given port (IPv4 and IPv6).
/// The server runs until [RsHttpServer::stop] is called.
///
/// Passing [web_send] additionally enables the web send (download API) so that
/// web browsers can download the offered files.
/// Passing [web] additionally serves the web pages: the download page when
/// [WebParams::send] is set (so web browsers can download the offered files)
/// or the upload page when [WebParams::upload] is enabled.
///
/// Passing [show_token] enables the internal `show` endpoint that lets another
/// application instance request this one to show itself (emitted as
@@ -170,7 +183,7 @@ pub async fn start_server(
device_type: Option<DeviceType>,
fingerprint: String,
pin: Option<String>,
web_send: Option<WebSendParams>,
web: Option<WebParams>,
show_token: Option<String>,
) -> anyhow::Result<RsHttpServer> {
// Stop a server left over from before a hot restart (its Dart owner died
@@ -183,16 +196,26 @@ pub async fn start_server(
let (event_tx, event_rx) = mpsc::channel::<ServerEventV2>(16);
let (stop_tx, stop_rx) = oneshot::channel::<()>();
let (web_send_config, web_event_rx) = match web_send {
Some(web_send) => {
let (web_event_tx, web_event_rx) = mpsc::channel::<WebSendEvent>(16);
let config = WebSendConfig {
files: web_send.files,
pin: web_send.pin,
i18n: web_send.i18n,
event_tx: web_event_tx,
let (web_config, web_event_rx) = match web {
Some(web) => {
let (send_config, web_event_rx) = match web.send {
Some(send) => {
let (web_event_tx, web_event_rx) = mpsc::channel::<WebSendEvent>(16);
let config = WebSendConfig {
files: send.files,
pin: send.pin,
event_tx: web_event_tx,
};
(Some(config), Some(web_event_rx))
}
None => (None, None),
};
(Some(config), Some(web_event_rx))
let config = WebConfig {
send: send_config,
upload: web.upload,
i18n: web.i18n,
};
(Some(config), web_event_rx)
}
None => (None, None),
};
@@ -221,7 +244,7 @@ pub async fn start_server(
},
internal_config,
Some(ServerConfigV2 { pin, event_tx }),
web_send_config,
web_config,
stop_rx,
)
.await?;
@@ -698,13 +721,15 @@ fn resolve_file_content(
}
}
#[frb(mirror(WebSendI18n))]
pub struct _WebSendI18n {
#[frb(mirror(WebI18n))]
pub struct _WebI18n {
pub waiting: String,
pub enter_pin: String,
pub invalid_pin: String,
pub too_many_attempts: String,
pub rejected: String,
pub upload_rejected: String,
pub busy: String,
pub files: String,
pub file_name: String,
pub size: String,
@@ -3201,8 +3201,7 @@ fn wire__crate__api__server__start_server_impl(
<Option<crate::api::model::DeviceType>>::sse_decode(&mut deserializer);
let api_fingerprint = <String>::sse_decode(&mut deserializer);
let api_pin = <Option<String>>::sse_decode(&mut deserializer);
let api_web_send =
<Option<crate::api::server::WebSendParams>>::sse_decode(&mut deserializer);
let api_web = <Option<crate::api::server::WebParams>>::sse_decode(&mut deserializer);
let api_show_token = <Option<String>>::sse_decode(&mut deserializer);
deserializer.end();
move |context| async move {
@@ -3217,7 +3216,7 @@ fn wire__crate__api__server__start_server_impl(
api_device_type,
api_fingerprint,
api_pin,
api_web_send,
api_web,
api_show_token,
)
.await?;
@@ -3383,15 +3382,17 @@ const _: fn() = || {
let _: String = TlsConfig.private_key;
}
{
let WebSendI18n = None::<crate::api::server::WebSendI18n>.unwrap();
let _: String = WebSendI18n.waiting;
let _: String = WebSendI18n.enter_pin;
let _: String = WebSendI18n.invalid_pin;
let _: String = WebSendI18n.too_many_attempts;
let _: String = WebSendI18n.rejected;
let _: String = WebSendI18n.files;
let _: String = WebSendI18n.file_name;
let _: String = WebSendI18n.size;
let WebI18n = None::<crate::api::server::WebI18n>.unwrap();
let _: String = WebI18n.waiting;
let _: String = WebI18n.enter_pin;
let _: String = WebI18n.invalid_pin;
let _: String = WebI18n.too_many_attempts;
let _: String = WebI18n.rejected;
let _: String = WebI18n.upload_rejected;
let _: String = WebI18n.busy;
let _: String = WebI18n.files;
let _: String = WebI18n.file_name;
let _: String = WebI18n.size;
}
match None::<crate::api::webrtc::WsServerMessage>.unwrap() {
crate::api::webrtc::WsServerMessage::Hello { client, peers } => {
@@ -4234,6 +4235,17 @@ impl SseDecode for Option<u32> {
}
}
impl SseDecode for Option<crate::api::server::WebParams> {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
if (<bool>::sse_decode(deserializer)) {
return Some(<crate::api::server::WebParams>::sse_decode(deserializer));
} else {
return None;
}
}
}
impl SseDecode for Option<crate::api::server::WebSendParams> {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
@@ -4812,7 +4824,7 @@ impl SseDecode for usize {
}
}
impl SseDecode for crate::api::server::WebSendI18n {
impl SseDecode for crate::api::server::WebI18n {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
let mut var_waiting = <String>::sse_decode(deserializer);
@@ -4820,15 +4832,19 @@ impl SseDecode for crate::api::server::WebSendI18n {
let mut var_invalidPin = <String>::sse_decode(deserializer);
let mut var_tooManyAttempts = <String>::sse_decode(deserializer);
let mut var_rejected = <String>::sse_decode(deserializer);
let mut var_uploadRejected = <String>::sse_decode(deserializer);
let mut var_busy = <String>::sse_decode(deserializer);
let mut var_files = <String>::sse_decode(deserializer);
let mut var_fileName = <String>::sse_decode(deserializer);
let mut var_size = <String>::sse_decode(deserializer);
return crate::api::server::WebSendI18n {
return crate::api::server::WebI18n {
waiting: var_waiting,
enter_pin: var_enterPin,
invalid_pin: var_invalidPin,
too_many_attempts: var_tooManyAttempts,
rejected: var_rejected,
upload_rejected: var_uploadRejected,
busy: var_busy,
files: var_files,
file_name: var_fileName,
size: var_size,
@@ -4836,6 +4852,20 @@ impl SseDecode for crate::api::server::WebSendI18n {
}
}
impl SseDecode for crate::api::server::WebParams {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
let mut var_send = <Option<crate::api::server::WebSendParams>>::sse_decode(deserializer);
let mut var_upload = <bool>::sse_decode(deserializer);
let mut var_i18N = <crate::api::server::WebI18n>::sse_decode(deserializer);
return crate::api::server::WebParams {
send: var_send,
upload: var_upload,
i18n: var_i18N,
};
}
}
impl SseDecode for crate::api::server::WebSendParams {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
@@ -4844,11 +4874,9 @@ impl SseDecode for crate::api::server::WebSendParams {
deserializer,
);
let mut var_pin = <Option<String>>::sse_decode(deserializer);
let mut var_i18N = <crate::api::server::WebSendI18n>::sse_decode(deserializer);
return crate::api::server::WebSendParams {
files: var_files,
pin: var_pin,
i18n: var_i18N,
};
}
}
@@ -6139,7 +6167,7 @@ impl flutter_rust_bridge::IntoIntoDart<FrbWrapper<crate::api::server::TlsConfig>
}
}
// Codec=Dco (DartCObject based), see doc to use other codecs
impl flutter_rust_bridge::IntoDart for FrbWrapper<crate::api::server::WebSendI18n> {
impl flutter_rust_bridge::IntoDart for FrbWrapper<crate::api::server::WebI18n> {
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
[
self.0.waiting.into_into_dart().into_dart(),
@@ -6147,6 +6175,8 @@ impl flutter_rust_bridge::IntoDart for FrbWrapper<crate::api::server::WebSendI18
self.0.invalid_pin.into_into_dart().into_dart(),
self.0.too_many_attempts.into_into_dart().into_dart(),
self.0.rejected.into_into_dart().into_dart(),
self.0.upload_rejected.into_into_dart().into_dart(),
self.0.busy.into_into_dart().into_dart(),
self.0.files.into_into_dart().into_dart(),
self.0.file_name.into_into_dart().into_dart(),
self.0.size.into_into_dart().into_dart(),
@@ -6155,23 +6185,41 @@ impl flutter_rust_bridge::IntoDart for FrbWrapper<crate::api::server::WebSendI18
}
}
impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive
for FrbWrapper<crate::api::server::WebSendI18n>
for FrbWrapper<crate::api::server::WebI18n>
{
}
impl flutter_rust_bridge::IntoIntoDart<FrbWrapper<crate::api::server::WebSendI18n>>
for crate::api::server::WebSendI18n
impl flutter_rust_bridge::IntoIntoDart<FrbWrapper<crate::api::server::WebI18n>>
for crate::api::server::WebI18n
{
fn into_into_dart(self) -> FrbWrapper<crate::api::server::WebSendI18n> {
fn into_into_dart(self) -> FrbWrapper<crate::api::server::WebI18n> {
self.into()
}
}
// Codec=Dco (DartCObject based), see doc to use other codecs
impl flutter_rust_bridge::IntoDart for crate::api::server::WebParams {
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
[
self.send.into_into_dart().into_dart(),
self.upload.into_into_dart().into_dart(),
self.i18n.into_into_dart().into_dart(),
]
.into_dart()
}
}
impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive for crate::api::server::WebParams {}
impl flutter_rust_bridge::IntoIntoDart<crate::api::server::WebParams>
for crate::api::server::WebParams
{
fn into_into_dart(self) -> crate::api::server::WebParams {
self
}
}
// Codec=Dco (DartCObject based), see doc to use other codecs
impl flutter_rust_bridge::IntoDart for crate::api::server::WebSendParams {
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
[
self.files.into_into_dart().into_dart(),
self.pin.into_into_dart().into_dart(),
self.i18n.into_into_dart().into_dart(),
]
.into_dart()
}
@@ -6914,6 +6962,16 @@ impl SseEncode for Option<u32> {
}
}
impl SseEncode for Option<crate::api::server::WebParams> {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
<bool>::sse_encode(self.is_some(), serializer);
if let Some(value) = self {
<crate::api::server::WebParams>::sse_encode(value, serializer);
}
}
}
impl SseEncode for Option<crate::api::server::WebSendParams> {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
@@ -7377,7 +7435,7 @@ impl SseEncode for usize {
}
}
impl SseEncode for crate::api::server::WebSendI18n {
impl SseEncode for crate::api::server::WebI18n {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
<String>::sse_encode(self.waiting, serializer);
@@ -7385,12 +7443,23 @@ impl SseEncode for crate::api::server::WebSendI18n {
<String>::sse_encode(self.invalid_pin, serializer);
<String>::sse_encode(self.too_many_attempts, serializer);
<String>::sse_encode(self.rejected, serializer);
<String>::sse_encode(self.upload_rejected, serializer);
<String>::sse_encode(self.busy, serializer);
<String>::sse_encode(self.files, serializer);
<String>::sse_encode(self.file_name, serializer);
<String>::sse_encode(self.size, serializer);
}
}
impl SseEncode for crate::api::server::WebParams {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
<Option<crate::api::server::WebSendParams>>::sse_encode(self.send, serializer);
<bool>::sse_encode(self.upload, serializer);
<crate::api::server::WebI18n>::sse_encode(self.i18n, serializer);
}
}
impl SseEncode for crate::api::server::WebSendParams {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
@@ -7398,7 +7467,6 @@ impl SseEncode for crate::api::server::WebSendParams {
self.files, serializer,
);
<Option<String>>::sse_encode(self.pin, serializer);
<crate::api::server::WebSendI18n>::sse_encode(self.i18n, serializer);
}
}