Build Android (FCM) / build-android-fcm (push) Canceled after 0s
Build Android / build-android (push) Canceled after 0s
Build iOS / build-ios (push) Canceled after 0s
Build Linux / build-linux (push) Canceled after 0s
Build macOS / build-macos (push) Canceled after 0s
Build Windows / build-windows (push) Canceled after 0s
Release (main) / android (oneme) (push) Canceled after 0s
Release (main) / android (qlyra) (push) Canceled after 0s
Release (main) / windows (push) Canceled after 0s
Release (main) / linux (push) Canceled after 0s
Release (main) / macos (push) Canceled after 0s
Release (main) / ios (push) Canceled after 0s
Release (main) / release (push) Canceled after 0s
329 lines
8.6 KiB
Dart
329 lines
8.6 KiB
Dart
import 'dart:convert';
|
|
import 'dart:typed_data';
|
|
|
|
import 'package:flutter_rust_bridge/flutter_rust_bridge.dart'
|
|
show RustStreamSink;
|
|
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated_io.dart'
|
|
show ExternalLibrary;
|
|
|
|
import 'src/rust/api.dart' hide authMode;
|
|
import 'src/rust/api.dart' as native;
|
|
import 'src/rust/frb_generated.dart';
|
|
|
|
export 'src/kusoft_frame.dart';
|
|
export 'src/rust/api.dart'
|
|
show
|
|
HandshakeInfo,
|
|
CallParams,
|
|
CallSignaling,
|
|
KusoftSession,
|
|
PushEvent,
|
|
SessionOptions,
|
|
UploadEvent,
|
|
UploadEvent_Done,
|
|
UploadEvent_Error,
|
|
UploadEvent_Progress,
|
|
WireLogEvent,
|
|
connectCallSignaling,
|
|
decodeVcp,
|
|
setTrustMincifryCa,
|
|
trustMincifryCa;
|
|
|
|
bool _initialized = false;
|
|
|
|
Future<void> initKusoft({String? libraryPath}) async {
|
|
if (_initialized) return;
|
|
await RustLib.init(
|
|
externalLibrary:
|
|
libraryPath == null ? null : ExternalLibrary.open(libraryPath),
|
|
);
|
|
_initialized = true;
|
|
}
|
|
|
|
SessionOptions _options({
|
|
required String host,
|
|
required int port,
|
|
required String deviceId,
|
|
required String instanceId,
|
|
required String appVersion,
|
|
required int buildNumber,
|
|
required String deviceType,
|
|
required String osVersion,
|
|
required String timezone,
|
|
required String screen,
|
|
required String pushDeviceType,
|
|
required String arch,
|
|
required String locale,
|
|
required String deviceName,
|
|
required String deviceLocale,
|
|
required int clientSessionId,
|
|
required int pingIntervalSecs,
|
|
required bool pingInteractive,
|
|
required bool autoReconnect,
|
|
required bool insecureTls,
|
|
required String? proxy,
|
|
}) =>
|
|
SessionOptions(
|
|
host: host,
|
|
port: port,
|
|
deviceId: deviceId,
|
|
instanceId: instanceId,
|
|
appVersion: appVersion,
|
|
buildNumber: buildNumber,
|
|
deviceType: deviceType,
|
|
osVersion: osVersion,
|
|
timezone: timezone,
|
|
screen: screen,
|
|
pushDeviceType: pushDeviceType,
|
|
arch: arch,
|
|
locale: locale,
|
|
deviceName: deviceName,
|
|
deviceLocale: deviceLocale,
|
|
clientSessionId: clientSessionId,
|
|
pingIntervalSecs: BigInt.from(pingIntervalSecs),
|
|
pingInteractive: pingInteractive,
|
|
autoReconnect: autoReconnect,
|
|
insecureTls: insecureTls,
|
|
proxy: proxy,
|
|
);
|
|
|
|
KusoftSession openSession({
|
|
required String host,
|
|
int port = 443,
|
|
String deviceId = 'kusoft-dart',
|
|
String instanceId = 'kusoft-dart',
|
|
String appVersion = '26.20.2',
|
|
int buildNumber = 6758,
|
|
String deviceType = 'ANDROID',
|
|
String osVersion = 'Android 14',
|
|
String timezone = 'Europe/Moscow',
|
|
String screen = '420dpi 420dpi 1080x2340',
|
|
String pushDeviceType = 'GCM',
|
|
String arch = 'arm64-v8a',
|
|
String locale = 'ru',
|
|
String deviceName = 'Dart',
|
|
String deviceLocale = 'ru',
|
|
int clientSessionId = 1700000000,
|
|
int pingIntervalSecs = 30,
|
|
bool pingInteractive = true,
|
|
bool autoReconnect = true,
|
|
bool insecureTls = false,
|
|
String? proxy,
|
|
}) =>
|
|
KusoftSession(
|
|
options: _options(
|
|
host: host,
|
|
port: port,
|
|
deviceId: deviceId,
|
|
instanceId: instanceId,
|
|
appVersion: appVersion,
|
|
buildNumber: buildNumber,
|
|
deviceType: deviceType,
|
|
osVersion: osVersion,
|
|
timezone: timezone,
|
|
screen: screen,
|
|
pushDeviceType: pushDeviceType,
|
|
arch: arch,
|
|
locale: locale,
|
|
deviceName: deviceName,
|
|
deviceLocale: deviceLocale,
|
|
clientSessionId: clientSessionId,
|
|
pingIntervalSecs: pingIntervalSecs,
|
|
pingInteractive: pingInteractive,
|
|
autoReconnect: autoReconnect,
|
|
insecureTls: insecureTls,
|
|
proxy: proxy,
|
|
),
|
|
);
|
|
|
|
(KusoftSession, Stream<WireLogEvent>) openSessionWithWireLog({
|
|
required String host,
|
|
int port = 443,
|
|
String deviceId = 'kusoft-dart',
|
|
String instanceId = 'kusoft-dart',
|
|
String appVersion = '26.20.2',
|
|
int buildNumber = 6758,
|
|
String deviceType = 'ANDROID',
|
|
String osVersion = 'Android 14',
|
|
String timezone = 'Europe/Moscow',
|
|
String screen = '420dpi 420dpi 1080x2340',
|
|
String pushDeviceType = 'GCM',
|
|
String arch = 'arm64-v8a',
|
|
String locale = 'ru',
|
|
String deviceName = 'Dart',
|
|
String deviceLocale = 'ru',
|
|
int clientSessionId = 1700000000,
|
|
int pingIntervalSecs = 30,
|
|
bool pingInteractive = true,
|
|
bool autoReconnect = true,
|
|
bool insecureTls = false,
|
|
String? proxy,
|
|
}) {
|
|
final sink = RustStreamSink<WireLogEvent>();
|
|
final session = KusoftSession(
|
|
options: _options(
|
|
host: host,
|
|
port: port,
|
|
deviceId: deviceId,
|
|
instanceId: instanceId,
|
|
appVersion: appVersion,
|
|
buildNumber: buildNumber,
|
|
deviceType: deviceType,
|
|
osVersion: osVersion,
|
|
timezone: timezone,
|
|
screen: screen,
|
|
pushDeviceType: pushDeviceType,
|
|
arch: arch,
|
|
locale: locale,
|
|
deviceName: deviceName,
|
|
deviceLocale: deviceLocale,
|
|
clientSessionId: clientSessionId,
|
|
pingIntervalSecs: pingIntervalSecs,
|
|
pingInteractive: pingInteractive,
|
|
autoReconnect: autoReconnect,
|
|
insecureTls: insecureTls,
|
|
proxy: proxy,
|
|
),
|
|
wireLog: sink,
|
|
);
|
|
return (session, sink.stream);
|
|
}
|
|
|
|
extension KusoftRequestMap on KusoftSession {
|
|
Future<Map<String, dynamic>> requestMap(
|
|
int opcode,
|
|
Map<String, dynamic> payload,
|
|
) async =>
|
|
_asMap(
|
|
await requestJson(
|
|
opcode: opcode,
|
|
jsonIn: jsonEncode(_escapeBinary(payload)),
|
|
),
|
|
);
|
|
|
|
Future<KusoftResponse> requestMapFull(
|
|
int opcode,
|
|
Map<String, dynamic> payload,
|
|
) async {
|
|
final response = await requestFull(
|
|
opcode: opcode,
|
|
jsonIn: jsonEncode(_escapeBinary(payload)),
|
|
);
|
|
return KusoftResponse(
|
|
cmd: response.cmd,
|
|
opcode: response.opcode,
|
|
payload: _asMap(response.payloadJson),
|
|
errorMessage: response.errorMessage,
|
|
errorKey: response.errorKey,
|
|
);
|
|
}
|
|
|
|
Stream<(int, Map<String, dynamic>)> pushesMap() =>
|
|
pushes().map((event) => (event.opcode, event.payloadMap));
|
|
}
|
|
|
|
class KusoftResponse {
|
|
const KusoftResponse({
|
|
required this.cmd,
|
|
required this.opcode,
|
|
required this.payload,
|
|
this.errorMessage,
|
|
this.errorKey,
|
|
});
|
|
|
|
final int cmd;
|
|
final int opcode;
|
|
final Map<String, dynamic> payload;
|
|
final String? errorMessage;
|
|
final String? errorKey;
|
|
|
|
bool get isOk => cmd == 1;
|
|
bool get isNotFound => cmd == 2;
|
|
bool get isError => cmd == 3;
|
|
}
|
|
|
|
extension KusoftHandshakeMap on HandshakeInfo {
|
|
Map<String, dynamic> get payloadMap => _asMap(payloadJson);
|
|
}
|
|
|
|
extension KusoftPushMap on PushEvent {
|
|
Map<String, dynamic> get payloadMap => _asMap(payloadJson);
|
|
}
|
|
|
|
Uint8List authMode(
|
|
int callsSeed,
|
|
String deviceId, {
|
|
List<int>? signature,
|
|
List<int>? dex,
|
|
List<int>? so,
|
|
}) =>
|
|
native.authMode(
|
|
signature: signature ?? _defaultSignatureDigest,
|
|
dex: dex ?? _defaultDexDigest,
|
|
so: so ?? _defaultSoDigest,
|
|
callsSeed: callsSeed,
|
|
deviceId: deviceId,
|
|
);
|
|
|
|
Map<String, dynamic> _asMap(String source) {
|
|
return decodeKusoftPayload(source);
|
|
}
|
|
|
|
Map<String, dynamic> decodeKusoftPayload(String source) {
|
|
final decoded = _unescapePayload(jsonDecode(source));
|
|
return decoded is Map ? Map<String, dynamic>.from(decoded) : {};
|
|
}
|
|
|
|
dynamic _unescapePayload(dynamic value) {
|
|
if (value is Map) {
|
|
if (value.length == 1 && value[r'$bin'] is String) {
|
|
return base64.decode(value[r'$bin'] as String);
|
|
}
|
|
return value.map((key, item) {
|
|
final candidate =
|
|
key is String && key.startsWith(r'$int:') ? key.substring(5) : null;
|
|
final normalizedKey = candidate != null && int.tryParse(candidate) != null
|
|
? candidate
|
|
: key;
|
|
return MapEntry(normalizedKey, _unescapePayload(item));
|
|
});
|
|
}
|
|
if (value is List) return value.map(_unescapePayload).toList();
|
|
return value;
|
|
}
|
|
|
|
dynamic _escapeBinary(dynamic value) {
|
|
if (value is Uint8List) return {r'$bin': base64.encode(value)};
|
|
if (value is Map) {
|
|
return value.map(
|
|
(key, item) => MapEntry(
|
|
key is int ? '\$int:$key' : key,
|
|
_escapeBinary(item),
|
|
),
|
|
);
|
|
}
|
|
if (value is List) return value.map(_escapeBinary).toList();
|
|
return value;
|
|
}
|
|
|
|
final Uint8List _defaultSignatureDigest = _hex(
|
|
'1684414033eb263e2c615f8b7df5ed8793850a07656304997fbf07e9e21e1e93',
|
|
);
|
|
final Uint8List _defaultSoDigest = _hex(
|
|
'90e2fb8745b17b42a10182f8d8ac590e3fca5b311e2ce2d5144fa2c18cb3090d',
|
|
);
|
|
final Uint8List _defaultDexDigest = _hex(
|
|
'0a6265f6e5d8231b9cba641f8c40475e6f3baeb06ed41b804b9bf7307aa4214e',
|
|
);
|
|
|
|
Uint8List _hex(String source) {
|
|
final output = Uint8List(source.length ~/ 2);
|
|
for (var index = 0; index < output.length; index++) {
|
|
output[index] = int.parse(
|
|
source.substring(index * 2, index * 2 + 2),
|
|
radix: 16,
|
|
);
|
|
}
|
|
return output;
|
|
}
|