Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/release-web.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,11 +103,11 @@ jobs:

- name: Build Alpha 🔧
if: needs.extract-version.outputs.isAlpha == 'true'
run: flutter build web --release --dart-define=flavor=alpha
run: flutter build web --release --dart-define=flavor=alpha --wasm

- name: Build Prod 🔧
if: needs.extract-version.outputs.isAlpha == 'false'
run: flutter build web --release --dart-define=flavor=prod
run: flutter build web --release --dart-define=flavor=prod --wasm

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
Expand Down
44 changes: 39 additions & 5 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,63 @@
"version": "0.2.0",
"configurations": [
{
"name": "Titan dev",
"name": "Titan dev (JS)",
"request": "launch",
"type": "dart",
"args": ["--flavor", "dev", "--dart-define", "flavor=dev"]
},
{
"name": "Titan dev (profile mode)",
"name": "Titan dev (JS, profile mode)",
"request": "launch",
"type": "dart",
"flutterMode": "profile",
"args": ["--flavor", "dev", "--dart-define", "flavor=dev"]
},
{
"name": "Titan alpha",
"name": "Titan alpha (JS)",
"request": "launch",
"type": "dart",
"args": ["--flavor", "alpha", "--dart-define", "flavor=alpha"]
},
{
"name": "Titan prod",
"name": "Titan dev (WASM)",
"request": "launch",
"type": "dart",
"args": ["--flavor", "prod", "--dart-define", "flavor=prod"]
"args": [
"--flavor",
"dev",
"--dart-define",
"flavor=dev",
"--wasm",
"--no-cross-origin-isolation"
]
},
{
"name": "Titan dev (WASM, profile mode)",
"request": "launch",
"type": "dart",
"flutterMode": "profile",
"args": [
"--flavor",
"dev",
"--dart-define",
"flavor=dev",
"--wasm",
"--no-cross-origin-isolation"
]
},
{
"name": "Titan alpha (WASM)",
"request": "launch",
"type": "dart",
"args": [
"--flavor",
"alpha",
"--dart-define",
"flavor=alpha",
"--wasm",
"--no-cross-origin-isolation"
]
}
]
}
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM nginx:1.28.0-alpine3.21-slim
FROM nginx:1.28.2-alpine3.23-slim
COPY nginx.conf /etc/nginx/nginx.conf
COPY ./build/web/ /app/html
RUN find /app/html/ -type f -size +512c -regex '.*\.\(html\|css\|js\|json\|svg\|ttf\|otf\|woff2\|wasm\|mjs\|symbols\|yaml\|env\)' -exec gzip -k9 {} +
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ flutter run --flavor alpha
More generally you can use:

```bash
flutter run --flavor <your_flavor> [ -d <your_device> ]
flutter run --flavor <your_flavor> [ -d <your_device> ] [ --wasm --no-cross-origin-isolation ]
```

- Where the flavor can be any of `dev`, `alpha`, or `prod` (whose policy is to only accept the prod client).
Expand All @@ -196,6 +196,8 @@ flutter run --flavor alpha -d chrome
flutter run --flavor dev -d web-server
```

- The optional duo `--wasm --no-cross-origin-isolation` runs a version compiled to WebAssembly instead of Javascript.

</details>

### Check the app is running
Expand Down Expand Up @@ -280,9 +282,11 @@ flutter build {target} --flavor={flavor}
Currently flavor are not supported for Flutter for web, you should use:

```
flutter build web --dart-define=flavor={flavor}
flutter build web --dart-define=flavor={flavor} [--wasm]
```

The optional `--wasm` flag enables Titan to be also compiled to WebAssembly (besides the Javascript compilation).

### Notifications setup

Notifications are handled using the Firebase Cloud Messaging API. On mobile platforms, a valid notification configuration is required to debug Titan. Notifications are disabled on web builds.
Expand Down
101 changes: 37 additions & 64 deletions lib/auth/providers/openid_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import 'package:titan/tools/cache/cache_manager.dart';
import 'package:titan/tools/functions.dart';
import 'package:titan/tools/repository/repository.dart';
import 'dart:convert';
import 'package:universal_html/html.dart' as html;
import 'package:titan/tools/web-window-callback/web_window_with_callback.dart';

final authTokenProvider =
StateNotifierProvider<OpenIdTokenProvider, AsyncValue<Map<String, String>>>(
Expand Down Expand Up @@ -140,7 +140,6 @@ class OpenIdTokenProvider
}

Future getTokenFromRequest() async {
html.WindowBase? popupWin;
final codeVerifier = generateRandomString(128);

final authUrl =
Expand All @@ -149,72 +148,46 @@ class OpenIdTokenProvider
state = const AsyncValue.loading();
try {
if (kIsWeb) {
popupWin = html.window.open(
webWindowWithCallback(
authUrl,
"Hyperion",
"width=800, height=900, scrollbars=yes",
);

final completer = Completer();
void checkWindowClosed() {
if (popupWin != null && popupWin!.closed == true) {
completer.complete();
} else {
Future.delayed(
const Duration(milliseconds: 100),
checkWindowClosed,
completerFutureCallback: () {
state.maybeWhen(
loading: () {
state = AsyncValue.data({tokenKey: "", refreshTokenKey: ""});
},
orElse: () {},
);
}
}

checkWindowClosed();
completer.future.then((_) {
state.maybeWhen(
loading: () {
state = AsyncValue.data({tokenKey: "", refreshTokenKey: ""});
},
orElse: () {},
);
});

void login(String data) async {
final receivedUri = Uri.parse(data);
final token = receivedUri.queryParameters["code"];
if (popupWin != null) {
popupWin!.close();
popupWin = null;
}
try {
if (token != null && token.isNotEmpty) {
final resp = await openIdRepository.getToken(
token,
clientId,
redirectURL.toString(),
codeVerifier,
"authorization_code",
);
final accessToken = resp[tokenKey]!;
final refreshToken = resp[refreshTokenKey]!;
await _secureStorage.write(key: tokenName, value: refreshToken);
state = AsyncValue.data({
tokenKey: accessToken,
refreshTokenKey: refreshToken,
});
} else {
throw Exception('Wrong credentials');
},
loginCallback: (String data) async {
final receivedUri = Uri.parse(data);
final token = receivedUri.queryParameters["code"];
try {
if (token != null && token.isNotEmpty) {
final resp = await openIdRepository.getToken(
token,
clientId,
redirectURL.toString(),
codeVerifier,
"authorization_code",
);
final accessToken = resp[tokenKey]!;
final refreshToken = resp[refreshTokenKey]!;
await _secureStorage.write(key: tokenName, value: refreshToken);
state = AsyncValue.data({
tokenKey: accessToken,
refreshTokenKey: refreshToken,
});
} else {
throw Exception('Wrong credentials');
}
} on TimeoutException catch (_) {
throw Exception('No response from server');
} catch (e) {
rethrow;
}
} on TimeoutException catch (_) {
throw Exception('No response from server');
} catch (e) {
rethrow;
}
}

html.window.onMessage.listen((event) {
if (event.data.toString().contains('code=')) {
login(event.data);
}
});
},
);
} else {
AuthorizationTokenResponse resp = await appAuth
.authorizeAndExchangeCode(
Expand Down
66 changes: 21 additions & 45 deletions lib/mypayment/ui/pages/fund_page/confirm_button.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import 'package:titan/mypayment/providers/my_wallet_provider.dart';
import 'package:titan/mypayment/providers/tos_provider.dart';
import 'package:titan/tools/functions.dart';
import 'package:titan/tools/ui/builders/waiting_button.dart';
import 'package:universal_html/html.dart' as html;
import 'package:url_launcher/url_launcher.dart';
import 'package:titan/tools/web-window-callback/web_window_with_callback.dart';

class ConfirmFundButton extends ConsumerWidget {
const ConfirmFundButton({super.key});
Expand Down Expand Up @@ -60,50 +60,26 @@ class ConfirmFundButton extends ConsumerWidget {
}

void helloAssoCallback(String fundingUrl) async {
html.WindowBase? popupWin =
html.window.open(
fundingUrl,
"HelloAsso",
"width=800, height=900, scrollbars=yes",
)
as html.WindowBase?;

if (popupWin == null) {
displayToastWithContext(TypeMsg.error, "Veuillez autoriser les popups");
return;
}

final completer = Completer();
void checkWindowClosed() {
if (popupWin.closed == true) {
completer.complete();
} else {
Future.delayed(const Duration(milliseconds: 100), checkWindowClosed);
}
}

checkWindowClosed();
completer.future.then((_) {});

void login(String data) async {
final receivedUri = Uri.parse(data);
final code = receivedUri.queryParameters["code"];
if (code == "succeeded") {
displayToastWithContext(TypeMsg.msg, "Paiement effectué avec succès");
myWalletNotifier.getMyWallet();
myHistoryNotifier.getHistory();
} else {
displayToastWithContext(TypeMsg.error, "Paiement annulé");
}
popupWin.close();
Navigator.pop(context, code);
}

html.window.onMessage.listen((event) {
if (event.data.toString().contains('code=')) {
login(event.data);
}
});
webWindowWithCallback(
fundingUrl,
"HelloAsso",
completerFutureCallback: () {},
loginCallback: (String data) async {
final receivedUri = Uri.parse(data);
final code = receivedUri.queryParameters["code"];
if (code == "succeeded") {
displayToastWithContext(
TypeMsg.msg,
"Paiement effectué avec succès",
);
myWalletNotifier.getMyWallet();
myHistoryNotifier.getHistory();
} else {
displayToastWithContext(TypeMsg.error, "Paiement annulé");
}
Navigator.pop(context, code);
},
);
}

return WaitingButton(
Expand Down
10 changes: 10 additions & 0 deletions lib/tools/web-window-callback/else.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import 'dart:async';

void webWindowWithCallback(
String windowUrl,
String windowName, {
required void Function() completerFutureCallback,
required Future<void> Function(String) loginCallback,
}) async => throw UnsupportedError(
"`webWindowWithCallback()` is not implemented for the current platform",
);
35 changes: 35 additions & 0 deletions lib/tools/web-window-callback/html.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import 'dart:async';
import 'package:universal_html/universal_html.dart';

void webWindowWithCallback(
String windowUrl,
String windowName, {
required void Function() completerFutureCallback,
required Future<void> Function(String) loginCallback,
}) async {
WindowBase popupWin = window.open(
windowUrl,
windowName,
"width=800, height=900, scrollbars=yes",
);

final completer = Completer();
void checkWindowClosed() {
if (popupWin.closed == true) {
completer.complete();
} else {
Future.delayed(const Duration(milliseconds: 100), checkWindowClosed);
}
}

checkWindowClosed();
completer.future.then((_) => completerFutureCallback());

window.onMessage.listen((event) {
final data = event.data.toString();
if (data.contains('code=')) {
loginCallback(data);
popupWin.close();
}
});
}
Loading
Loading