Files
verify/sdk/cpp/verify_client.cpp
T
admin ed4a561704 feat: add application API documentation and C++ SDK
- Add complete API documentation for application integration (docs/API_DOCUMENT.md)
- Fix check-update API to use version ID comparison instead of string comparison
- Add validation: client version must exist in server version list
- Add support for patch/incremental updates with base_version check
- Add C++ SDK with HTTP client, JSON parser, and crypto support
- Add simple_test.cpp for standalone testing on Windows

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 11:58:44 +08:00

831 lines
26 KiB
C++

#include "verify_client.hpp"
#include <sstream>
#include <fstream>
#include <stdexcept>
#include <ctime>
#include <iomanip>
#include <random>
#include <algorithm>
#ifdef USE_OPENSSL
#include <openssl/bio.h>
#include <openssl/buffer.h>
#include <openssl/evp.h>
#include <openssl/rand.h>
#endif
namespace verify {
// ============================================================================
// Crypto Implementation
// ============================================================================
Crypto::Crypto(EncryptType type, const std::string& key)
: type_(type), key_(key) {
}
Crypto::~Crypto() = default;
std::vector<uint8_t> Crypto::padKey(const std::vector<uint8_t>& key, size_t targetLen) {
if (key.size() >= targetLen) {
return std::vector<uint8_t>(key.begin(), key.begin() + targetLen);
}
std::vector<uint8_t> padded(targetLen);
std::copy(key.begin(), key.end(), padded.begin());
for (size_t i = key.size(); i < targetLen; ++i) {
padded[i] = key[i % key.size()];
}
return padded;
}
std::string base64Encode(const std::vector<uint8_t>& data) {
#ifdef USE_OPENSSL
BIO* bio = BIO_new(BIO_s_mem());
BIO* b64 = BIO_new(BIO_f_base64());
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
BIO_push(b64, bio);
BIO_write(b64, data.data(), static_cast<int>(data.size()));
BIO_flush(b64);
BUF_MEM* buffer;
BIO_get_mem_ptr(b64, &buffer);
std::string result(buffer->data, buffer->length);
BIO_free_all(b64);
return result;
#else
static const char* chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
std::string result;
result.reserve(((data.size() + 2) / 3) * 4);
for (size_t i = 0; i < data.size(); i += 3) {
uint32_t n = (data[i] << 16);
if (i + 1 < data.size()) n |= (data[i + 1] << 8);
if (i + 2 < data.size()) n |= data[i + 2];
result.push_back(chars[(n >> 18) & 0x3F]);
result.push_back(chars[(n >> 12) & 0x3F]);
result.push_back((i + 1 < data.size()) ? chars[(n >> 6) & 0x3F] : '=');
result.push_back((i + 2 < data.size()) ? chars[n & 0x3F] : '=');
}
return result;
#endif
}
std::vector<uint8_t> base64Decode(const std::string& encoded) {
#ifdef USE_OPENSSL
BIO* bio = BIO_new_mem_buf(encoded.data(), static_cast<int>(encoded.size()));
BIO* b64 = BIO_new(BIO_f_base64());
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
BIO_push(b64, bio);
std::vector<uint8_t> result(encoded.size());
int len = BIO_read(b64, result.data(), static_cast<int>(result.size()));
result.resize(len > 0 ? len : 0);
BIO_free_all(b64);
return result;
#else
static const int table[] = {
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63,
52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-1,-1,-1,
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,
15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,
-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,
41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1
};
std::vector<uint8_t> result;
result.reserve(encoded.size() * 3 / 4);
int val = 0, bits = 0;
for (char c : encoded) {
if (c == '=') break;
int v = table[static_cast<uint8_t>(c)];
if (v < 0) continue;
val = (val << 6) | v;
bits += 6;
if (bits >= 8) {
bits -= 8;
result.push_back(static_cast<uint8_t>((val >> bits) & 0xFF));
}
}
return result;
#endif
}
std::string Crypto::encrypt(const std::string& plaintext) {
if (type_ == EncryptType::None || key_.empty()) {
return plaintext;
}
switch (type_) {
case EncryptType::AES:
return encryptAES(plaintext);
case EncryptType::RC4:
return encryptRC4(plaintext);
default:
return plaintext;
}
}
std::string Crypto::decrypt(const std::string& ciphertext) {
if (type_ == EncryptType::None || key_.empty()) {
return ciphertext;
}
switch (type_) {
case EncryptType::AES:
return decryptAES(ciphertext);
case EncryptType::RC4:
return decryptRC4(ciphertext);
default:
return ciphertext;
}
}
std::string Crypto::encryptAES(const std::string& plaintext) {
#ifdef USE_OPENSSL
std::vector<uint8_t> keyBytes = padKey(std::vector<uint8_t>(key_.begin(), key_.end()), 32);
EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
if (!ctx) throw CryptoException("Failed to create cipher context");
std::vector<uint8_t> nonce(12);
RAND_bytes(nonce.data(), 12);
if (EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), nullptr, nullptr, nullptr) != 1) {
EVP_CIPHER_CTX_free(ctx);
throw CryptoException("Failed to init encryption");
}
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, 12, nullptr);
EVP_EncryptInit_ex(ctx, nullptr, nullptr, keyBytes.data(), nonce.data());
std::vector<uint8_t> ciphertext(plaintext.size() + 16);
int len;
EVP_EncryptUpdate(ctx, ciphertext.data(), &len,
reinterpret_cast<const uint8_t*>(plaintext.data()), plaintext.size());
int ciphertextLen = len;
EVP_EncryptFinal_ex(ctx, ciphertext.data() + len, &len);
ciphertextLen += len;
std::vector<uint8_t> tag(16);
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, tag.data());
EVP_CIPHER_CTX_free(ctx);
// nonce (12) + ciphertext + tag (16)
std::vector<uint8_t> result;
result.reserve(12 + ciphertextLen + 16);
result.insert(result.end(), nonce.begin(), nonce.end());
result.insert(result.end(), ciphertext.begin(), ciphertext.begin() + ciphertextLen);
result.insert(result.end(), tag.begin(), tag.end());
return base64Encode(result);
#else
throw CryptoException("OpenSSL not available. Compile with -DUSE_OPENSSL");
#endif
}
std::string Crypto::decryptAES(const std::string& ciphertext) {
#ifdef USE_OPENSSL
std::vector<uint8_t> data = base64Decode(ciphertext);
if (data.size() < 28) throw CryptoException("Ciphertext too short");
std::vector<uint8_t> keyBytes = padKey(std::vector<uint8_t>(key_.begin(), key_.end()), 32);
std::vector<uint8_t> nonce(data.begin(), data.begin() + 12);
std::vector<uint8_t> tag(data.end() - 16, data.end());
std::vector<uint8_t> encrypted(data.begin() + 12, data.end() - 16);
EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
if (!ctx) throw CryptoException("Failed to create cipher context");
EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), nullptr, nullptr, nullptr);
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, 12, nullptr);
EVP_DecryptInit_ex(ctx, nullptr, nullptr, keyBytes.data(), nonce.data());
std::vector<uint8_t> decrypted(encrypted.size());
int len;
EVP_DecryptUpdate(ctx, decrypted.data(), &len, encrypted.data(), encrypted.size());
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, tag.data());
int ret = EVP_DecryptFinal_ex(ctx, decrypted.data() + len, &len);
EVP_CIPHER_CTX_free(ctx);
if (ret != 1) throw CryptoException("Decryption failed: authentication error");
return std::string(decrypted.begin(), decrypted.begin() + len);
#else
throw CryptoException("OpenSSL not available. Compile with -DUSE_OPENSSL");
#endif
}
class RC4 {
public:
explicit RC4(const std::vector<uint8_t>& key) {
for (int i = 0; i < 256; ++i) s_[i] = static_cast<uint8_t>(i);
uint8_t j = 0;
for (int i = 0; i < 256; ++i) {
j = j + s_[i] + key[i % key.size()];
std::swap(s_[i], s_[j]);
}
}
void process(std::vector<uint8_t>& data) {
for (auto& byte : data) {
i_++;
j_ += s_[i_];
std::swap(s_[i_], s_[j_]);
byte ^= s_[(s_[i_] + s_[j_]) & 0xFF];
}
}
private:
uint8_t s_[256] = {};
uint8_t i_ = 0, j_ = 0;
};
std::string Crypto::encryptRC4(const std::string& plaintext) {
std::vector<uint8_t> keyBytes(key_.begin(), key_.end());
if (keyBytes.empty()) throw CryptoException("RC4 key cannot be empty");
std::vector<uint8_t> data(plaintext.begin(), plaintext.end());
RC4 rc4(keyBytes);
rc4.process(data);
return base64Encode(data);
}
std::string Crypto::decryptRC4(const std::string& ciphertext) {
std::vector<uint8_t> keyBytes(key_.begin(), key_.end());
if (keyBytes.empty()) throw CryptoException("RC4 key cannot be empty");
std::vector<uint8_t> data = base64Decode(ciphertext);
RC4 rc4(keyBytes);
rc4.process(data);
return std::string(data.begin(), data.end());
}
// ============================================================================
// HttpClient Implementation
// ============================================================================
HttpClient::HttpClient() {
curl_global_init(CURL_GLOBAL_DEFAULT);
}
HttpClient::~HttpClient() {
curl_global_cleanup();
}
void HttpClient::setBaseUrl(const std::string& url) {
baseUrl_ = url;
if (!baseUrl_.empty() && baseUrl_.back() == '/') {
baseUrl_.pop_back();
}
}
void HttpClient::setTimeout(int seconds) {
timeout_ = seconds;
}
void HttpClient::addHeader(const std::string& key, const std::string& value) {
headers_[key] = value;
}
void HttpClient::clearHeaders() {
headers_.clear();
}
void HttpClient::setCrypto(std::shared_ptr<Crypto> crypto) {
crypto_ = crypto;
}
std::string HttpClient::buildUrl(const std::string& path) {
return baseUrl_ + path;
}
size_t HttpClient::writeCallback(void* contents, size_t size, size_t nmemb, std::string* userp) {
size_t totalSize = size * nmemb;
userp->append(static_cast<char*>(contents), totalSize);
return totalSize;
}
size_t HttpClient::writeBinaryCallback(void* contents, size_t size, size_t nmemb, std::vector<uint8_t>* userp) {
size_t totalSize = size * nmemb;
userp->insert(userp->end(), static_cast<uint8_t*>(contents), static_cast<uint8_t*>(contents) + totalSize);
return totalSize;
}
json HttpClient::processResponse(const std::string& response) {
std::string data = response;
if (crypto_) {
try {
data = crypto_->decrypt(response);
auto j = json::parse(data);
if (j.contains("data") && j["data"].is_string()) {
j["data"] = json::parse(crypto_->decrypt(j["data"].get<std::string>()));
}
return j;
} catch (...) {
// Try parsing as-is
}
}
return json::parse(response);
}
json HttpClient::get(const std::string& path) {
CURL* curl = curl_easy_init();
if (!curl) throw std::runtime_error("Failed to initialize CURL");
std::string response;
struct curl_slist* headers = nullptr;
for (const auto& h : headers_) {
std::string header = h.first + ": " + h.second;
headers = curl_slist_append(headers, header.c_str());
}
curl_easy_setopt(curl, CURLOPT_URL, buildUrl(path).c_str());
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout_);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
CURLcode res = curl_easy_perform(curl);
long httpCode = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
if (res != CURLE_OK) {
throw std::runtime_error(std::string("CURL error: ") + curl_easy_strerror(res));
}
auto j = processResponse(response);
if (j["code"].get<int>() != 200) {
throw ApiException(j["code"].get<int>(), j["message"].get<std::string>());
}
return j;
}
json HttpClient::post(const std::string& path, const json& body) {
CURL* curl = curl_easy_init();
if (!curl) throw std::runtime_error("Failed to initialize CURL");
std::string response;
struct curl_slist* headers = nullptr;
headers = curl_slist_append(headers, "Content-Type: application/json");
for (const auto& h : headers_) {
std::string header = h.first + ": " + h.second;
headers = curl_slist_append(headers, header.c_str());
}
std::string bodyStr = body.dump();
curl_easy_setopt(curl, CURLOPT_URL, buildUrl(path).c_str());
curl_easy_setopt(curl, CURLOPT_POST, 1L);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, bodyStr.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout_);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
CURLcode res = curl_easy_perform(curl);
long httpCode = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
if (res != CURLE_OK) {
throw std::runtime_error(std::string("CURL error: ") + curl_easy_strerror(res));
}
auto j = processResponse(response);
if (j["code"].get<int>() != 200) {
throw ApiException(j["code"].get<int>(), j["message"].get<std::string>());
}
return j;
}
json HttpClient::postForm(const std::string& path, const std::map<std::string, std::string>& fields, const std::map<std::string, std::string>& files) {
CURL* curl = curl_easy_init();
if (!curl) throw std::runtime_error("Failed to initialize CURL");
std::string response;
curl_mime* mime = curl_mime_init(curl);
struct curl_slist* headers = nullptr;
for (const auto& h : headers_) {
std::string header = h.first + ": " + h.second;
headers = curl_slist_append(headers, header.c_str());
}
for (const auto& f : fields) {
curl_mimepart* part = curl_mime_addpart(mime);
curl_mime_name(part, f.first.c_str());
curl_mime_data(part, f.second.c_str(), CURL_ZERO_TERMINATED);
}
for (const auto& f : files) {
curl_mimepart* part = curl_mime_addpart(mime);
curl_mime_name(part, f.first.c_str());
curl_mime_filedata(part, f.second.c_str());
}
curl_easy_setopt(curl, CURLOPT_URL, buildUrl(path).c_str());
curl_easy_setopt(curl, CURLOPT_MIMEPOST, mime);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout_);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
CURLcode res = curl_easy_perform(curl);
curl_mime_free(mime);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
if (res != CURLE_OK) {
throw std::runtime_error(std::string("CURL error: ") + curl_easy_strerror(res));
}
auto j = processResponse(response);
if (j["code"].get<int>() != 200) {
throw ApiException(j["code"].get<int>(), j["message"].get<std::string>());
}
return j;
}
std::vector<uint8_t> HttpClient::download(const std::string& path) {
CURL* curl = curl_easy_init();
if (!curl) throw std::runtime_error("Failed to initialize CURL");
std::vector<uint8_t> response;
struct curl_slist* headers = nullptr;
for (const auto& h : headers_) {
std::string header = h.first + ": " + h.second;
headers = curl_slist_append(headers, header.c_str());
}
curl_easy_setopt(curl, CURLOPT_URL, buildUrl(path).c_str());
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeBinaryCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout_);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
CURLcode res = curl_easy_perform(curl);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
if (res != CURLE_OK) {
throw std::runtime_error(std::string("CURL error: ") + curl_easy_strerror(res));
}
return response;
}
// ============================================================================
// Client Implementation
// ============================================================================
Client::Client(const std::string& baseUrl, const std::string& appKey)
: baseUrl_(baseUrl), appKey_(appKey) {
http_ = std::make_shared<HttpClient>();
http_->setBaseUrl(baseUrl_);
}
Client::~Client() = default;
void Client::setToken(const std::string& token) {
token_ = token;
http_->addHeader("Authorization", "Bearer " + token_);
}
void Client::setEncryptType(EncryptType type, const std::string& key) {
encryptType_ = type;
crypto_ = std::make_shared<Crypto>(type, key);
http_->setCrypto(crypto_);
}
void Client::setTimeout(int seconds) {
http_->setTimeout(seconds);
}
std::string Client::buildPath(const std::string& endpoint) {
return "/api/v1/app/" + appKey_ + endpoint;
}
AppConfig Client::getAppInfo() {
auto j = http_->get(buildPath("/info"));
AppConfig config;
config.id = j["data"]["id"].get<uint64_t>();
config.name = j["data"]["name"].get<std::string>();
config.description = j["data"]["description"].get<std::string>();
config.iconUrl = j["data"]["icon_url"].get<std::string>();
config.status = j["data"]["status"].get<std::string>();
config.billingType = j["data"]["billing_type"].get<std::string>();
config.loginPolicy = j["data"]["login_policy"].get<std::string>();
config.maxDevices = j["data"]["max_devices"].get<int>();
config.multiOpenMode = j["data"]["multi_open_mode"].get<std::string>();
config.maxInstances = j["data"]["max_instances"].get<int>();
config.enableTrial = j["data"]["enable_trial"].get<bool>();
config.trialBalance = j["data"]["trial_balance"].get<double>();
config.heartbeatInterval = j["data"]["heartbeat_interval"].get<int>();
config.heartbeatTimeout = j["data"]["heartbeat_timeout"].get<int>();
return config;
}
VersionInfo Client::checkUpdate(const std::string& version) {
std::string path = buildPath("/check-update");
if (!version.empty()) {
path += "?version=" + version;
}
auto j = http_->get(path);
VersionInfo info;
info.hasUpdate = j["data"]["has_update"].get<bool>();
info.latestVersion = j["data"]["latest_version"].get<std::string>();
info.downloadUrl = j["data"]["download_url"].get<std::string>();
info.fileSize = j["data"]["file_size"].get<int64_t>();
info.fileHash = j["data"]["file_hash"].get<std::string>();
info.entryFile = j["data"]["entry_file"].get<std::string>();
info.updateNotes = j["data"]["update_notes"].get<std::string>();
info.updateStrategy = j["data"]["update_strategy"].get<std::string>();
info.updateType = j["data"]["update_type"].get<std::string>();
info.updateMethod = j["data"]["update_method"].get<std::string>();
return info;
}
std::vector<json> Client::getAnnouncements() {
auto j = http_->get(buildPath("/announcements"));
return j["data"].get<std::vector<json>>();
}
uint64_t Client::registerUser(const std::string& username,
const std::string& password,
const std::string& deviceId,
const std::string& deviceName,
const std::string& deviceType,
const std::string& instanceId,
const std::string& email,
const std::string& emailCode) {
json body;
body["username"] = username;
body["password"] = password;
body["device_id"] = deviceId;
if (!deviceName.empty()) body["device_name"] = deviceName;
if (!deviceType.empty()) body["device_type"] = deviceType;
if (!instanceId.empty()) body["instance_id"] = instanceId;
if (!email.empty()) body["email"] = email;
if (!emailCode.empty()) body["email_code"] = emailCode;
auto j = http_->post(buildPath("/register"), body);
return j["data"]["user_id"].get<uint64_t>();
}
json Client::login(const std::string& username,
const std::string& password,
const std::string& deviceId,
const std::string& deviceName,
const std::string& deviceType,
const std::string& instanceId) {
json body;
body["username"] = username;
body["password"] = password;
body["device_id"] = deviceId;
if (!deviceName.empty()) body["device_name"] = deviceName;
if (!deviceType.empty()) body["device_type"] = deviceType;
if (!instanceId.empty()) body["instance_id"] = instanceId;
auto j = http_->post(buildPath("/login"), body);
// Auto-set token
if (j["data"].contains("token")) {
setToken(j["data"]["token"].get<std::string>());
}
return j["data"];
}
void Client::sendEmailCode(const std::string& email, const std::string& purpose) {
json body;
body["email"] = email;
body["purpose"] = purpose;
http_->post(buildPath("/send-email-code"), body);
}
void Client::resetPassword(const std::string& email, const std::string& code, const std::string& newPassword) {
json body;
body["email"] = email;
body["code"] = code;
body["password"] = newPassword;
http_->post(buildPath("/reset-password"), body);
}
void Client::changePassword(const std::string& username, const std::string& oldPassword, const std::string& newPassword) {
json body;
body["username"] = username;
body["old_password"] = oldPassword;
body["new_password"] = newPassword;
http_->post(buildPath("/change-password"), body);
}
UserInfo Client::getAccount(uint64_t userId) {
json body;
body["user_id"] = userId;
auto j = http_->post(buildPath("/account"), body);
UserInfo info;
info.userId = j["data"]["user_id"].get<uint64_t>();
info.username = j["data"]["username"].get<std::string>();
info.balance = j["data"]["balance"].get<double>();
info.status = j["data"]["status"].get<std::string>();
return info;
}
json Client::heartbeat(uint64_t userId, const std::string& deviceId, const std::string& instanceId) {
json body;
body["user_id"] = userId;
body["device_id"] = deviceId;
if (!instanceId.empty()) body["instance_id"] = instanceId;
return http_->post(buildPath("/heartbeat"), body)["data"];
}
json Client::recharge(const std::string& username, const std::string& cardKey, const std::string& deviceId) {
json body;
body["username"] = username;
body["card_key"] = cardKey;
body["device_id"] = deviceId;
return http_->post(buildPath("/recharge"), body)["data"];
}
json Client::trial(uint64_t userId) {
json body;
body["user_id"] = userId;
return http_->post(buildPath("/trial"), body)["data"];
}
std::vector<DeviceInfo> Client::getDevices() {
auto j = http_->get(buildPath("/devices"));
std::vector<DeviceInfo> devices;
for (const auto& d : j["data"]) {
DeviceInfo info;
info.id = d["id"].get<uint64_t>();
info.deviceId = d["device_id"].get<std::string>();
info.deviceName = d["device_name"].get<std::string>();
info.deviceType = d["device_type"].get<std::string>();
info.status = d["status"].get<std::string>();
info.onlineSessions = d["online_sessions"].get<int>();
info.createdAt = d["created_at"].get<std::string>();
devices.push_back(info);
}
return devices;
}
json Client::getDeviceCount(uint64_t userId) {
json body;
body["user_id"] = userId;
return http_->post(buildPath("/device-count"), body)["data"];
}
void Client::unbindDevice(uint64_t userId, const std::string& deviceId) {
json body;
body["user_id"] = userId;
body["device_id"] = deviceId;
http_->post(buildPath("/unbind-device"), body);
}
void Client::unbindDeviceWithAuth(const std::string& username, const std::string& password, const std::string& deviceId) {
json body;
body["username"] = username;
body["password"] = password;
body["device_id"] = deviceId;
http_->post(buildPath("/unbind-device-with-auth"), body);
}
std::vector<json> Client::getInstances(uint64_t userId) {
json body;
body["user_id"] = userId;
return http_->post(buildPath("/instances"), body)["data"].get<std::vector<json>>();
}
void Client::forceOfflineInstance(uint64_t userId, const std::string& instanceId) {
json body;
body["user_id"] = userId;
http_->post(buildPath("/instances/" + instanceId + "/offline"), body);
}
json Client::getConstants() {
return http_->get(buildPath("/constants"))["data"];
}
json Client::getConstant(const std::string& key) {
return http_->get(buildPath("/constants/" + key))["data"];
}
std::vector<uint8_t> Client::downloadConstant(const std::string& key) {
return http_->download(buildPath("/constants/" + key + "/download"));
}
json Client::getVariables() {
return http_->get(buildPath("/variables"))["data"];
}
json Client::getVariable(const std::string& key) {
return http_->get(buildPath("/variables/" + key))["data"];
}
std::vector<uint8_t> Client::downloadVariable(const std::string& key) {
return http_->download(buildPath("/variables/" + key + "/download"));
}
json Client::uploadVariable(const std::string& key, const std::string& filePath) {
return http_->postForm(buildPath("/variables/" + key + "/upload"), {}, {{"file", filePath}})["data"];
}
void Client::updateVariables(const std::map<std::string, std::string>& variables) {
json body;
body["variables"] = variables;
http_->post(buildPath("/variables"), body);
}
json Client::createVariableRecord(const std::string& key, const json& data) {
return http_->post(buildPath("/variables/" + key + "/records"), data)["data"];
}
json Client::getVariableRecords(const std::string& key, int page, int pageSize) {
std::string path = buildPath("/variables/" + key + "/records") +
"?page=" + std::to_string(page) + "&page_size=" + std::to_string(pageSize);
return http_->get(path)["data"];
}
void Client::deleteVariableRecord(const std::string& key, uint64_t recordId) {
http_->get(buildPath("/variables/" + key + "/records/" + std::to_string(recordId)));
}
json Client::executeDynamicCode(const std::string& key, const json& params, uint64_t targetUserId) {
json body;
body["params"] = params;
if (targetUserId > 0) {
body["user_id"] = targetUserId;
}
return http_->post(buildPath("/dynamic-code/" + key + "/execute"), body)["data"];
}
} // namespace verify