Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/static/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,7 @@
"scan_error_reveal_failed": "Failed to reveal vote",
"scan_error_sign_failed": "Failed to sign. Please try again.",
"scan_error_social_create": "Failed to create social binding.",
"scan_error_social_ename_not_found": "This eID could not be found. Please check the QR code and try again.",
"scan_error_social_no_vault": "No active vault",
"scan_error_social_parse": "Failed to process social binding request.",
"scan_error_social_self": "You cannot create a social binding with yourself.",
Expand Down Expand Up @@ -526,6 +527,7 @@
"social_drawer_success_title": "Binding Complete!",
"social_drawer_they_said": "They said",
"social_drawer_your_contact": "Your contact",
"social_ename_not_found": "This eID could not be found.",
"social_role_received": "Received",
"social_role_sent": "Sent",
"social_role_sent_received": "Sent & Received",
Expand Down Expand Up @@ -989,6 +991,7 @@
"scan_error_reveal_failed": "Не удалось раскрыть голос",
"scan_error_sign_failed": "Не удалось подписать. Попробуйте ещё раз.",
"scan_error_social_create": "Не удалось создать социальную связь.",
"scan_error_social_ename_not_found": "Этот eID не найден. Проверьте QR-код и попробуйте снова.",
"scan_error_social_no_vault": "Активное хранилище отсутствует",
"scan_error_social_parse": "Не удалось обработать запрос на социальную связь.",
"scan_error_social_self": "Нельзя создать социальную связь с самим собой.",
Expand Down Expand Up @@ -1066,6 +1069,7 @@
"social_drawer_success_title": "Связь установлена!",
"social_drawer_they_said": "Комментарий",
"social_drawer_your_contact": "Ваш контакт",
"social_ename_not_found": "Этот eID не найден.",
"social_role_received": "Получено",
"social_role_sent": "Отправлено",
"social_role_sent_received": "Отправлено и получено",
Expand Down Expand Up @@ -1529,6 +1533,7 @@
"scan_error_reveal_failed": "Не вдалося розкрити голос",
"scan_error_sign_failed": "Не вдалося підписати. Спробуйте ще раз.",
"scan_error_social_create": "Не вдалося створити соціальний зв’язок.",
"scan_error_social_ename_not_found": "Цей eID не знайдено. Перевірте QR-код і спробуйте ще раз.",
"scan_error_social_no_vault": "Активного сховища немає",
"scan_error_social_parse": "Не вдалося обробити запит на соціальний зв’язок.",
"scan_error_social_self": "Не можна створити соціальний зв’язок із самим собою.",
Expand Down Expand Up @@ -1606,6 +1611,7 @@
"social_drawer_success_title": "Зв’язок встановлено!",
"social_drawer_they_said": "Коментар",
"social_drawer_your_contact": "Ваш контакт",
"social_ename_not_found": "Цей eID не знайдено.",
"social_role_received": "Отримано",
"social_role_sent": "Надіслано",
"social_role_sent_received": "Надіслано та отримано",
Expand Down
2 changes: 2 additions & 0 deletions infrastructure/eid-wallet/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,7 @@
"scan_error_reveal_failed": "Failed to reveal vote",
"scan_error_sign_failed": "Failed to sign. Please try again.",
"scan_error_social_create": "Failed to create social binding.",
"scan_error_social_ename_not_found": "This eID could not be found. Please check the QR code and try again.",
"scan_error_social_no_vault": "No active vault",
"scan_error_social_parse": "Failed to process social binding request.",
"scan_error_social_self": "You cannot create a social binding with yourself.",
Expand Down Expand Up @@ -676,6 +677,7 @@
"social_drawer_success_title": "Binding Complete!",
"social_drawer_they_said": "They said",
"social_drawer_your_contact": "Your contact",
"social_ename_not_found": "This eID could not be found.",
"social_role_received": "Received",
"social_role_sent": "Sent",
"social_role_sent_received": "Sent & Received",
Expand Down
2 changes: 2 additions & 0 deletions infrastructure/eid-wallet/messages/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,7 @@
"scan_error_reveal_failed": "Не удалось раскрыть голос",
"scan_error_sign_failed": "Не удалось подписать. Попробуйте ещё раз.",
"scan_error_social_create": "Не удалось создать социальную связь.",
"scan_error_social_ename_not_found": "Этот eID не найден. Проверьте QR-код и попробуйте снова.",
"scan_error_social_no_vault": "Активное хранилище отсутствует",
"scan_error_social_parse": "Не удалось обработать запрос на социальную связь.",
"scan_error_social_self": "Нельзя создать социальную связь с самим собой.",
Expand Down Expand Up @@ -696,6 +697,7 @@
"social_drawer_success_title": "Связь установлена!",
"social_drawer_they_said": "Комментарий",
"social_drawer_your_contact": "Ваш контакт",
"social_ename_not_found": "Этот eID не найден.",
"social_role_received": "Получено",
"social_role_sent": "Отправлено",
"social_role_sent_received": "Отправлено и получено",
Expand Down
2 changes: 2 additions & 0 deletions infrastructure/eid-wallet/messages/uk.json
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,7 @@
"scan_error_reveal_failed": "Не вдалося розкрити голос",
"scan_error_sign_failed": "Не вдалося підписати. Спробуйте ще раз.",
"scan_error_social_create": "Не вдалося створити соціальний зв’язок.",
"scan_error_social_ename_not_found": "Цей eID не знайдено. Перевірте QR-код і спробуйте ще раз.",
"scan_error_social_no_vault": "Активного сховища немає",
"scan_error_social_parse": "Не вдалося обробити запит на соціальний зв’язок.",
"scan_error_social_self": "Не можна створити соціальний зв’язок із самим собою.",
Expand Down Expand Up @@ -696,6 +697,7 @@
"social_drawer_success_title": "Зв’язок встановлено!",
"social_drawer_they_said": "Коментар",
"social_drawer_your_contact": "Ваш контакт",
"social_ename_not_found": "Цей eID не знайдено.",
"social_role_received": "Отримано",
"social_role_sent": "Надіслано",
"social_role_sent_received": "Надіслано та отримано",
Expand Down
50 changes: 50 additions & 0 deletions infrastructure/eid-wallet/src/lib/utils/socialBinding.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@ vi.mock("$env/static/public", () => ({

import {
CANCEL_NOT_PENDING,
ENAME_NOT_FOUND,
REGISTRY_UNAVAILABLE,
acceptSocialBinding,
cancelSentSocialBinding,
declineSocialBinding,
fetchReconciledSocialBindings,
fetchSocialBindings,
fetchUnsignedSocialDocs,
resolveVaultUri,
} from "./socialBinding";

const ME = "@me";
Expand Down Expand Up @@ -436,3 +439,50 @@ describe("fetchSocialBindings", () => {
expect(summary.parsed.data.relation_description).toBe("hi");
});
});

describe("resolveVaultUri", () => {
beforeEach(() => {
vi.spyOn(console, "error").mockImplementation(() => {});
});

afterEach(() => {
vi.mocked(console.error).mockRestore();
});

// The throw reaches a user-facing error box, so it carries a code the
// caller can translate and nothing the registry said.
it("reports an unknown eName as a code, not as a status line", async () => {
vi.stubGlobal(
"fetch",
vi.fn(async () => ({ ok: false, status: 404 }) as Response),
);

await expect(resolveVaultUri("@nobody")).rejects.toThrow(
ENAME_NOT_FOUND,
);
});

it("does not report a registry outage as an unknown eName", async () => {
vi.stubGlobal(
"fetch",
vi.fn(async () => ({ ok: false, status: 503 }) as Response),
);

await expect(resolveVaultUri("@alice")).rejects.toMatchObject({
message: REGISTRY_UNAVAILABLE,
});
});

it("reports a registry answer without a URI the same way", async () => {
vi.stubGlobal(
"fetch",
vi.fn(
async () => ({ ok: true, json: async () => ({}) }) as Response,
),
);

await expect(resolveVaultUri("@nobody")).rejects.toThrow(
ENAME_NOT_FOUND,
);
});
});
26 changes: 22 additions & 4 deletions infrastructure/eid-wallet/src/lib/utils/socialBinding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,16 @@ export interface BindingDocEdge {
// Registry resolution
// ---------------------------------------------------------------------------

/**
* The registry has no eVault for this eName, as a code rather than a sentence:
* this module has no i18n, so the caller renders the wording. The status and
* the eName stay in the console.
*/
export const ENAME_NOT_FOUND = "social-binding/ename-not-found";

/** The registry failed for another reason (5xx, a proxy error): no verdict on the eName. */
export const REGISTRY_UNAVAILABLE = "social-binding/registry-unavailable";

/**
* Resolve an eName to its eVault GraphQL endpoint via the registry.
*/
Expand All @@ -40,13 +50,21 @@ export async function resolveVaultUri(ename: string): Promise<string> {
PUBLIC_REGISTRY_URL,
);
const res = await fetch(url.toString(), { method: "GET" });
if (!res.ok)
if (!res.ok) {
console.error(
`[socialBinding] registry could not resolve ${normalized}: ${res.status}`,
);
throw new Error(
`Registry could not resolve ${normalized}: ${res.status}`,
res.status === 404 ? ENAME_NOT_FOUND : REGISTRY_UNAVAILABLE,
);
}
const json = await res.json();
if (!json?.uri)
throw new Error(`Registry returned no URI for ${normalized}`);
if (!json?.uri) {
console.error(
`[socialBinding] registry returned no URI for ${normalized}`,
);
throw new Error(ENAME_NOT_FOUND);
}
const base = json.uri as string;
return base.endsWith("/graphql")
? base
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { getLocale } from "$lib/paraglide/runtime";
import { BottomSheet, ButtonAction } from "$lib/ui";
import {
CANCEL_NOT_PENDING,
ENAME_NOT_FOUND,
REGISTRY_UNAVAILABLE,
type SocialBindingSummary,
acceptSocialBinding,
cancelSentSocialBinding,
Expand Down Expand Up @@ -96,12 +98,15 @@ async function callerContext(): Promise<CallerContext> {

/**
* socialBinding.ts carries no i18n so it stays testable without the app's module
* aliases; its one user-facing refusal arrives as a code and is worded here.
* aliases; its user-facing refusals arrive as codes and are worded here.
*/
function messageFor(err: Error): string {
return err.message === CANCEL_NOT_PENDING
? m.social_cancel_not_pending()
: err.message;
if (err.message === CANCEL_NOT_PENDING)
return m.social_cancel_not_pending();
if (err.message === ENAME_NOT_FOUND) return m.social_ename_not_found();
if (err.message === REGISTRY_UNAVAILABLE)
return m.social_drawer_error_generic();
return err.message;
}

async function runAction(
Expand Down Expand Up @@ -186,24 +191,31 @@ function cancel(binding: SocialBindingSummary) {
binding.role === "received" && !binding.mutuallySigned}
{@const awaitingThem =
binding.role === "sent" && !binding.mutuallySigned}
<!-- The badge above already names the role. Repeat it per entry
only when it differs between entries, or when it prefixes
an awaiting note. -->
{@const showRole =
contact.role === "both" || !binding.mutuallySigned}
<div
class="flex flex-col gap-3 rounded-2xl bg-card-alternative px-4 py-3"
>
<div class="flex-1 min-w-0">
<p class="font-semibold text-black-900 text-sm">
{binding.role === "sent"
? m.social_role_sent()
: m.social_role_received()}
{#if needsMyConfirmation}
<span class="font-normal text-amber-600"
>{m.social_details_awaiting_you_suffix()}</span
>
{:else if awaitingThem}
<span class="font-normal text-amber-600"
>{m.social_details_awaiting_suffix()}</span
>
{/if}
</p>
{#if showRole}
<p class="font-semibold text-black-900 text-sm">
{binding.role === "sent"
? m.social_role_sent()
: m.social_role_received()}
{#if needsMyConfirmation}
<span class="font-normal text-amber-600"
>{m.social_details_awaiting_you_suffix()}</span
>
{:else if awaitingThem}
<span class="font-normal text-amber-600"
>{m.social_details_awaiting_suffix()}</span
>
{/if}
</p>
{/if}
{#if binding.relationDescription}
<p
class="text-sm text-black-700 mt-0.5 leading-snug"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import { authenticate } from "wallet-sdk";

import type { GlobalState } from "$lib/global";
import {
ENAME_NOT_FOUND,
REGISTRY_UNAVAILABLE,
createOwnSocialBindingMirror,
createSocialConnectionDoc,
fetchNameFromVault,
Expand Down Expand Up @@ -706,6 +708,18 @@ export function createScanLogic({
}
}

/**
* socialBinding.ts carries no i18n; its user-facing refusals arrive as
* codes and are worded here.
*/
function socialBindingMessageFor(err: Error): string {
if (err.message === ENAME_NOT_FOUND)
return m.scan_error_social_ename_not_found();
if (err.message === REGISTRY_UNAVAILABLE)
return m.scan_error_social_create();
return err.message;
}

async function handleSocialBinding() {
const requesterEname = get(socialBindingRequesterEname);
if (!requesterEname) return;
Expand Down Expand Up @@ -820,7 +834,7 @@ export function createScanLogic({
console.error("[SocialBinding] failed:", err);
socialBindingError.set(
err instanceof Error
? err.message
? socialBindingMessageFor(err)
: m.scan_error_social_create(),
);
} finally {
Expand Down
Loading