Skip to content

Commit a10ee7f

Browse files
committed
[web_app] Minor clean up to API and HTTP related files
1 parent 591b8b2 commit a10ee7f

File tree

4 files changed

+57
-87
lines changed

4 files changed

+57
-87
lines changed

pkg/web_app/lib/src/api_client/_rpc.dart

Lines changed: 32 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import 'dart:async';
66
import 'dart:html';
7+
78
import 'package:_pub_shared/pubapi.dart';
89

910
import '../_dom_helper.dart';
@@ -17,7 +18,7 @@ Future<R?> rpc<R>({
1718

1819
/// The async RPC call. If this throws, the error will be displayed as a modal
1920
/// popup, and then it will be re-thrown (or `onError` will be called).
20-
Future<R?> Function()? fn,
21+
required Future<R?> Function() fn,
2122

2223
/// Message to show when the RPC returns without exceptions.
2324
required Element? successMessage,
@@ -37,12 +38,12 @@ Future<R?> rpc<R>({
3738
return null;
3839
}
3940

40-
// capture keys
41+
// Capture key down events.
4142
final keyDownSubscription = window.onKeyDown.listen((event) {
4243
event.preventDefault();
4344
event.stopPropagation();
4445
});
45-
// disable inputs and buttons that are not already disabled
46+
// Disable inputs and buttons that are not already disabled.
4647
final inputs = document
4748
.querySelectorAll('input')
4849
.cast<InputElement>()
@@ -67,22 +68,21 @@ Future<R?> rpc<R>({
6768
}
6869

6970
R? result;
70-
Exception? error;
71-
String? errorMessage;
71+
({Exception exception, String message})? error;
7272
try {
73-
result = await fn!();
73+
result = await fn();
7474
} on RequestException catch (e) {
7575
final asJson = e.bodyAsJson();
7676
if (e.status == 401 && asJson.containsKey('go')) {
77-
final location = e.bodyAsJson()['go'] as String;
77+
final location = asJson['go'] as String;
7878
final locationUri = Uri.tryParse(location);
7979
if (locationUri != null && locationUri.toString().isNotEmpty) {
8080
await cancelSpinner();
81-
final errorObject = e.bodyAsJson()['error'] as Map?;
81+
final errorObject = asJson['error'] as Map?;
8282
final message = errorObject?['message'];
8383
await modalMessage(
8484
'Further consent needed.',
85-
Element.p()
85+
ParagraphElement()
8686
..text = [
8787
if (message != null) message,
8888
'You will be redirected, please authorize the action.',
@@ -101,21 +101,25 @@ Future<R?> rpc<R>({
101101
return null;
102102
}
103103
}
104-
error = e;
105-
errorMessage = _requestExceptionMessage(e) ?? 'Unexpected error: $e';
104+
error = (
105+
exception: e,
106+
message: _requestExceptionMessage(asJson) ?? 'Unexpected error: $e'
107+
);
106108
} catch (e) {
107-
error = Exception('Unexpected error: $e');
108-
errorMessage = 'Unexpected error: $e';
109+
error = (
110+
exception: Exception('Unexpected error: $e'),
111+
message: 'Unexpected error: $e'
112+
);
109113
} finally {
110114
await cancelSpinner();
111115
}
112116

113117
if (error != null) {
114-
await modalMessage('Error', await markdown(errorMessage!));
118+
await modalMessage('Error', await markdown(error.message));
115119
if (onError != null) {
116-
return await onError(error);
120+
return await onError(error.exception);
117121
} else {
118-
throw error;
122+
throw error.exception;
119123
}
120124
}
121125

@@ -128,37 +132,18 @@ Future<R?> rpc<R>({
128132
return result;
129133
}
130134

131-
String? _requestExceptionMessage(RequestException e) {
132-
try {
133-
final map = e.bodyAsJson();
134-
String? message;
135-
136-
if (map['error'] is Map) {
137-
final errorMap = map['error'] as Map;
138-
if (errorMap['message'] is String) {
139-
message = errorMap['message'] as String;
140-
}
141-
}
142-
143-
// TODO: remove after the server is migrated to returns only `{'error': {'message': 'XX'}}`
144-
if (message == null && map['message'] is String) {
145-
message = map['message'] as String;
146-
}
147-
148-
// TODO: check if we ever send responses like this and remove if not
149-
if (message == null && map['error'] is String) {
150-
message = map['error'] as String;
151-
}
152-
153-
return message;
154-
} on FormatException catch (_) {
155-
// ignore bad body
156-
}
157-
return null;
158-
}
159-
160-
Element _createSpinner() => Element.div()
135+
String? _requestExceptionMessage(Map<String, Object?> jsonBody) =>
136+
switch (jsonBody) {
137+
{'error': {'message': final String errorMessage}} => errorMessage,
138+
// TODO: Remove after the server is migrated to return only `{'error': {'message': 'XX'}}`.
139+
{'message': final String errorMessage} => errorMessage,
140+
// TODO: Check if we ever send responses like this and remove if not.
141+
{'error': final String errorMessage} => errorMessage,
142+
_ => null,
143+
};
144+
145+
Element _createSpinner() => DivElement()
161146
..className = 'spinner-frame'
162147
..children = [
163-
Element.div()..className = 'spinner',
148+
DivElement()..className = 'spinner',
164149
];

pkg/web_app/lib/src/api_client/api_client.dart

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// BSD-style license that can be found in the LICENSE file.
44

55
import 'dart:html';
6+
67
import 'package:_pub_shared/pubapi.dart';
78
import 'package:api_builder/_client_utils.dart';
89

@@ -24,18 +25,18 @@ PubApiClient get unauthenticatedClient =>
2425
PubApiClient(_baseUrl, client: http.Client());
2526

2627
/// The pub API client to use with account credentials.
27-
PubApiClient get client {
28-
return PubApiClient(_baseUrl, client: http.createClientWithCsrf());
29-
}
28+
PubApiClient get client =>
29+
PubApiClient(_baseUrl, client: http.createClientWithCsrf());
3030

31-
/// Sends a JSON request to the [path] endpoint using [verb] method with [body] content.
31+
/// Sends a JSON request to the [path] endpoint using
32+
/// [verb] method with [body] content.
3233
///
3334
/// Sets the `Content-Type` header to `application/json; charset="utf-8` and
3435
/// expects a valid JSON response body.
35-
Future<Map<String, dynamic>> sendJson({
36+
Future<Map<String, Object?>> sendJson({
3637
required String verb,
3738
required String path,
38-
required Map<String, dynamic>? body,
39+
required Map<String, Object?>? body,
3940
}) async {
4041
final client = http.createClientWithCsrf();
4142
try {

pkg/web_app/lib/src/deferred/http.dart

Lines changed: 17 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,60 +4,42 @@
44

55
import 'dart:html';
66

7+
import 'package:collection/collection.dart' show IterableExtension;
78
import 'package:http/browser_client.dart';
89
import 'package:http/http.dart';
910

1011
export 'package:http/http.dart';
1112

1213
/// Creates an authenticated [Client] that extends request with the CSRF header.
13-
Client createClientWithCsrf() {
14-
return _AuthenticatedClient();
15-
}
14+
Client createClientWithCsrf() => _AuthenticatedClient();
1615

17-
String? _getCsrfMetaContent() {
18-
final values = document.head
19-
?.querySelectorAll('meta[name="csrf-token"]')
20-
.map((e) => e.attributes['content'])
21-
.nonNulls
22-
.toList();
23-
if (values == null || values.isEmpty) {
24-
return null;
25-
}
26-
return values.first.trim();
27-
}
16+
String? get _csrfMetaContent => document.head
17+
?.querySelectorAll('meta[name="csrf-token"]')
18+
.map((e) => e.getAttribute('content'))
19+
.firstWhereOrNull((tokenContent) => tokenContent != null)
20+
?.trim();
2821

2922
/// An HTTP [Client] which sends custom headers alongside each request:
3023
///
3124
/// - `x-pub-csrf-token` header when the HTML document's `<head>` contains the
3225
/// `<meta name="csrf-token" content="<token>">` element.
3326
class _AuthenticatedClient extends _BrowserClient {
34-
_AuthenticatedClient()
35-
: super(
36-
getHeadersFn: () async {
37-
final csrfToken = _getCsrfMetaContent();
38-
return {
39-
if (csrfToken != null && csrfToken.isNotEmpty)
40-
'x-pub-csrf-token': csrfToken,
41-
};
42-
},
43-
);
27+
@override
28+
Future<Map<String, String>> get _sendHeaders async => {
29+
if (_csrfMetaContent case final csrfToken? when csrfToken.isNotEmpty)
30+
'x-pub-csrf-token': csrfToken,
31+
};
4432
}
4533

4634
/// An [Client] which updates the headers for each request.
47-
class _BrowserClient extends BrowserClient {
48-
final Future<Map<String, String>> Function() getHeadersFn;
49-
final _client = BrowserClient();
50-
_BrowserClient({
51-
required this.getHeadersFn,
52-
});
35+
abstract class _BrowserClient extends BrowserClient {
36+
final BrowserClient _client = BrowserClient();
5337

5438
@override
5539
Future<StreamedResponse> send(BaseRequest request) async {
56-
final headers = await getHeadersFn();
57-
5840
final modifiedRequest = _RequestImpl.fromRequest(
5941
request,
60-
updateHeaders: headers,
42+
updateHeaders: await _sendHeaders,
6143
);
6244

6345
return await _client.send(modifiedRequest);
@@ -67,6 +49,8 @@ class _BrowserClient extends BrowserClient {
6749
void close() {
6850
_client.close();
6951
}
52+
53+
Future<Map<String, String>> get _sendHeaders;
7054
}
7155

7256
class _RequestImpl extends BaseRequest {

pkg/web_app/lib/src/deferred/markdown.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import 'package:markdown/markdown.dart' as md;
88

99
/// Creates an [Element] with Markdown-formatted content.
1010
Future<Element> markdown(String text) async {
11-
return Element.div()
11+
return DivElement()
1212
..setInnerHtml(
1313
md.markdownToHtml(text),
1414
validator: NodeValidator(uriPolicy: _UnsafeUriPolicy()),

0 commit comments

Comments
 (0)