feat: trust peer

This commit is contained in:
2026-02-07 03:17:37 +08:00
parent d8ffc5eea5
commit f3adb56bd0
19 changed files with 438 additions and 155 deletions

View File

@@ -9,6 +9,10 @@ import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Cr
// @ts-ignore: Unused imports
import * as $models from "./models.js";
export function AddTrustedPeer(peerID: string, publicKey: string): $CancellablePromise<void> {
return $Call.ByID(2866399505, peerID, publicKey);
}
export function GetAutoAccept(): $CancellablePromise<boolean> {
return $Call.ByID(2605668438);
}
@@ -29,16 +33,30 @@ export function GetSavePath(): $CancellablePromise<string> {
return $Call.ByID(4081533263);
}
export function GetTrustedPeer(): $CancellablePromise<{ [_: string]: string }> {
return $Call.ByID(1253442080).then(($result: any) => {
return $$createType0($result);
});
}
export function GetVersion(): $CancellablePromise<string> {
return $Call.ByID(3578438023);
}
export function GetWindowState(): $CancellablePromise<$models.WindowState> {
return $Call.ByID(341414414).then(($result: any) => {
return $$createType0($result);
return $$createType1($result);
});
}
export function IsTrustedPeer(peerID: string): $CancellablePromise<boolean> {
return $Call.ByID(3452062706, peerID);
}
export function RemoveTrustedPeer(peerID: string): $CancellablePromise<void> {
return $Call.ByID(909233322, peerID);
}
/**
* Save 保存配置到磁盘
*/
@@ -70,4 +88,5 @@ export function SetWindowState(state: $models.WindowState): $CancellablePromise<
}
// Private type creation functions
const $$createType0 = $models.WindowState.createFrom;
const $$createType0 = $Create.Map($Create.Any, $Create.Any);
const $$createType1 = $models.WindowState.createFrom;

View File

@@ -47,6 +47,13 @@ export class Peer {
*/
"port": number;
"os": OS;
"pk": string;
/**
* TrustMismatch 指示该节点的公钥与本地信任列表中的公钥不匹配
* 如果为 true说明可能存在 ID 欺骗或密钥轮换
*/
"trust_mismatch": boolean;
/** Creates a new Peer instance. */
constructor($$source: Partial<Peer> = {}) {
@@ -65,6 +72,12 @@ export class Peer {
if (!("os" in $$source)) {
this["os"] = OS.$zero;
}
if (!("pk" in $$source)) {
this["pk"] = "";
}
if (!("trust_mismatch" in $$source)) {
this["trust_mismatch"] = false;
}
Object.assign(this, $$source);
}

View File

@@ -17,22 +17,29 @@ export function GetLocalIPInSameSubnet(receiverIP: string): $CancellablePromise<
return $Call.ByID(3089425954, receiverIP);
}
export function GetLocalIPs(): $CancellablePromise<[string[], boolean]> {
return $Call.ByID(2403939179).then(($result: any) => {
$result[0] = $$createType0($result[0]);
export function GetPeerByID(id: string): $CancellablePromise<[$models.Peer | null, boolean]> {
return $Call.ByID(1962377788, id).then(($result: any) => {
$result[0] = $$createType1($result[0]);
return $result;
});
}
export function GetPeerByIP(ip: string): $CancellablePromise<$models.Peer | null> {
export function GetPeerByIP(ip: string): $CancellablePromise<[$models.Peer | null, boolean]> {
return $Call.ByID(1626825408, ip).then(($result: any) => {
return $$createType2($result);
$result[0] = $$createType1($result[0]);
return $result;
});
}
export function GetPeers(): $CancellablePromise<$models.Peer[]> {
return $Call.ByID(3041084029).then(($result: any) => {
return $$createType3($result);
return $$createType2($result);
});
}
export function GetSelf(): $CancellablePromise<$models.Peer> {
return $Call.ByID(3599633538).then(($result: any) => {
return $$createType0($result);
});
}
@@ -41,7 +48,6 @@ export function Start(): $CancellablePromise<void> {
}
// Private type creation functions
const $$createType0 = $Create.Array($Create.Any);
const $$createType1 = $models.Peer.createFrom;
const $$createType2 = $Create.Nullable($$createType1);
const $$createType3 = $Create.Array($$createType1);
const $$createType0 = $models.Peer.createFrom;
const $$createType1 = $Create.Nullable($$createType0);
const $$createType2 = $Create.Array($$createType0);

View File

@@ -9,7 +9,6 @@ export {
export {
ContentType,
Progress,
Sender,
Transfer,
TransferStatus,
TransferType

View File

@@ -5,6 +5,10 @@
// @ts-ignore: Unused imports
import { Create as $Create } from "@wailsio/runtime";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as discovery$0 from "../discovery/models.js";
export enum ContentType {
/**
* The Go zero value for the underlying type of the enum.
@@ -59,46 +63,6 @@ export class Progress {
}
}
export class Sender {
/**
* 发送者 ID
*/
"id": string;
/**
* 发送者名称
*/
"name": string;
/**
* 发送者 IP
*/
"ip": string;
/** Creates a new Sender instance. */
constructor($$source: Partial<Sender> = {}) {
if (!("id" in $$source)) {
this["id"] = "";
}
if (!("name" in $$source)) {
this["name"] = "";
}
if (!("ip" in $$source)) {
this["ip"] = "";
}
Object.assign(this, $$source);
}
/**
* Creates a new Sender instance from a string or object.
*/
static createFrom($$source: any = {}): Sender {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new Sender($$parsedSource as Partial<Sender>);
}
}
/**
* Transfer
*/
@@ -116,7 +80,7 @@ export class Transfer {
/**
* 发送者
*/
"sender": Sender;
"sender": discovery$0.Peer;
/**
* 文件名
@@ -177,7 +141,7 @@ export class Transfer {
this["create_time"] = 0;
}
if (!("sender" in $$source)) {
this["sender"] = (new Sender());
this["sender"] = (new discovery$0.Peer());
}
if (!("file_name" in $$source)) {
this["file_name"] = "";
@@ -256,5 +220,5 @@ export enum TransferType {
};
// Private type creation functions
const $$createType0 = Sender.createFrom;
const $$createType0 = discovery$0.Peer.createFrom;
const $$createType1 = Progress.createFrom;

View File

@@ -66,12 +66,10 @@ onMounted(async () => {
// --- 后端集成 & 事件监听 ---
onMounted(async () => {
peers.value = await GetPeers();
peers.value = peers.value.sort((a, b) => a.name.localeCompare(b.name));
});
Events.On("peers:update", (event) => {
peers.value = event.data;
peers.value = peers.value.sort((a, b) => a.name.localeCompare(b.name));
});
Events.On("transfer:refreshList", async () => {

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
// --- Vue 核心 ---
import { computed, ref, watch } from "vue";
import { computed, ref, watch, onMounted } from "vue";
// --- 组件 ---
import FileSendModal from "./modals/FileSendModal.vue";
@@ -13,6 +13,20 @@ import {
SendText,
} from "../../bindings/mesh-drop/internal/transfer/service";
import { Peer } from "../../bindings/mesh-drop/internal/discovery/models";
import {
IsTrustedPeer,
AddTrustedPeer,
RemoveTrustedPeer,
} from "../../bindings/mesh-drop/internal/config/config";
// --- 生命周期 ---
onMounted(async () => {
try {
isTrusted.value = await IsTrustedPeer(props.peer.id);
} catch (err) {
console.error("Failed to check trusted peer status:", err);
}
});
// --- 属性 & 事件 ---
const props = defineProps<{
@@ -27,6 +41,7 @@ const emit = defineEmits<{
const selectedIp = ref<string>("");
const showFileModal = ref(false);
const showTextModal = ref(false);
const isTrusted = ref(false);
const sendOptions = [
{
@@ -136,10 +151,20 @@ const handleSendClipboard = async () => {
});
emit("transferStarted");
};
const handleTrust = () => {
AddTrustedPeer(props.peer.id, props.peer.pk);
isTrusted.value = true;
};
const handleUntrust = () => {
RemoveTrustedPeer(props.peer.id);
isTrusted.value = false;
};
</script>
<template>
<v-card hover link class="peer-card pa-2">
<v-card hover link class="peer-card pa-2" :ripple="false">
<template #title>
<div class="d-flex align-center">
<v-icon :icon="osIcon" size="24" class="mr-2"></v-icon>
@@ -187,12 +212,25 @@ const handleSendClipboard = async () => {
</div>
</template>
<template #actions>
<v-menu>
<v-card-actions>
<!-- Trust Mismatch Warning -->
<v-btn
v-if="peer.trust_mismatch"
class="flex-grow-1"
color="warning"
variant="tonal"
prepend-icon="mdi-alert"
:ripple="false"
style="pointer-events: none"
>
Mismatch
</v-btn>
<v-menu v-else>
<template #activator="{ props }">
<v-btn
v-bind="props"
block
class="flex-grow-1"
color="primary"
variant="tonal"
:disabled="ips.length === 0"
@@ -218,7 +256,32 @@ const handleSendClipboard = async () => {
</v-list-item>
</v-list>
</v-menu>
</template>
<!-- Trust Mismatch Reset Override -->
<v-btn
v-if="peer.trust_mismatch"
variant="tonal"
color="error"
@click="handleUntrust"
>
<v-icon icon="mdi-delete"></v-icon>
<v-tooltip activator="parent" location="bottom">Reset Trust</v-tooltip>
</v-btn>
<v-btn
v-else-if="!isTrusted"
variant="tonal"
color="primary"
@click="handleTrust"
>
<v-icon icon="mdi-star-outline"></v-icon>
<v-tooltip activator="parent" location="bottom">Trust peer</v-tooltip>
</v-btn>
<v-btn v-else variant="tonal" color="primary" @click="handleUntrust">
<v-icon icon="mdi-star"></v-icon>
<v-tooltip activator="parent" location="bottom">Untrust peer</v-tooltip>
</v-btn>
</v-card-actions>
</v-card>
<!-- Modals -->

View File

@@ -187,9 +187,23 @@ const handleCopy = async () => {
v-if="
props.transfer.sender.name && props.transfer.type === 'receive'
"
prepend-icon="mdi-account"
:color="
props.transfer.sender.trust_mismatch ? 'warning' : undefined
"
:prepend-icon="
props.transfer.sender.trust_mismatch
? 'mdi-alert'
: 'mdi-account'
"
>
{{ props.transfer.sender.name }}
<v-tooltip
v-if="props.transfer.sender.trust_mismatch"
activator="parent"
location="bottom"
>
Security Alert: Key Mismatch
</v-tooltip>
</v-chip>
<v-chip

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
// --- Vue 核心 ---
import { computed, ref } from "vue";
import { computed, ref, watch, nextTick } from "vue";
// --- Wails & 后端绑定 ---
import { SendText } from "../../../bindings/mesh-drop/internal/transfer/service";
@@ -20,6 +20,7 @@ const emit = defineEmits<{
// --- 状态 ---
const textContent = ref("");
const textareaRef = ref();
// --- 计算属性 ---
const show = computed({
@@ -27,6 +28,14 @@ const show = computed({
set: (value) => emit("update:modelValue", value),
});
// --- 监听 ---
watch(show, async (val) => {
if (val) {
await nextTick();
textareaRef.value?.focus();
}
});
// --- 方法 ---
const executeSendText = async () => {
if (!props.selectedIp || !textContent.value) return;
@@ -48,6 +57,7 @@ const executeSendText = async () => {
<v-card title="Send Text">
<v-card-text>
<v-textarea
ref="textareaRef"
v-model="textContent"
label="Content"
placeholder="Type something to send..."