fix: use Android intent: URL format for Amber (not nostrsigner: directly)
The nostrsigner: scheme used directly caused "malformed request" in Amber when opened from a browser context. The correct format per Amber's README for web clients is the Android intent: URL format: intent:<payload>#Intent;scheme=nostrsigner;S.type=...;S.callbackUrl=...;end Updated all Amber operations (login, sign, nip04/nip44 encrypt/decrypt) to use buildIntentUrl() helper instead of raw nostrsigner: URLs. Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
This commit is contained in:
parent
9ffc961015
commit
c30546f700
1 changed files with 73 additions and 24 deletions
|
|
@ -300,24 +300,44 @@ function buildCallbackUrl(): string {
|
|||
/**
|
||||
* Navigate to an Amber nostrsigner: URL.
|
||||
*
|
||||
* Per NIP-55, web clients must navigate directly to the nostrsigner: URI
|
||||
* using window.location.href. This fires an Android intent that Amber
|
||||
* intercepts via its registered nostrsigner: scheme handler.
|
||||
* For web apps the correct Android intent format is:
|
||||
* intent:<payload>#Intent;scheme=nostrsigner;S.<key>=<value>;...;end
|
||||
*
|
||||
* DO NOT use window.open(..., '_blank') — that opens a new tab/window
|
||||
* instead of firing the intent, which is what caused "malformed request".
|
||||
* This is the format Amber's GitHub README documents for web clients.
|
||||
* The nostrsigner: URL format (without intent: wrapper) causes the
|
||||
* "malformed request" error because Android doesn't route it correctly
|
||||
* from a browser context.
|
||||
*
|
||||
* Reference: https://github.com/greenart7c3/Amber (Web Applications section)
|
||||
*/
|
||||
function openAmberUrl(url: string): void {
|
||||
window.location.href = url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an Android intent URL for Amber web usage.
|
||||
* Converts nostrsigner: params into the intent: format Amber expects from browsers.
|
||||
*
|
||||
* Format: intent:<payload>#Intent;scheme=nostrsigner;S.<key>=<val>;...;end
|
||||
*/
|
||||
function buildIntentUrl(payload: string, params: Record<string, string>): string {
|
||||
const extras = Object.entries(params)
|
||||
.map(([k, v]) => `S.${k}=${encodeURIComponent(v)}`)
|
||||
.join(';');
|
||||
return `intent:${payload}#Intent;scheme=nostrsigner;${extras};end`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open Amber for get_public_key WITHOUT a callback URL.
|
||||
* Used in PWA/Capacitor mode: Amber copies pubkey to clipboard,
|
||||
* the app reads it on visibilitychange.
|
||||
*/
|
||||
export function openAmberForLoginNoCallback(): void {
|
||||
const amberUrl = 'nostrsigner:?compressionType=none&returnType=signature&type=get_public_key';
|
||||
const amberUrl = buildIntentUrl('', {
|
||||
compressionType: 'none',
|
||||
returnType: 'signature',
|
||||
type: 'get_public_key',
|
||||
});
|
||||
openAmberUrl(amberUrl);
|
||||
}
|
||||
|
||||
|
|
@ -343,10 +363,15 @@ export function openAmberForLogin(): void {
|
|||
const currentPath = window.location.hash.replace(/^#/, '') || '/';
|
||||
localStorage.setItem('amber_return_path', currentPath);
|
||||
|
||||
// NIP-55 nostrsigner: URI — Amber opens, user approves, Amber calls callbackUrl
|
||||
const amberUrl = `nostrsigner:?compressionType=none&returnType=signature&type=get_public_key&callbackUrl=${encodeURIComponent(callbackUrl)}`;
|
||||
// Android intent format for web → Amber communication (per Amber README)
|
||||
const amberUrl = buildIntentUrl('', {
|
||||
compressionType: 'none',
|
||||
returnType: 'signature',
|
||||
type: 'get_public_key',
|
||||
callbackUrl: callbackUrl,
|
||||
});
|
||||
|
||||
console.log('[Amber] Opening Amber for login, callback:', callbackUrl);
|
||||
console.log('[Amber] Opening Amber for login, intent:', amberUrl);
|
||||
openAmberUrl(amberUrl);
|
||||
}
|
||||
|
||||
|
|
@ -386,9 +411,13 @@ export function openAmberForSignEvent(event: Omit<NostrEvent, 'id' | 'sig'>): vo
|
|||
timestamp: Date.now(),
|
||||
});
|
||||
|
||||
const encodedEvent = encodeURIComponent(eventJson);
|
||||
const amberUrl = `nostrsigner:${encodedEvent}?compressionType=none&returnType=event&type=sign_event&callbackUrl=${encodeURIComponent(callbackUrl)}`;
|
||||
|
||||
const amberUrl = buildIntentUrl(encodeURIComponent(eventJson), {
|
||||
compressionType: 'none',
|
||||
returnType: 'event',
|
||||
type: 'sign_event',
|
||||
callbackUrl: callbackUrl,
|
||||
});
|
||||
|
||||
openAmberUrl(amberUrl);
|
||||
}
|
||||
|
||||
|
|
@ -407,9 +436,14 @@ export function openAmberForNip04Encrypt(pubkey: string, plaintext: string): voi
|
|||
timestamp: Date.now(),
|
||||
});
|
||||
|
||||
const encodedText = encodeURIComponent(plaintext);
|
||||
const amberUrl = `nostrsigner:${encodedText}?pubkey=${pubkey}&compressionType=none&returnType=signature&type=nip04_encrypt&callbackUrl=${encodeURIComponent(callbackUrl)}`;
|
||||
|
||||
const amberUrl = buildIntentUrl(encodeURIComponent(plaintext), {
|
||||
pubKey: pubkey,
|
||||
compressionType: 'none',
|
||||
returnType: 'signature',
|
||||
type: 'nip04_encrypt',
|
||||
callbackUrl: callbackUrl,
|
||||
});
|
||||
|
||||
openAmberUrl(amberUrl);
|
||||
}
|
||||
|
||||
|
|
@ -428,9 +462,14 @@ export function openAmberForNip04Decrypt(pubkey: string, ciphertext: string): vo
|
|||
timestamp: Date.now(),
|
||||
});
|
||||
|
||||
const encodedText = encodeURIComponent(ciphertext);
|
||||
const amberUrl = `nostrsigner:${encodedText}?pubkey=${pubkey}&compressionType=none&returnType=signature&type=nip04_decrypt&callbackUrl=${encodeURIComponent(callbackUrl)}`;
|
||||
|
||||
const amberUrl = buildIntentUrl(encodeURIComponent(ciphertext), {
|
||||
pubKey: pubkey,
|
||||
compressionType: 'none',
|
||||
returnType: 'signature',
|
||||
type: 'nip04_decrypt',
|
||||
callbackUrl: callbackUrl,
|
||||
});
|
||||
|
||||
openAmberUrl(amberUrl);
|
||||
}
|
||||
|
||||
|
|
@ -449,9 +488,14 @@ export function openAmberForNip44Encrypt(pubkey: string, plaintext: string): voi
|
|||
timestamp: Date.now(),
|
||||
});
|
||||
|
||||
const encodedText = encodeURIComponent(plaintext);
|
||||
const amberUrl = `nostrsigner:${encodedText}?pubkey=${pubkey}&compressionType=none&returnType=signature&type=nip44_encrypt&callbackUrl=${encodeURIComponent(callbackUrl)}`;
|
||||
|
||||
const amberUrl = buildIntentUrl(encodeURIComponent(plaintext), {
|
||||
pubKey: pubkey,
|
||||
compressionType: 'none',
|
||||
returnType: 'signature',
|
||||
type: 'nip44_encrypt',
|
||||
callbackUrl: callbackUrl,
|
||||
});
|
||||
|
||||
openAmberUrl(amberUrl);
|
||||
}
|
||||
|
||||
|
|
@ -470,9 +514,14 @@ export function openAmberForNip44Decrypt(pubkey: string, ciphertext: string): vo
|
|||
timestamp: Date.now(),
|
||||
});
|
||||
|
||||
const encodedText = encodeURIComponent(ciphertext);
|
||||
const amberUrl = `nostrsigner:${encodedText}?pubkey=${pubkey}&compressionType=none&returnType=signature&type=nip44_decrypt&callbackUrl=${encodeURIComponent(callbackUrl)}`;
|
||||
|
||||
const amberUrl = buildIntentUrl(encodeURIComponent(ciphertext), {
|
||||
pubKey: pubkey,
|
||||
compressionType: 'none',
|
||||
returnType: 'signature',
|
||||
type: 'nip44_decrypt',
|
||||
callbackUrl: callbackUrl,
|
||||
});
|
||||
|
||||
openAmberUrl(amberUrl);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue