diff --git a/src/hooks/useUploadFile.ts b/src/hooks/useUploadFile.ts
index a3c9793..6738164 100644
--- a/src/hooks/useUploadFile.ts
+++ b/src/hooks/useUploadFile.ts
@@ -13,8 +13,13 @@ export function useUploadFile() {
}
const uploader = new BlossomUploader({
+ // Multiple servers so a single slow/down Blossom host doesn't hang the
+ // upload (crew logo uploads were stalling when primal.net was slow).
+ // The uploader tries them in order and falls back on failure.
servers: [
'https://blossom.primal.net/',
+ 'https://blossom.band/',
+ 'https://cdn.satellite.earth/',
],
signer: user.signer,
});
diff --git a/src/lib/amber.ts b/src/lib/amber.ts
index ae4184f..dd1a51b 100644
--- a/src/lib/amber.ts
+++ b/src/lib/amber.ts
@@ -604,11 +604,17 @@ export class AmberSigner implements NostrSigner {
return this.pubkey;
}
- async sign(event: Omit): Promise {
+ // NOTE: Nostrify's NostrSigner interface requires `signEvent` (not `sign`).
+ // The old method was named `sign`, so every `user.signer.signEvent(...)` call
+ // on a web/PWA Amber login threw "signer.signEvent is not a function". This
+ // now correctly implements the interface. On web, signing genuinely requires
+ // a redirect round-trip to Amber, so this navigates away and the result is
+ // processed on return by AmberCallbackPage.
+ async signEvent(event: Omit): Promise {
// Store the current state and redirect to Amber
// The result will be processed on page reload
openAmberForSignEvent(event);
-
+
// This promise will never resolve in the current page context
// because we're redirecting to Amber
return new Promise(() => {
diff --git a/src/lib/capacitorAmberSigner.ts b/src/lib/capacitorAmberSigner.ts
index 8533870..b54d0c9 100644
--- a/src/lib/capacitorAmberSigner.ts
+++ b/src/lib/capacitorAmberSigner.ts
@@ -202,7 +202,7 @@ export class CapacitorAmberSigner implements NostrSigner {
async signEvent(event: Omit): Promise {
const plugin = await getPlugin();
- const { event: signedJson } = await withTimeout(
+ const res = await withTimeout(
plugin.signEvent({
packageName: AMBER_PACKAGE,
eventJson: JSON.stringify(event),
@@ -211,7 +211,54 @@ export class CapacitorAmberSigner implements NostrSigner {
}),
'signature',
);
- return JSON.parse(signedJson) as NostrEvent;
+
+ // Amber's response shape varies across builds: some return the full signed
+ // event JSON in `event`, others return only `signature` (expecting the caller
+ // to attach it to the original event). If we blindly `JSON.parse` an empty
+ // `event`, or return an event with no `sig`, the resulting object silently
+ // publishes as a malformed event and stalls the relay forever — the exact
+ // "publish hangs" symptom. Handle every shape defensively and always fail
+ // loud rather than emitting a bad event.
+ let signed: Partial | null = null;
+
+ if (res.event && res.event.trim()) {
+ try {
+ signed = JSON.parse(res.event) as Partial;
+ } catch {
+ signed = null;
+ }
+ }
+
+ // Fall back to reconstructing the event from a bare signature.
+ if ((!signed || !signed.sig) && res.signature) {
+ signed = { ...(signed ?? {}), sig: res.signature };
+ }
+
+ if (!signed || !signed.sig) {
+ throw new Error(
+ 'Amber returned an incomplete signature. Open Amber, approve the request, then try again.',
+ );
+ }
+
+ // Merge Amber's response over the original event so required fields are
+ // never lost, then verify the essentials are present before returning.
+ const merged: NostrEvent = {
+ kind: signed.kind ?? event.kind,
+ content: signed.content ?? event.content,
+ tags: signed.tags ?? event.tags,
+ created_at: signed.created_at ?? event.created_at,
+ pubkey: signed.pubkey ?? this.pubkey,
+ id: signed.id ?? '',
+ sig: signed.sig,
+ };
+
+ if (!merged.id || !merged.sig || !merged.pubkey) {
+ throw new Error(
+ 'Amber returned a malformed event. Please retry, or restart Amber if it persists.',
+ );
+ }
+
+ return merged;
}
readonly nip04 = {
diff --git a/src/pages/CreateTourPage.tsx b/src/pages/CreateTourPage.tsx
index 7b431a0..60091f3 100644
--- a/src/pages/CreateTourPage.tsx
+++ b/src/pages/CreateTourPage.tsx
@@ -14,6 +14,7 @@ import { useNostrPublish } from '@/hooks/useNostrPublish';
import { useCurrentUser } from '@/hooks/useCurrentUser';
import { useUploadFile } from '@/hooks/useUploadFile';
import { useShops } from '@/hooks/useShops';
+import { sanitizeUrl } from '@/lib/sanitizeUrl';
import { useToast } from '@/hooks/useToast';
import { LoginArea } from '@/components/auth/LoginArea';
import { FeaturedUpgrade } from '@/components/FeaturedEventUpgrade';
@@ -1811,9 +1812,9 @@ export default function CreateTourPage() {
)}