feat: trust peer
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -9,7 +9,6 @@ export {
|
||||
export {
|
||||
ContentType,
|
||||
Progress,
|
||||
Sender,
|
||||
Transfer,
|
||||
TransferStatus,
|
||||
TransferType
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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..."
|
||||
|
||||
Reference in New Issue
Block a user