From cd49c5ba27117db54c475e46c828f06d8d8a1588 Mon Sep 17 00:00:00 2001 From: Harry Fei Date: Fri, 24 Feb 2023 04:02:31 +0800 Subject: [PATCH] use new graphql based enkra api --- lib/models/api.dart | 91 ++++++++++++++++++++++++++++++++++++ lib/models/paired.dart | 7 +-- lib/models/wait_to_pair.dart | 64 ++++++++++++------------- 3 files changed, 125 insertions(+), 37 deletions(-) create mode 100644 lib/models/api.dart diff --git a/lib/models/api.dart b/lib/models/api.dart new file mode 100644 index 0000000..5bb6cf2 --- /dev/null +++ b/lib/models/api.dart @@ -0,0 +1,91 @@ +import 'dart:convert'; + +import 'package:http/http.dart' as http; + +const String ENKRA_API_URL = String.fromEnvironment('ENKRA_API_URL'); + +Uri convertToWebSocket(Uri uri) { + String scheme = uri.scheme == "https" ? "wss" : "ws"; + return uri.replace(scheme: scheme, path: "/send-ws"); +} + +final String apiUrl = '$ENKRA_API_URL/graphql'; +final String wsUrl = convertToWebSocket(Uri.parse(ENKRA_API_URL)).toString(); + +Future requestChannel() async { + final response = await http.post( + Uri.parse(apiUrl), + headers: { + 'Content-Type': 'application/json', + }, + body: jsonEncode({ + 'query': '''mutation { + requestSendChannel { + channelId + hostToken + } + } + ''', + }), + ); + + if (response.statusCode == 200) { + final res = jsonDecode(response.body); + + return res["data"]["requestSendChannel"]; + } + + return null; +} + +Future joinChannel(channelId, encappedKey) async { + final response = await http.post( + Uri.parse(apiUrl), + headers: { + 'Content-Type': 'application/json', + }, + body: jsonEncode({ + 'query': '''mutation { + joinSendChannel( + channelId: "$channelId", + encappedKey: "$encappedKey", + ) + } + ''', + }), + ); + + if (response.statusCode == 200) { + final res = jsonDecode(response.body); + + return res["data"]["joinSendChannel"]; + } + + return null; +} + +Future approvePairing(channelId, deviceToken) async { + final response = await http.post( + Uri.parse(apiUrl), + headers: { + 'Content-Type': 'application/json', + }, + body: jsonEncode({ + 'query': '''mutation { + approveSendChannelPairing( + channelId: "$channelId", + deviceToken: "$deviceToken", + ) + } + ''', + }), + ); + + if (response.statusCode == 200) { + final res = jsonDecode(response.body); + + return res["data"]["approveSendChannelPairing"]; + } + + return null; +} diff --git a/lib/models/paired.dart b/lib/models/paired.dart index 96a75e7..fa5120f 100644 --- a/lib/models/paired.dart +++ b/lib/models/paired.dart @@ -121,11 +121,8 @@ class PairedState extends AppState { _sendMessage(Message msg) async { final content = await _encryptContent(msg.toJson()); - final message = jsonEncode({ - "event": "message", - "content": content, - }); - _channel.sink.add(message); + + _channel.sink.add(content); _addNewMessage(msg); } diff --git a/lib/models/wait_to_pair.dart b/lib/models/wait_to_pair.dart index edaee76..ea1adee 100644 --- a/lib/models/wait_to_pair.dart +++ b/lib/models/wait_to_pair.dart @@ -1,21 +1,19 @@ import 'dart:convert'; -import 'dart:typed_data'; import 'package:web_socket_channel/web_socket_channel.dart'; -import 'package:nanoid/nanoid.dart'; import 'app_state.dart'; import 'ws_client.dart'; +import 'api.dart'; import '../native/native.dart'; -const String WS_API_URL = String.fromEnvironment('WS_API_URL'); const String ENKRA_SEND_APP_URL = String.fromEnvironment('SEND_APP_URL'); class WaitToPairState extends AppState { final WsClient _channel; - - final String _sessionKey; + final String _channelId; + final String _deviceToken; final SecureChannelCipher _cipher; @@ -26,10 +24,11 @@ class WaitToPairState extends AppState { WaitToPairState._internal( WebSocketChannel channel, - this._sessionKey, + this._channelId, this._cipher, this._isSender, this._senderAeadCipher, + this._deviceToken, ) : _channel = WsClient(channel) { if (_isSender) { _senderListen(_channel); @@ -39,51 +38,62 @@ class WaitToPairState extends AppState { } static create() async { - final sessionKey = nanoid(); - final wsUrl = Uri.parse('$WS_API_URL/$sessionKey'); - final channel = WebSocketChannel.connect(wsUrl); + final result = await Future.wait([ + requestChannel(), + SecureChannelCipher.newRandom(bridge: api), + ]); - final channelCipher = await SecureChannelCipher.newRandom(bridge: api); + final requestChannelResult = result[0]; + final channelCipher = result[1]; + + final channelId = requestChannelResult["channelId"]!; + final deviceToken = requestChannelResult["hostToken"]!; + + final wsChannelUrl = Uri.parse('$wsUrl/$channelId/$deviceToken'); + final channel = WebSocketChannel.connect(wsChannelUrl); return WaitToPairState._internal( channel, - sessionKey, + channelId, channelCipher, false, null, + deviceToken, ); } - static connect(List keys) async { - final sessionKey = keys[0]; - final publicKey = base64Url.decode(keys[1]); + static connect(List tokens) async { + final channelId = tokens[0]; + final publicKey = base64Url.decode(tokens[1]); final channelCipher = await SecureChannelCipher.newRandom(bridge: api); final encapKeys = await channelCipher.encapKey(public: publicKey); - final wsUrl = Uri.parse('$WS_API_URL/$sessionKey'); - final channel = WebSocketChannel.connect(wsUrl); + final deviceToken = await joinChannel( + channelId, base64Url.encode(encapKeys.encapsulatedKey)); - _sendPairingMessage(encapKeys.encapsulatedKey, channel); + final wsChannelUrl = Uri.parse('$wsUrl/$channelId/$deviceToken'); + final channel = WebSocketChannel.connect(wsChannelUrl); return WaitToPairState._internal( channel, - sessionKey, + channelId, channelCipher, true, encapKeys.sharedSecret, + deviceToken, ); } String sesssionKey() { - return _sessionKey; + return _channelId; } Future pairingUrl() async { final key = await _cipher.public(); final publicKey = base64Url.encode(key); - return '$ENKRA_SEND_APP_URL/#/$_sessionKey/$publicKey'; + return '$ENKRA_SEND_APP_URL/#/$_channelId/$publicKey'; } _receiverListen(channel) async { @@ -102,9 +112,7 @@ class WaitToPairState extends AppState { final cipherKey = await _cipher.sharedSecret(encapsulatedKey: encappedKey); - _channel.sink.add(jsonEncode({ - "event": "paired", - })); + final result = await approvePairing(_channelId, _deviceToken); _onPaired?.call(cipherKey); } @@ -125,19 +133,11 @@ class WaitToPairState extends AppState { }); } - static _sendPairingMessage(encappedKey, wsChannel) async { - final msg = jsonEncode({ - "event": "pairing", - "encappedKey": base64Url.encode(encappedKey), - }); - - wsChannel.sink.add(msg); - } - setOnPaired(Function(AeadCipher) onPaired) { _onPaired = onPaired; } + @override void dispose() { _cipher.key.dispose(); _cipher.csprng.dispose();