From 6cdbe753ad2b6e918b8ddf944e7c33aa2406cb84 Mon Sep 17 00:00:00 2001 From: "smx.pusha" <139338836+smxpusha@users.noreply.github.com> Date: Fri, 14 Aug 2026 19:51:15 +0200 Subject: [PATCH] FIX - share phone contacts through EasyShare Load the server-canonical own contact instead of constructing an unsupported profile payload in the Phone app. Move canonical contact resolution before payload sanitization so self-contact transfers are validated and received correctly, with a regression contract test. --- .../src/views/apps/PhoneApp.contract.test.ts | 15 ++++++++ frontend/src/views/apps/PhoneApp.vue | 23 +++++------ sky_phone/source/server/easyshare.lua | 38 +++++++++---------- 3 files changed, 46 insertions(+), 30 deletions(-) create mode 100644 frontend/src/views/apps/PhoneApp.contract.test.ts diff --git a/frontend/src/views/apps/PhoneApp.contract.test.ts b/frontend/src/views/apps/PhoneApp.contract.test.ts new file mode 100644 index 0000000..ecc3409 --- /dev/null +++ b/frontend/src/views/apps/PhoneApp.contract.test.ts @@ -0,0 +1,15 @@ +import { readFileSync } from 'node:fs' + +import { describe, expect, it } from 'vitest' + +const source = readFileSync(new URL('./PhoneApp.vue', import.meta.url), 'utf8') + +describe('PhoneApp EasyShare contract', () => { + it('loads the server-canonical own contact instead of creating a profile payload', () => { + expect(source).toContain( + "nuiCall('easyshare:own-contact')", + ) + expect(source).toContain('easyShare.open(response.data)') + expect(source).not.toContain("kind: 'profile'") + }) +}) diff --git a/frontend/src/views/apps/PhoneApp.vue b/frontend/src/views/apps/PhoneApp.vue index 59b4d93..a3a4fc8 100644 --- a/frontend/src/views/apps/PhoneApp.vue +++ b/frontend/src/views/apps/PhoneApp.vue @@ -49,7 +49,9 @@ import { useEasyShareStore } from '@/stores/easyshare' import { useMessageMediaStore } from '@/stores/messageMedia' import { useMessagesStore } from '@/stores/messages' import { usePhoneStore } from '@/stores/phone' +import type { EasySharePayload } from '@/types/easyshare' import type { PhoneContact, RecentCall } from '@/types/phone' +import { nuiCall } from '@/utils/nui' import { formatPhoneNumber, normalizePhoneNumber } from '@/utils/phone' type PhoneTab = 'recents' | 'contacts' | 'keypad' @@ -526,17 +528,16 @@ function shareSelectedContact(): void { }) } -function shareOwnProfile(): void { - const number = phone.device?.sim?.number - if (!number) return - easyShare.open({ - appId: 'phone', - copyText: `${phone.t('Apps.phone.myCard')}\n${number}`, - kind: 'profile', - link: `skyphone://phone/${number}`, - subtitle: formatPhoneNumber(number), - title: phone.t('Apps.phone.myCard'), - }) +async function shareOwnProfile(): Promise { + const response = await nuiCall('easyshare:own-contact') + if (!response.success || !response.data) { + error.value = phone.t( + `Apps.easyShare.errors.${response.error ?? 'request_failed'}`, + ) + return + } + error.value = '' + easyShare.open(response.data) } function messageActiveCaller(): void { diff --git a/sky_phone/source/server/easyshare.lua b/sky_phone/source/server/easyshare.lua index 83f57f3..5617aec 100644 --- a/sky_phone/source/server/easyshare.lua +++ b/sky_phone/source/server/easyshare.lua @@ -598,6 +598,25 @@ local function nearby_targets(source, device) return targets end +local function canonical_own_contact(source, device) + if not device.phone_number then + return nil + end + local name = display_name(source) + return { + appId = "phone", + kind = "contact", + id = "self", + title = name, + subtitle = device.phone_number, + copyText = ("%s\n%s"):format(name, device.phone_number), + meta = { + name = name, + phoneNumber = device.phone_number, + }, + } +end + local function sanitize_payload(source, device, data) if type(data) ~= "table" or not valid_kinds[data.kind] then return nil, "invalid_payload" @@ -882,25 +901,6 @@ local function apply_received_payload(transfer) return true end -local function canonical_own_contact(source, device) - if not device.phone_number then - return nil - end - local name = display_name(source) - return { - appId = "phone", - kind = "contact", - id = "self", - title = name, - subtitle = device.phone_number, - copyText = ("%s\n%s"):format(name, device.phone_number), - meta = { - name = name, - phoneNumber = device.phone_number, - }, - } -end - local function advance_transfer(id) local transfer = active_transfers[id] if not transfer or transfer.status ~= "transferring" then