mirror of
https://github.com/localsend/localsend.git
synced 2026-08-07 07:14:52 +00:00
feat: PIN on web receive
This commit is contained in:
@@ -9,17 +9,15 @@ class WebSendState with WebSendStateMappable {
|
|||||||
final Map<String, WebSendSession> sessions; // session id -> session data, also includes incoming requests
|
final Map<String, WebSendSession> sessions; // session id -> session data, also includes incoming requests
|
||||||
final Map<String, WebSendFile> files; // file id as key
|
final Map<String, WebSendFile> files; // file id as key
|
||||||
final bool autoAccept; // automatically accept incoming requests
|
final bool autoAccept; // automatically accept incoming requests
|
||||||
final String? pin; // the PIN is enforced by the Rust server; changing it requires a server restart
|
|
||||||
|
|
||||||
const WebSendState({
|
const WebSendState({
|
||||||
required this.sessions,
|
required this.sessions,
|
||||||
required this.files,
|
required this.files,
|
||||||
required this.autoAccept,
|
required this.autoAccept,
|
||||||
required this.pin,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'WebSendState(sessions: $sessions, files: <${files.keys}>, autoAccept: $autoAccept, pin: $pin)';
|
return 'WebSendState(sessions: $sessions, files: <${files.keys}>, autoAccept: $autoAccept)';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,15 +37,12 @@ class WebSendStateMapper extends ClassMapperBase<WebSendState> {
|
|||||||
'autoAccept',
|
'autoAccept',
|
||||||
_$autoAccept,
|
_$autoAccept,
|
||||||
);
|
);
|
||||||
static String? _$pin(WebSendState v) => v.pin;
|
|
||||||
static const Field<WebSendState, String> _f$pin = Field('pin', _$pin);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final MappableFields<WebSendState> fields = const {
|
final MappableFields<WebSendState> fields = const {
|
||||||
#sessions: _f$sessions,
|
#sessions: _f$sessions,
|
||||||
#files: _f$files,
|
#files: _f$files,
|
||||||
#autoAccept: _f$autoAccept,
|
#autoAccept: _f$autoAccept,
|
||||||
#pin: _f$pin,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static WebSendState _instantiate(DecodingData data) {
|
static WebSendState _instantiate(DecodingData data) {
|
||||||
@@ -53,7 +50,6 @@ class WebSendStateMapper extends ClassMapperBase<WebSendState> {
|
|||||||
sessions: data.dec(_f$sessions),
|
sessions: data.dec(_f$sessions),
|
||||||
files: data.dec(_f$files),
|
files: data.dec(_f$files),
|
||||||
autoAccept: data.dec(_f$autoAccept),
|
autoAccept: data.dec(_f$autoAccept),
|
||||||
pin: data.dec(_f$pin),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,7 +133,6 @@ abstract class WebSendStateCopyWith<$R, $In extends WebSendState, $Out>
|
|||||||
Map<String, WebSendSession>? sessions,
|
Map<String, WebSendSession>? sessions,
|
||||||
Map<String, WebSendFile>? files,
|
Map<String, WebSendFile>? files,
|
||||||
bool? autoAccept,
|
bool? autoAccept,
|
||||||
String? pin,
|
|
||||||
});
|
});
|
||||||
WebSendStateCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t);
|
WebSendStateCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t);
|
||||||
}
|
}
|
||||||
@@ -179,13 +174,11 @@ class _WebSendStateCopyWithImpl<$R, $Out>
|
|||||||
Map<String, WebSendSession>? sessions,
|
Map<String, WebSendSession>? sessions,
|
||||||
Map<String, WebSendFile>? files,
|
Map<String, WebSendFile>? files,
|
||||||
bool? autoAccept,
|
bool? autoAccept,
|
||||||
Object? pin = $none,
|
|
||||||
}) => $apply(
|
}) => $apply(
|
||||||
FieldCopyWithData({
|
FieldCopyWithData({
|
||||||
if (sessions != null) #sessions: sessions,
|
if (sessions != null) #sessions: sessions,
|
||||||
if (files != null) #files: files,
|
if (files != null) #files: files,
|
||||||
if (autoAccept != null) #autoAccept: autoAccept,
|
if (autoAccept != null) #autoAccept: autoAccept,
|
||||||
if (pin != $none) #pin: pin,
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@override
|
@override
|
||||||
@@ -193,7 +186,6 @@ class _WebSendStateCopyWithImpl<$R, $Out>
|
|||||||
sessions: data.get(#sessions, or: $value.sessions),
|
sessions: data.get(#sessions, or: $value.sessions),
|
||||||
files: data.get(#files, or: $value.files),
|
files: data.get(#files, or: $value.files),
|
||||||
autoAccept: data.get(#autoAccept, or: $value.autoAccept),
|
autoAccept: data.get(#autoAccept, or: $value.autoAccept),
|
||||||
pin: data.get(#pin, or: $value.pin),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -15,6 +15,10 @@ class ServerState with ServerStateMappable {
|
|||||||
/// Whether the upload page is served so web browsers can upload files.
|
/// Whether the upload page is served so web browsers can upload files.
|
||||||
final bool webUpload;
|
final bool webUpload;
|
||||||
|
|
||||||
|
/// Optional session-scoped PIN of the active web share mode (download or upload page).
|
||||||
|
/// The PIN is enforced by the Rust server; changing it requires a server restart.
|
||||||
|
final String? webPin;
|
||||||
|
|
||||||
const ServerState({
|
const ServerState({
|
||||||
required this.alias,
|
required this.alias,
|
||||||
required this.port,
|
required this.port,
|
||||||
@@ -22,10 +26,11 @@ class ServerState with ServerStateMappable {
|
|||||||
required this.session,
|
required this.session,
|
||||||
required this.webSendState,
|
required this.webSendState,
|
||||||
required this.webUpload,
|
required this.webUpload,
|
||||||
|
required this.webPin,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'ServerState(alias: $alias, port: $port, https: $https, session: $session, webSendState: $webSendState, webUpload: $webUpload)';
|
return 'ServerState(alias: $alias, port: $port, https: $https, session: $session, webSendState: $webSendState, webUpload: $webUpload, webPin: $webPin)';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ class ServerStateMapper extends ClassMapperBase<ServerState> {
|
|||||||
'webUpload',
|
'webUpload',
|
||||||
_$webUpload,
|
_$webUpload,
|
||||||
);
|
);
|
||||||
|
static String? _$webPin(ServerState v) => v.webPin;
|
||||||
|
static const Field<ServerState, String> _f$webPin = Field('webPin', _$webPin);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final MappableFields<ServerState> fields = const {
|
final MappableFields<ServerState> fields = const {
|
||||||
@@ -54,6 +56,7 @@ class ServerStateMapper extends ClassMapperBase<ServerState> {
|
|||||||
#session: _f$session,
|
#session: _f$session,
|
||||||
#webSendState: _f$webSendState,
|
#webSendState: _f$webSendState,
|
||||||
#webUpload: _f$webUpload,
|
#webUpload: _f$webUpload,
|
||||||
|
#webPin: _f$webPin,
|
||||||
};
|
};
|
||||||
|
|
||||||
static ServerState _instantiate(DecodingData data) {
|
static ServerState _instantiate(DecodingData data) {
|
||||||
@@ -64,6 +67,7 @@ class ServerStateMapper extends ClassMapperBase<ServerState> {
|
|||||||
session: data.dec(_f$session),
|
session: data.dec(_f$session),
|
||||||
webSendState: data.dec(_f$webSendState),
|
webSendState: data.dec(_f$webSendState),
|
||||||
webUpload: data.dec(_f$webUpload),
|
webUpload: data.dec(_f$webUpload),
|
||||||
|
webPin: data.dec(_f$webPin),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,6 +141,7 @@ abstract class ServerStateCopyWith<$R, $In extends ServerState, $Out>
|
|||||||
ReceiveSessionState? session,
|
ReceiveSessionState? session,
|
||||||
WebSendState? webSendState,
|
WebSendState? webSendState,
|
||||||
bool? webUpload,
|
bool? webUpload,
|
||||||
|
String? webPin,
|
||||||
});
|
});
|
||||||
ServerStateCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t);
|
ServerStateCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t);
|
||||||
}
|
}
|
||||||
@@ -163,6 +168,7 @@ class _ServerStateCopyWithImpl<$R, $Out>
|
|||||||
Object? session = $none,
|
Object? session = $none,
|
||||||
Object? webSendState = $none,
|
Object? webSendState = $none,
|
||||||
bool? webUpload,
|
bool? webUpload,
|
||||||
|
Object? webPin = $none,
|
||||||
}) => $apply(
|
}) => $apply(
|
||||||
FieldCopyWithData({
|
FieldCopyWithData({
|
||||||
if (alias != null) #alias: alias,
|
if (alias != null) #alias: alias,
|
||||||
@@ -171,6 +177,7 @@ class _ServerStateCopyWithImpl<$R, $Out>
|
|||||||
if (session != $none) #session: session,
|
if (session != $none) #session: session,
|
||||||
if (webSendState != $none) #webSendState: webSendState,
|
if (webSendState != $none) #webSendState: webSendState,
|
||||||
if (webUpload != null) #webUpload: webUpload,
|
if (webUpload != null) #webUpload: webUpload,
|
||||||
|
if (webPin != $none) #webPin: webPin,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@override
|
@override
|
||||||
@@ -181,6 +188,7 @@ class _ServerStateCopyWithImpl<$R, $Out>
|
|||||||
session: data.get(#session, or: $value.session),
|
session: data.get(#session, or: $value.session),
|
||||||
webSendState: data.get(#webSendState, or: $value.webSendState),
|
webSendState: data.get(#webSendState, or: $value.webSendState),
|
||||||
webUpload: data.get(#webUpload, or: $value.webUpload),
|
webUpload: data.get(#webUpload, or: $value.webUpload),
|
||||||
|
webPin: data.get(#webPin, or: $value.webPin),
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -62,8 +62,15 @@ class _WebSharePageState extends State<WebSharePage> with Refena {
|
|||||||
await sleepAsync(500);
|
await sleepAsync(500);
|
||||||
try {
|
try {
|
||||||
final files = widget.files;
|
final files = widget.files;
|
||||||
|
|
||||||
|
// The pin of a previous web share session is kept;
|
||||||
|
// receive mode initially uses the receive pin from settings.
|
||||||
|
final previousState = ref.read(serverProvider);
|
||||||
|
final wasWebActive = previousState?.webSendState != null || previousState?.webUpload == true;
|
||||||
|
final webPin = wasWebActive ? previousState?.webPin : (files == null ? settings.receivePin : null);
|
||||||
|
|
||||||
if (files != null) {
|
if (files != null) {
|
||||||
// The auto accept setting and the pin of a previous web send state are kept.
|
// The auto accept setting of a previous web send state is kept.
|
||||||
await ref
|
await ref
|
||||||
.notifier(serverProvider)
|
.notifier(serverProvider)
|
||||||
.restartServerWithWebSend(
|
.restartServerWithWebSend(
|
||||||
@@ -71,6 +78,7 @@ class _WebSharePageState extends State<WebSharePage> with Refena {
|
|||||||
port: settings.port,
|
port: settings.port,
|
||||||
https: _encrypted,
|
https: _encrypted,
|
||||||
files: files,
|
files: files,
|
||||||
|
webPin: webPin,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
await ref
|
await ref
|
||||||
@@ -80,6 +88,7 @@ class _WebSharePageState extends State<WebSharePage> with Refena {
|
|||||||
port: settings.port,
|
port: settings.port,
|
||||||
https: _encrypted,
|
https: _encrypted,
|
||||||
webUpload: true,
|
webUpload: true,
|
||||||
|
webPin: webPin,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -169,6 +178,7 @@ class _WebSharePageState extends State<WebSharePage> with Refena {
|
|||||||
}
|
}
|
||||||
final networkState = context.watch(localIpProvider);
|
final networkState = context.watch(localIpProvider);
|
||||||
final settings = context.watch(settingsProvider);
|
final settings = context.watch(settingsProvider);
|
||||||
|
final pin = serverState.webPin;
|
||||||
|
|
||||||
return ResponsiveListView(
|
return ResponsiveListView(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 20),
|
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 20),
|
||||||
@@ -184,8 +194,8 @@ class _WebSharePageState extends State<WebSharePage> with Refena {
|
|||||||
children: [
|
children: [
|
||||||
...networkState.localIps.map((ip) {
|
...networkState.localIps.map((ip) {
|
||||||
final url = '${_encrypted ? 'https' : 'http'}://$ip:${serverState.port}';
|
final url = '${_encrypted ? 'https' : 'http'}://$ip:${serverState.port}';
|
||||||
final urlWithPin = switch (webSendState?.pin) {
|
final urlWithPin = switch (pin) {
|
||||||
String() => '$url/?pin=${Uri.encodeQueryComponent(webSendState!.pin!)}',
|
String() => '$url/?pin=${Uri.encodeQueryComponent(pin)}',
|
||||||
null => url,
|
null => url,
|
||||||
};
|
};
|
||||||
return Padding(
|
return Padding(
|
||||||
@@ -217,7 +227,7 @@ class _WebSharePageState extends State<WebSharePage> with Refena {
|
|||||||
data: urlWithPin,
|
data: urlWithPin,
|
||||||
label: url,
|
label: url,
|
||||||
listenIncomingWebSendRequests: _sendMode,
|
listenIncomingWebSendRequests: _sendMode,
|
||||||
pin: webSendState?.pin,
|
pin: pin,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -233,7 +243,7 @@ class _WebSharePageState extends State<WebSharePage> with Refena {
|
|||||||
builder: (_) => ZoomDialog(
|
builder: (_) => ZoomDialog(
|
||||||
label: url,
|
label: url,
|
||||||
listenIncomingWebSendRequests: _sendMode,
|
listenIncomingWebSendRequests: _sendMode,
|
||||||
pin: webSendState?.pin,
|
pin: pin,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -354,18 +364,16 @@ class _WebSharePageState extends State<WebSharePage> with Refena {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (webSendState != null) ...[
|
|
||||||
Row(
|
Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Text(t.webSharePage.requirePin, style: Theme.of(context).textTheme.titleMedium),
|
Text(t.webSharePage.requirePin, style: Theme.of(context).textTheme.titleMedium),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
Checkbox(
|
Checkbox(
|
||||||
value: webSendState.pin != null,
|
value: pin != null,
|
||||||
onChanged: (value) async {
|
onChanged: (value) async {
|
||||||
final currentPIN = webSendState.pin;
|
if (pin != null) {
|
||||||
if (currentPIN != null) {
|
await ref.notifier(serverProvider).setWebPin(null);
|
||||||
await ref.notifier(serverProvider).setWebSendPin(null);
|
|
||||||
} else {
|
} else {
|
||||||
final String? newPin = await showDialog<String>(
|
final String? newPin = await showDialog<String>(
|
||||||
context: context,
|
context: context,
|
||||||
@@ -376,20 +384,19 @@ class _WebSharePageState extends State<WebSharePage> with Refena {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (newPin != null && newPin.isNotEmpty) {
|
if (newPin != null && newPin.isNotEmpty) {
|
||||||
await ref.notifier(serverProvider).setWebSendPin(newPin);
|
await ref.notifier(serverProvider).setWebPin(newPin);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (webSendState.pin != null)
|
if (pin != null)
|
||||||
Text(
|
Text(
|
||||||
t.webSharePage.pinHint(pin: webSendState.pin!),
|
t.webSharePage.pinHint(pin: pin),
|
||||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(color: Theme.of(context).colorScheme.warning),
|
style: Theme.of(context).textTheme.bodyMedium!.copyWith(color: Theme.of(context).colorScheme.warning),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
],
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -79,7 +79,6 @@ class SendController {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
autoAccept: currentWebSendState?.autoAccept ?? server.ref.read(settingsProvider).shareViaLinkAutoAccept,
|
autoAccept: currentWebSendState?.autoAccept ?? server.ref.read(settingsProvider).shareViaLinkAutoAccept,
|
||||||
pin: currentWebSendState?.pin,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -109,12 +109,15 @@ class ServerService extends Notifier<ServerState?> {
|
|||||||
/// Starts the server.
|
/// Starts the server.
|
||||||
/// Passing a [webSendState] additionally serves the web send (download) API.
|
/// Passing a [webSendState] additionally serves the web send (download) API.
|
||||||
/// Passing [webUpload] serves the upload page so web browsers can upload files.
|
/// Passing [webUpload] serves the upload page so web browsers can upload files.
|
||||||
|
/// [webPin] protects the active web share mode: the download page in send mode;
|
||||||
|
/// in upload mode, it replaces the receive pin from settings.
|
||||||
Future<ServerState?> startServer({
|
Future<ServerState?> startServer({
|
||||||
required String alias,
|
required String alias,
|
||||||
required int port,
|
required int port,
|
||||||
required bool https,
|
required bool https,
|
||||||
WebSendState? webSendState,
|
WebSendState? webSendState,
|
||||||
bool webUpload = false,
|
bool webUpload = false,
|
||||||
|
String? webPin,
|
||||||
}) async {
|
}) async {
|
||||||
if (state != null) {
|
if (state != null) {
|
||||||
_logger.info('Server already running.');
|
_logger.info('Server already running.');
|
||||||
@@ -141,7 +144,7 @@ class ServerService extends Notifier<ServerState?> {
|
|||||||
.redux(parentIsolateProvider)
|
.redux(parentIsolateProvider)
|
||||||
.dispatchTakeResult(
|
.dispatchTakeResult(
|
||||||
IsolateHttpServerStartAction(
|
IsolateHttpServerStartAction(
|
||||||
pin: settings.receivePin,
|
pin: webUpload ? webPin : settings.receivePin,
|
||||||
verifyChecksums: settings.verifyChecksums,
|
verifyChecksums: settings.verifyChecksums,
|
||||||
web: webSendState != null || webUpload
|
web: webSendState != null || webUpload
|
||||||
? WebParams(
|
? WebParams(
|
||||||
@@ -150,7 +153,7 @@ class ServerService extends Notifier<ServerState?> {
|
|||||||
files: {
|
files: {
|
||||||
for (final entry in webSendState.files.entries) entry.key: entry.value.file.toRust(),
|
for (final entry in webSendState.files.entries) entry.key: entry.value.file.toRust(),
|
||||||
},
|
},
|
||||||
pin: webSendState.pin,
|
pin: webPin,
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
upload: webUpload,
|
upload: webUpload,
|
||||||
@@ -210,6 +213,7 @@ class ServerService extends Notifier<ServerState?> {
|
|||||||
session: null,
|
session: null,
|
||||||
webSendState: webSendState,
|
webSendState: webSendState,
|
||||||
webUpload: webUpload,
|
webUpload: webUpload,
|
||||||
|
webPin: webSendState != null || webUpload ? webPin : null,
|
||||||
);
|
);
|
||||||
|
|
||||||
state = newServerState;
|
state = newServerState;
|
||||||
@@ -237,9 +241,10 @@ class ServerService extends Notifier<ServerState?> {
|
|||||||
required bool https,
|
required bool https,
|
||||||
WebSendState? webSendState,
|
WebSendState? webSendState,
|
||||||
bool webUpload = false,
|
bool webUpload = false,
|
||||||
|
String? webPin,
|
||||||
}) async {
|
}) async {
|
||||||
await stopServer();
|
await stopServer();
|
||||||
return await startServer(alias: alias, port: port, https: https, webSendState: webSendState, webUpload: webUpload);
|
return await startServer(alias: alias, port: port, https: https, webSendState: webSendState, webUpload: webUpload, webPin: webPin);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> acceptFileRequest(Map<String, String> fileNameMap) async {
|
Future<void> acceptFileRequest(Map<String, String> fileNameMap) async {
|
||||||
@@ -271,23 +276,23 @@ class ServerService extends Notifier<ServerState?> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Restarts the server with web send (the download API) enabled for [files].
|
/// Restarts the server with web send (the download API) enabled for [files].
|
||||||
/// The auto accept setting and the pin of a previous web send state are kept.
|
/// The auto accept setting of a previous web send state is kept.
|
||||||
Future<void> restartServerWithWebSend({
|
Future<void> restartServerWithWebSend({
|
||||||
required String alias,
|
required String alias,
|
||||||
required int port,
|
required int port,
|
||||||
required bool https,
|
required bool https,
|
||||||
required List<CrossFile> files,
|
required List<CrossFile> files,
|
||||||
|
String? webPin,
|
||||||
}) async {
|
}) async {
|
||||||
final webSendState = await _sendController.buildWebSendState(files: files);
|
final webSendState = await _sendController.buildWebSendState(files: files);
|
||||||
await restartServer(alias: alias, port: port, https: https, webSendState: webSendState);
|
await restartServer(alias: alias, port: port, https: https, webSendState: webSendState, webPin: webPin);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Updates the web send pin.
|
/// Updates the pin of the active web share mode (download or upload page).
|
||||||
/// The pin is enforced by the Rust server, so the server is restarted.
|
/// The pin is enforced by the Rust server, so the server is restarted.
|
||||||
Future<void> setWebSendPin(String? pin) async {
|
Future<void> setWebPin(String? pin) async {
|
||||||
final current = state;
|
final current = state;
|
||||||
final webSendState = current?.webSendState;
|
if (current == null || (current.webSendState == null && !current.webUpload) || current.webPin == pin) {
|
||||||
if (current == null || webSendState == null || webSendState.pin == pin) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,7 +300,9 @@ class ServerService extends Notifier<ServerState?> {
|
|||||||
alias: current.alias,
|
alias: current.alias,
|
||||||
port: current.port,
|
port: current.port,
|
||||||
https: current.https,
|
https: current.https,
|
||||||
webSendState: webSendState.copyWith(sessions: {}, pin: pin),
|
webSendState: current.webSendState?.copyWith(sessions: {}),
|
||||||
|
webUpload: current.webUpload,
|
||||||
|
webPin: pin,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -68,6 +68,17 @@
|
|||||||
var BASE_URL = '/api/localsend/v2';
|
var BASE_URL = '/api/localsend/v2';
|
||||||
|
|
||||||
var i18n = {};
|
var i18n = {};
|
||||||
|
var queryParams = location.search.slice(1).split('&');
|
||||||
|
var pin = null;
|
||||||
|
|
||||||
|
// Parse query parameters manually for IE
|
||||||
|
for (var i = 0; i < queryParams.length; i++) {
|
||||||
|
var pair = queryParams[i].split('=');
|
||||||
|
if (pair[0] === 'pin') {
|
||||||
|
pin = decodeURIComponent(pair[1]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function makeRequest(url, method, body, callback) {
|
function makeRequest(url, method, body, callback) {
|
||||||
var xhr = new XMLHttpRequest();
|
var xhr = new XMLHttpRequest();
|
||||||
@@ -154,7 +165,32 @@
|
|||||||
files: filesDto
|
files: filesDto
|
||||||
});
|
});
|
||||||
|
|
||||||
makeRequest(BASE_URL + '/prepare-upload', 'POST', body, function (response) {
|
prepareUpload(body, selectedFiles, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function prepareUpload(body, selectedFiles, firstAttempt) {
|
||||||
|
var url = BASE_URL + '/prepare-upload';
|
||||||
|
if (pin) {
|
||||||
|
url += '?pin=' + encodeURIComponent(pin);
|
||||||
|
}
|
||||||
|
|
||||||
|
makeRequest(url, 'POST', body, function (response) {
|
||||||
|
if (response.status === 401) {
|
||||||
|
var newPin = prompt(i18n.enterPin + (firstAttempt ? '' : '\n' + i18n.invalidPin));
|
||||||
|
if (!newPin) {
|
||||||
|
finishWithError(i18n.invalidPin);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pin = newPin;
|
||||||
|
prepareUpload(body, selectedFiles, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.status === 429) {
|
||||||
|
finishWithError(i18n.tooManyAttempts);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (response.status === 403) {
|
if (response.status === 403) {
|
||||||
finishWithError(i18n.uploadRejected);
|
finishWithError(i18n.uploadRejected);
|
||||||
return;
|
return;
|
||||||
|
|||||||
Reference in New Issue
Block a user