import 'dart:convert'; import 'dart:typed_data'; import 'package:dio/dio.dart'; import '../../../common/network/dio_provider.dart'; /// 金值账号服务的 protobuf HTTP 契约。 /// /// 这个轻量编解码只覆盖短信登录需要的字段,避免把整套历史生成模型带入 /// 当前重建中的应用;字段号与金值 `account.proto` 保持一致。 class AuthApi { const AuthApi(this._dio); final Dio _dio; Future sendSmsCode(String phone) async { final response = await _dio.post>( '/api/v1/auth/sms/send', // Dio may JSON-encode a List as `[10,11,...]`. The API expects // the raw protobuf bytes, so preserve the binary payload explicitly. data: Uint8List.fromList(_ProtoWriter.stringField(1, phone)), options: protobufOptions(), ); return _ProtoMessage(response.data ?? const []).integer(1); } Future verifySmsCode(String phone, String code) async { final response = await _dio.post>( '/api/v1/auth/sms/verify', data: Uint8List.fromList([ ..._ProtoWriter.stringField(1, phone), ..._ProtoWriter.stringField(2, code), ]), options: protobufOptions(), ); final reply = _ProtoMessage(response.data ?? const []); final accessToken = reply.string(1).trim(); if (accessToken.isEmpty) { throw const FormatException('登录响应缺少 access_token'); } final user = _ProtoMessage(reply.bytes(3)); return AuthLoginResult( accessToken: accessToken, phone: user.string(2).trim().isEmpty ? phone : user.string(2).trim(), ); } } class AuthLoginResult { const AuthLoginResult({required this.accessToken, required this.phone}); final String accessToken; final String phone; } class _ProtoWriter { static List stringField(int field, String value) => [ ..._varint(field << 3 | 2), ..._varint(utf8.encode(value).length), ...utf8.encode(value), ]; static List _varint(int value) { final bytes = []; var remaining = value; while (remaining > 0x7f) { bytes.add(remaining & 0x7f | 0x80); remaining >>= 7; } bytes.add(remaining); return bytes; } } class _ProtoMessage { _ProtoMessage(List bytes) : _fields = _parse(bytes); final Map _fields; int integer(int field) => _fields[field] is int ? _fields[field]! as int : 0; String string(int field) => _fields[field] is List ? utf8.decode(_fields[field]! as List, allowMalformed: true) : ''; List bytes(int field) => _fields[field] is List ? _fields[field]! as List : const []; static Map _parse(List bytes) { final fields = {}; var cursor = 0; while (cursor < bytes.length) { final tag = _readVarint(bytes, cursor); if (tag == null) break; cursor = tag.next; final field = tag.value >> 3; switch (tag.value & 7) { case 0: final value = _readVarint(bytes, cursor); if (value == null) return fields; fields[field] = value.value; cursor = value.next; case 2: final length = _readVarint(bytes, cursor); if (length == null || length.next + length.value > bytes.length) { return fields; } fields[field] = bytes.sublist( length.next, length.next + length.value, ); cursor = length.next + length.value; default: return fields; } } return fields; } static _ProtoValue? _readVarint(List bytes, int cursor) { var value = 0; var shift = 0; while (cursor < bytes.length && shift <= 63) { final byte = bytes[cursor++]; value |= (byte & 0x7f) << shift; if (byte & 0x80 == 0) return _ProtoValue(value, cursor); shift += 7; } return null; } } class _ProtoValue { const _ProtoValue(this.value, this.next); final int value; final int next; }