mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-11-13 20:25:53 +08:00
[feat] add HarmonyOS mobile trusted device authentication
This commit is contained in:
1 parent
8a1e58d62d
commit
dec9e445c4
9 files changed
+121
-3
No files matched your search
@@ -11,6 +11,12 @@
|
|||||||
|
|
||||||
手机控制端的“设置 → 屏幕方向 → 连接建立后锁定横屏”默认关闭并保存在本机。开启后,手机连接时进入并保持横屏;关闭时跟随设备方向。平板和多窗口不受此选项影响。退出会话恢复首页方向,返回后台会话沿用该会话的方向设置。已移除检测手机姿态后解除初始横屏的逻辑。
|
手机控制端的“设置 → 屏幕方向 → 连接建立后锁定横屏”默认关闭并保存在本机。开启后,手机连接时进入并保持横屏;关闭时跟随设备方向。平板和多窗口不受此选项影响。退出会话恢复首页方向,返回后台会话沿用该会话的方向设置。已移除检测手机姿态后解除初始横屏的逻辑。
|
||||||
|
|
||||||
|
## 移动端可信设备鉴权
|
||||||
|
|
||||||
|
手机和平板的“设置 → 本机配对信息”支持查看指纹和复制配对信息。在被控电脑的“管理可信设备 → 添加可信设备”中粘贴并核对指纹后,可以连接开启“仅允许可信设备控制”的电脑;连接密码仍需验证。
|
||||||
|
|
||||||
|
移动模式使用共享 Ed25519 挑战响应协议,私钥通过 AssetStore 持久保存并禁止同步,不放入缓存、日志或配对信息。重启、清理缓存、更换服务器和本机登录 ID 不改变公钥;已初始化的密钥损坏、不可用或丢失时报告错误,不自动换钥。清除应用数据或重新安装后需要重新配对。
|
||||||
|
|
||||||
## 构建
|
## 构建
|
||||||
|
|
||||||
依赖仓库沿用 Windows/macOS/Linux 的 Xmake 策略:使用默认仓库或调用环境指定的 `XMAKE_GLOBALDIR` / `XMAKE_MAIN_REPO`,不固定或校验仓库提交。鸿蒙 PC 与移动端共用此规则,SDK、编译产物和依赖安装缓存仍按平台隔离。
|
依赖仓库沿用 Windows/macOS/Linux 的 Xmake 策略:使用默认仓库或调用环境指定的 `XMAKE_GLOBALDIR` / `XMAKE_MAIN_REPO`,不固定或校验仓库提交。鸿蒙 PC 与移动端共用此规则,SDK、编译产物和依赖安装缓存仍按平台隔离。
|
||||||
|
|||||||
@@ -3,6 +3,40 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
napi_value TrustKey(napi_env env, napi_callback_info) {
|
||||||
|
try {
|
||||||
|
const auto seed = crossdesk::harmony::MobileGenerateTrustKey();
|
||||||
|
napi_value result;
|
||||||
|
napi_create_string_utf8(env, seed.data(), seed.size(), &result);
|
||||||
|
return result;
|
||||||
|
} catch (const std::exception&) {
|
||||||
|
napi_throw_error(env, nullptr, "Device signing identity unavailable"); return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
napi_value TrustInfo(napi_env env, napi_callback_info info) {
|
||||||
|
size_t count = 2; napi_value arguments[2];
|
||||||
|
if (napi_get_cb_info(env, info, &count, arguments, nullptr, nullptr) != napi_ok || count != 2) {
|
||||||
|
napi_throw_type_error(env, nullptr, "Expected device identity and public ID"); return nullptr;
|
||||||
|
}
|
||||||
|
std::string values[2];
|
||||||
|
for (size_t i = 0; i < 2; ++i) {
|
||||||
|
size_t size = 0;
|
||||||
|
if (napi_get_value_string_utf8(env, arguments[i], nullptr, 0, &size) != napi_ok || size > 128) {
|
||||||
|
napi_throw_type_error(env, nullptr, "Invalid device identity"); return nullptr;
|
||||||
|
}
|
||||||
|
values[i].resize(size + 1);
|
||||||
|
napi_get_value_string_utf8(env, arguments[i], values[i].data(), values[i].size(), &size);
|
||||||
|
values[i].resize(size);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const auto info = crossdesk::harmony::MobileTrustInfo(values[0], values[1]);
|
||||||
|
napi_value result;
|
||||||
|
napi_create_string_utf8(env, info.data(), info.size(), &result);
|
||||||
|
return result;
|
||||||
|
} catch (const std::exception&) {
|
||||||
|
napi_throw_error(env, nullptr, "Device signing identity unavailable"); return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
napi_value Command(napi_env env, napi_callback_info info) {
|
napi_value Command(napi_env env, napi_callback_info info) {
|
||||||
size_t count = 1; napi_value argument;
|
size_t count = 1; napi_value argument;
|
||||||
if (napi_get_cb_info(env, info, &count, &argument, nullptr, nullptr) != napi_ok || count != 1) {
|
if (napi_get_cb_info(env, info, &count, &argument, nullptr, nullptr) != napi_ok || count != 1) {
|
||||||
@@ -24,6 +58,8 @@ napi_value Poll(napi_env env, napi_callback_info) {
|
|||||||
}
|
}
|
||||||
napi_value Init(napi_env env, napi_value exports) {
|
napi_value Init(napi_env env, napi_value exports) {
|
||||||
napi_property_descriptor properties[] = {
|
napi_property_descriptor properties[] = {
|
||||||
|
{"mobileGenerateTrustKey", nullptr, TrustKey, nullptr, nullptr, nullptr, napi_default, nullptr},
|
||||||
|
{"mobileTrustInfo", nullptr, TrustInfo, nullptr, nullptr, nullptr, napi_default, nullptr},
|
||||||
{"mobileCommand", nullptr, Command, nullptr, nullptr, nullptr, napi_default, nullptr},
|
{"mobileCommand", nullptr, Command, nullptr, nullptr, nullptr, napi_default, nullptr},
|
||||||
{"mobilePoll", nullptr, Poll, nullptr, nullptr, nullptr, napi_default, nullptr}};
|
{"mobilePoll", nullptr, Poll, nullptr, nullptr, nullptr, napi_default, nullptr}};
|
||||||
napi_define_properties(env, exports, sizeof(properties) / sizeof(properties[0]), properties);
|
napi_define_properties(env, exports, sizeof(properties) / sizeof(properties[0]), properties);
|
||||||
|
|||||||
@@ -1,2 +1,4 @@
|
|||||||
export const mobileCommand: (json: string) => void;
|
export const mobileCommand: (json: string) => void;
|
||||||
export const mobilePoll: () => string;
|
export const mobilePoll: () => string;
|
||||||
|
export const mobileGenerateTrustKey: () => string;
|
||||||
|
export const mobileTrustInfo: (seed: string, deviceId: string) => string;
|
||||||
@@ -12,6 +12,7 @@ import { AnnouncementInbox, AnnouncementItem, AnnouncementRequest, AnnouncementR
|
|||||||
import { VideoSettings, VideoValues, NetworkStatistics, NetworkReport, RecentPresence, PresenceMessage, asciiKey } from './ControllerModels';
|
import { VideoSettings, VideoValues, NetworkStatistics, NetworkReport, RecentPresence, PresenceMessage, asciiKey } from './ControllerModels';
|
||||||
|
|
||||||
interface Command {
|
interface Command {
|
||||||
|
trustKey?: string;
|
||||||
displayResolutions?: ResolutionPreference[];
|
displayResolutions?: ResolutionPreference[];
|
||||||
displayId?: number; displayKey?: string; width?: number; height?: number;
|
displayId?: number; displayKey?: string; width?: number; height?: number;
|
||||||
op: string; session?: number; connection?: number; server?: string; port?: number; identity?: string;
|
op: string; session?: number; connection?: number; server?: string; port?: number; identity?: string;
|
||||||
@@ -34,9 +35,11 @@ interface VideoCodecStatus { encoder: string; }
|
|||||||
interface ControlData { display_resolution?: ResolutionRequest; display_resolutions?: ResolutionDisplay[]; type: number; host_info?: HostInfo; video_settings?: SettingAck; service_status?: ServiceStatus; privacy_status?: PrivacyStatus; video_codec?: VideoCodecStatus; }
|
interface ControlData { display_resolution?: ResolutionRequest; display_resolutions?: ResolutionDisplay[]; type: number; host_info?: HostInfo; video_settings?: SettingAck; service_status?: ServiceStatus; privacy_status?: PrivacyStatus; video_codec?: VideoCodecStatus; }
|
||||||
interface PresenceRequest { type: string; devices: string[]; subscribe: boolean; }
|
interface PresenceRequest { type: string; devices: string[]; subscribe: boolean; }
|
||||||
interface SignalType { type: string; }
|
interface SignalType { type: string; }
|
||||||
|
interface TrustInfo { pairing: string; fingerprint: string; }
|
||||||
|
|
||||||
@Observed
|
@Observed
|
||||||
export class MobileState {
|
export class MobileState {
|
||||||
|
trustFingerprint: string = '';
|
||||||
page: string = 'home';
|
page: string = 'home';
|
||||||
consent: boolean = false;
|
consent: boolean = false;
|
||||||
privacyAvailable: boolean = false;
|
privacyAvailable: boolean = false;
|
||||||
@@ -200,12 +203,13 @@ export class MobileController {
|
|||||||
this.starting = true; const session = ++this.session, scope = this.scope();
|
this.starting = true; const session = ++this.session, scope = this.scope();
|
||||||
try {
|
try {
|
||||||
let identity = await this.store.secret(`identity:${scope}`);
|
let identity = await this.store.secret(`identity:${scope}`);
|
||||||
|
const trustKey = await this.store.trustKey();
|
||||||
// A public ID alone is not a login credential. Discard legacy incomplete records.
|
// A public ID alone is not a login credential. Discard legacy incomplete records.
|
||||||
if (identity && !/^[0-9]{9}@.+$/.test(identity)) { await this.store.removeSecret(`identity:${scope}`); identity = ''; }
|
if (identity && !/^[0-9]{9}@.+$/.test(identity)) { await this.store.removeSecret(`identity:${scope}`); identity = ''; }
|
||||||
if (session !== this.session || !this.foreground || !this.state.consent) return;
|
if (session !== this.session || !this.foreground || !this.state.consent) return;
|
||||||
this.state.signalText = tr("正在连接…"); this.state.signal = 0; this.started = true;
|
this.state.signalText = tr("正在连接…"); this.state.signal = 0; this.started = true;
|
||||||
this.send({ op: 'start', session: session, server: this.state.server, port: Number(this.state.port), identity: identity,
|
this.send({ op: 'start', session: session, server: this.state.server, port: Number(this.state.port), identity: identity,
|
||||||
logs: this.context.cacheDir, certificates: '/etc/security/certificates', directory: this.store.directory,
|
trustKey: trustKey, logs: this.context.cacheDir, certificates: '/etc/security/certificates', directory: this.store.directory,
|
||||||
previewDirectory: this.store.previewDirectory, version: this.state.version });
|
previewDirectory: this.store.previewDirectory, version: this.state.version });
|
||||||
} catch { this.state.signalText = tr("安全存储不可用,请解锁设备后重试"); this.state.signal = -1; }
|
} catch { this.state.signalText = tr("安全存储不可用,请解锁设备后重试"); this.state.signal = -1; }
|
||||||
finally { if (session === this.session) this.starting = false; }
|
finally { if (session === this.session) this.starting = false; }
|
||||||
@@ -228,6 +232,20 @@ export class MobileController {
|
|||||||
this.navigate('settings');
|
this.navigate('settings');
|
||||||
}
|
}
|
||||||
navigate(page: string): void { this.state.page = page; this.state.modal = ''; }
|
navigate(page: string): void { this.state.page = page; this.state.modal = ''; }
|
||||||
|
async pairingInfo(copy: boolean): Promise<void> {
|
||||||
|
try {
|
||||||
|
const seed = await this.store.trustKey();
|
||||||
|
const info = JSON.parse(bridge.mobileTrustInfo(seed, this.state.identity)) as TrustInfo;
|
||||||
|
if (!this.foreground || this.state.page !== 'settings') return;
|
||||||
|
this.state.trustFingerprint = info.fingerprint.replace(/(.{4})(?!$)/g, '$1 ');
|
||||||
|
if (copy) {
|
||||||
|
try {
|
||||||
|
await pasteboard.getSystemPasteboard().setData(pasteboard.createData(pasteboard.MIMETYPE_TEXT_PLAIN, info.pairing));
|
||||||
|
this.toast(tr("配对信息已复制。"));
|
||||||
|
} catch { this.toast(tr("无法复制配对信息,请重试。")); }
|
||||||
|
}
|
||||||
|
} catch { this.toast(tr("安全存储不可用,请解锁设备后重试")); }
|
||||||
|
}
|
||||||
history(): void {
|
history(): void {
|
||||||
const values = this.store.recent(this.scope()), now = this.now(); this.presence?.watch(values.map((d: RecentDevice) => d.id), now);
|
const values = this.store.recent(this.scope()), now = this.now(); this.presence?.watch(values.map((d: RecentDevice) => d.id), now);
|
||||||
this.state.recent = values.filter((d: RecentDevice) => this.presence?.online(d.id, now))
|
this.state.recent = values.filter((d: RecentDevice) => this.presence?.online(d.id, now))
|
||||||
|
|||||||
@@ -6,9 +6,11 @@ import { cryptoFramework } from '@kit.CryptoArchitectureKit';
|
|||||||
import { util } from '@kit.ArkTS';
|
import { util } from '@kit.ArkTS';
|
||||||
import { fileIo as fs } from '@kit.CoreFileKit';
|
import { fileIo as fs } from '@kit.CoreFileKit';
|
||||||
import { image } from '@kit.ImageKit';
|
import { image } from '@kit.ImageKit';
|
||||||
|
import bridge from 'libcrossdesk.so';
|
||||||
|
|
||||||
export interface RecentDevice { id: string; name: string; platform: string; remember: boolean; preview: string; }
|
export interface RecentDevice { id: string; name: string; platform: string; remember: boolean; preview: string; }
|
||||||
export class MobileStore {
|
export class MobileStore {
|
||||||
|
private static trustLoading?: Promise<string>;
|
||||||
private data: preferences.Preferences;
|
private data: preferences.Preferences;
|
||||||
readonly directory: string;
|
readonly directory: string;
|
||||||
readonly previewDirectory: string;
|
readonly previewDirectory: string;
|
||||||
@@ -59,6 +61,25 @@ export class MobileStore {
|
|||||||
try { await asset.remove(await this.query(key)); }
|
try { await asset.remove(await this.query(key)); }
|
||||||
catch (error) { if ((error as ErrorWithCode).code !== asset.ErrorCode.NOT_FOUND) throw new Error(tr("无法删除已保存的凭据")); }
|
catch (error) { if ((error as ErrorWithCode).code !== asset.ErrorCode.NOT_FOUND) throw new Error(tr("无法删除已保存的凭据")); }
|
||||||
}
|
}
|
||||||
|
async trustKey(): Promise<string> {
|
||||||
|
// Serializes pairing export and reconnects, including overlapping page lifetimes.
|
||||||
|
if (MobileStore.trustLoading) return MobileStore.trustLoading;
|
||||||
|
const task = this.loadTrustKey(); MobileStore.trustLoading = task;
|
||||||
|
try { return await task; }
|
||||||
|
finally { if (MobileStore.trustLoading === task) MobileStore.trustLoading = undefined; }
|
||||||
|
}
|
||||||
|
private async loadTrustKey(): Promise<string> {
|
||||||
|
let seed = await this.secret('device-trust:v1');
|
||||||
|
if (!seed) {
|
||||||
|
if (this.flag('deviceTrustInitialized')) throw new Error('Device signing identity missing');
|
||||||
|
seed = bridge.mobileGenerateTrustKey();
|
||||||
|
await this.saveSecret('device-trust:v1', seed);
|
||||||
|
}
|
||||||
|
// Validate persisted data without replacing a corrupt/unavailable identity.
|
||||||
|
bridge.mobileTrustInfo(seed, '');
|
||||||
|
this.put('deviceTrustInitialized', true);
|
||||||
|
return seed;
|
||||||
|
}
|
||||||
recent(scope: string): RecentDevice[] {
|
recent(scope: string): RecentDevice[] {
|
||||||
try {
|
try {
|
||||||
const entries = JSON.parse(this.text(`recent:${scope}`, '[]')) as RecentDevice[];
|
const entries = JSON.parse(this.text(`recent:${scope}`, '[]')) as RecentDevice[];
|
||||||
|
|||||||
@@ -304,6 +304,18 @@ struct Mobile {
|
|||||||
}.width('100%').backgroundColor(Color.White).borderRadius(26).clip(true).border({ width: 0.5, color: MobileTheme.line }).margin({ top: 28 })
|
}.width('100%').backgroundColor(Color.White).borderRadius(26).clip(true).border({ width: 0.5, color: MobileTheme.line }).margin({ top: 28 })
|
||||||
Text(this.state.identity ? tr("本机 ID {0}", [String(this.formatId(this.state.identity))]) : this.state.signalText).fontSize(12).fontColor(MobileTheme.secondary)
|
Text(this.state.identity ? tr("本机 ID {0}", [String(this.formatId(this.state.identity))]) : this.state.signalText).fontSize(12).fontColor(MobileTheme.secondary)
|
||||||
.textAlign(TextAlign.Center).padding({ left: 16, right: 16, top: 12, bottom: 16 }).width('100%').copyOption(CopyOptions.LocalDevice)
|
.textAlign(TextAlign.Center).padding({ left: 16, right: 16, top: 12, bottom: 16 }).width('100%').copyOption(CopyOptions.LocalDevice)
|
||||||
|
this.SectionTitle(tr("本机配对信息"))
|
||||||
|
Column() {
|
||||||
|
this.SettingRow(tr("查看指纹"), '', () => this.controller?.pairingInfo(false))
|
||||||
|
if (this.state.trustFingerprint) {
|
||||||
|
Text(this.state.trustFingerprint).fontSize(14).fontFamily('monospace').fontColor(MobileTheme.secondary)
|
||||||
|
.padding(16).width('100%').copyOption(CopyOptions.LocalDevice)
|
||||||
|
}
|
||||||
|
this.Rule()
|
||||||
|
this.SettingRow(tr("复制配对信息"), '', () => this.controller?.pairingInfo(true))
|
||||||
|
}.width('100%').backgroundColor(Color.White).borderRadius(26).clip(true).border({ width: 0.5, color: MobileTheme.line })
|
||||||
|
Text(tr("将配对信息添加到被控端的可信设备列表,并核对指纹。"))
|
||||||
|
.fontSize(12).fontColor(MobileTheme.secondary).width('100%').padding({ left: 16, right: 16, top: 8, bottom: 12 })
|
||||||
this.SectionTitle(tr("隐私"))
|
this.SectionTitle(tr("隐私"))
|
||||||
Column() {
|
Column() {
|
||||||
this.SettingRow(tr("隐私与授权"), this.state.consent ? tr("已授权") : tr("未授权"), () => this.navigate('privacy'))
|
this.SettingRow(tr("隐私与授权"), this.state.consent ? tr("已授权") : tr("未授权"), () => this.navigate('privacy'))
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include "key_mapping.h"
|
#include "key_mapping.h"
|
||||||
#include <app_version.h>
|
#include <app_version.h>
|
||||||
#include <mobile_controller_protocol.h>
|
#include <mobile_controller_protocol.h>
|
||||||
|
#include <mobile_device_trust.h>
|
||||||
#include <mobile_file_transfer_session.h>
|
#include <mobile_file_transfer_session.h>
|
||||||
#include <mobile_video_timing.h>
|
#include <mobile_video_timing.h>
|
||||||
#include <display_stream_id.h>
|
#include <display_stream_id.h>
|
||||||
@@ -146,7 +147,9 @@ class MobileSession {
|
|||||||
auto info = MakeHostInformation("CrossDesk HarmonyOS Mobile", {}, true, s->version_, HostPlatform::HarmonyOS);
|
auto info = MakeHostInformation("CrossDesk HarmonyOS Mobile", {}, true, s->version_, HostPlatform::HarmonyOS);
|
||||||
info.i.supports_video_codec_status = true;
|
info.i.supports_video_codec_status = true;
|
||||||
info.preferred_display_resolutions = s->preferred_display_resolutions_;
|
info.preferred_display_resolutions = s->preferred_display_resolutions_;
|
||||||
s->Send(info); FreeRemoteAction(info);
|
const auto message = mobile_controller::DeviceTrustIdentity::Advertise(info.to_json());
|
||||||
|
MiniRtcSendReliableDataFrame(s->controller_, message.data(), message.size(), kControlStream);
|
||||||
|
FreeRemoteAction(info);
|
||||||
RemoteAction audio{}; audio.type = audio_capture; audio.a = !s->muted_; s->Send(audio);
|
RemoteAction audio{}; audio.type = audio_capture; audio.a = !s->muted_; s->Send(audio);
|
||||||
s->audio_.Start();
|
s->audio_.Start();
|
||||||
} else if (status >= MiniRtcConnectionDisconnected) s->Disconnect("");
|
} else if (status >= MiniRtcConnectionDisconnected) s->Disconnect("");
|
||||||
@@ -183,6 +186,13 @@ class MobileSession {
|
|||||||
else if (name == kClipboardStream && bytes.size() <= kMaxClipboardBytes)
|
else if (name == kClipboardStream && bytes.size() <= kMaxClipboardBytes)
|
||||||
s->Event({{"type", "clipboard"}, {"text", bytes}}, true);
|
s->Event({{"type", "clipboard"}, {"text", bytes}}, true);
|
||||||
else if (name == kControlStream) {
|
else if (name == kControlStream) {
|
||||||
|
if (s->trust_) {
|
||||||
|
if (auto proof = s->trust_->ChallengeResponse(bytes, s->login_.substr(0, s->login_.find('@')),
|
||||||
|
"CrossDesk HarmonyOS Mobile")) {
|
||||||
|
if (!proof->empty()) MiniRtcSendReliableDataFrame(s->controller_, proof->data(), proof->size(), kControlStream);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
RemoteAction action{};
|
RemoteAction action{};
|
||||||
if (action.from_json(bytes)) {
|
if (action.from_json(bytes)) {
|
||||||
const auto value = json::parse(action.to_json()); FreeRemoteAction(action);
|
const auto value = json::parse(action.to_json()); FreeRemoteAction(action);
|
||||||
@@ -248,11 +258,13 @@ class MobileSession {
|
|||||||
Disconnect(""); online_=false;
|
Disconnect(""); online_=false;
|
||||||
if (identity_) MiniRtcDestroyPeer(&identity_);
|
if (identity_) MiniRtcDestroyPeer(&identity_);
|
||||||
++session_; login_.clear(); signal_status_=-1;
|
++session_; login_.clear(); signal_status_=-1;
|
||||||
|
trust_.reset();
|
||||||
}
|
}
|
||||||
void Execute(const json& v) {
|
void Execute(const json& v) {
|
||||||
const auto op=v.at("op").get<std::string>();
|
const auto op=v.at("op").get<std::string>();
|
||||||
if (op=="start") {
|
if (op=="start") {
|
||||||
Stop(); session_=v.at("session").get<uint64_t>();
|
Stop(); session_=v.at("session").get<uint64_t>();
|
||||||
|
trust_=std::make_unique<mobile_controller::DeviceTrustIdentity>(v.at("trustKey").get_ref<const std::string&>());
|
||||||
server_=v.at("server").get<std::string>(); port_=v.at("port").get<int>();
|
server_=v.at("server").get<std::string>(); port_=v.at("port").get<int>();
|
||||||
logs_=v.at("logs").get<std::string>(); login_=v.value("identity",""); version_=v.value("version","0.1.0");
|
logs_=v.at("logs").get<std::string>(); login_=v.value("identity",""); version_=v.value("version","0.1.0");
|
||||||
const auto roots=v.at("certificates").get<std::string>(); directory_=v.at("directory").get<std::string>();
|
const auto roots=v.at("certificates").get<std::string>(); directory_=v.at("directory").get<std::string>();
|
||||||
@@ -408,6 +420,7 @@ class MobileSession {
|
|||||||
MiniRtcPeerPtr* controller_=nullptr;
|
MiniRtcPeerPtr* controller_=nullptr;
|
||||||
std::unique_ptr<Context> identity_context_,control_context_;
|
std::unique_ptr<Context> identity_context_,control_context_;
|
||||||
std::unique_ptr<mobile_controller::FileTransferSession> files_;
|
std::unique_ptr<mobile_controller::FileTransferSession> files_;
|
||||||
|
std::unique_ptr<mobile_controller::DeviceTrustIdentity> trust_;
|
||||||
std::string server_,logs_,login_,controller_login_,remote_,directory_,preview_directory_,preview_path_,version_;
|
std::string server_,logs_,login_,controller_login_,remote_,directory_,preview_directory_,preview_path_,version_;
|
||||||
std::vector<DisplayResolutionPreference> preferred_display_resolutions_;
|
std::vector<DisplayResolutionPreference> preferred_display_resolutions_;
|
||||||
int port_=9099,signal_status_=-1;
|
int port_=9099,signal_status_=-1;
|
||||||
@@ -432,4 +445,10 @@ void MobileCommand(const std::string& text) {
|
|||||||
}
|
}
|
||||||
std::string MobilePoll() { std::lock_guard lock(mobile_mutex); return mobile?mobile->Poll():"[]"; }
|
std::string MobilePoll() { std::lock_guard lock(mobile_mutex); return mobile?mobile->Poll():"[]"; }
|
||||||
void MobileShutdown() { std::lock_guard lock(mobile_mutex); mobile.reset(); }
|
void MobileShutdown() { std::lock_guard lock(mobile_mutex); mobile.reset(); }
|
||||||
|
std::string MobileGenerateTrustKey() { return mobile_controller::DeviceTrustIdentity::GeneratePrivateKey(); }
|
||||||
|
std::string MobileTrustInfo(const std::string& seed, const std::string& device_id) {
|
||||||
|
mobile_controller::DeviceTrustIdentity trust(seed);
|
||||||
|
return json{{"pairing", trust.PairingInfo(device_id.substr(0, device_id.find('@')), "CrossDesk HarmonyOS Mobile")},
|
||||||
|
{"fingerprint", trust.Fingerprint()}}.dump();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -16,6 +16,8 @@ namespace crossdesk::harmony {
|
|||||||
__attribute__((visibility("default"))) void MobileCommand(const std::string& json);
|
__attribute__((visibility("default"))) void MobileCommand(const std::string& json);
|
||||||
__attribute__((visibility("default"))) std::string MobilePoll();
|
__attribute__((visibility("default"))) std::string MobilePoll();
|
||||||
__attribute__((visibility("default"))) void MobileShutdown();
|
__attribute__((visibility("default"))) void MobileShutdown();
|
||||||
|
__attribute__((visibility("default"))) std::string MobileGenerateTrustKey();
|
||||||
|
__attribute__((visibility("default"))) std::string MobileTrustInfo(const std::string& seed, const std::string& device_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -5,13 +5,15 @@ add_rules("mode.release", "mode.debug")
|
|||||||
set_policy("package.precompiled", false)
|
set_policy("package.precompiled", false)
|
||||||
add_cxflags("-fPIC")
|
add_cxflags("-fPIC")
|
||||||
includes("../../../deps/submodules/minirtc")
|
includes("../../../deps/submodules/minirtc")
|
||||||
|
add_requires("openssl3 3.3.2", {system = false, configs = {shared = false}})
|
||||||
|
|
||||||
target("crossdesk_harmony_mobile")
|
target("crossdesk_harmony_mobile")
|
||||||
set_kind("shared")
|
set_kind("shared")
|
||||||
set_default(false)
|
set_default(false)
|
||||||
add_deps("minirtc")
|
add_deps("minirtc")
|
||||||
add_packages("nlohmann_json", "libyuv")
|
add_packages("nlohmann_json", "libyuv", "openssl3")
|
||||||
add_files("mobile_session.cpp", "media.cpp", "audio.cpp", "../../../libs/wire/src/remote_action.cpp", "../../../libs/wire/src/file_transfer.cpp", "../../../libs/wire/src/app_version.cpp")
|
add_files("mobile_session.cpp", "media.cpp", "audio.cpp", "../../../libs/wire/src/remote_action.cpp", "../../../libs/wire/src/file_transfer.cpp", "../../../libs/wire/src/app_version.cpp")
|
||||||
|
add_files("../../../libs/controller/src/mobile_device_trust.cpp")
|
||||||
-- Export only the bridge API; bundled static dependencies are internal.
|
-- Export only the bridge API; bundled static dependencies are internal.
|
||||||
add_cxflags("-fvisibility=hidden", "-fvisibility-inlines-hidden")
|
add_cxflags("-fvisibility=hidden", "-fvisibility-inlines-hidden")
|
||||||
add_shflags("-Wl,--exclude-libs,ALL", {force = true})
|
add_shflags("-Wl,--exclude-libs,ALL", {force = true})
|
||||||
|
|||||||
Reference in new issue
Block a user