fix: revert to nostrsigner: format, fix double-encoding of callbackUrl
Root cause of "malformed request": - buildIntentUrl() was calling encodeURIComponent() on ALL values including callbackUrl, which was ALREADY a full URL with special chars - This double-encoded the callbackUrl, arriving at Amber as garbage - The intent: wrapper format was also unnecessary Fix: back to clean nostrsigner: format per NIP-55 spec - Only callbackUrl gets encodeURIComponent (once) - All other params are plain query string values - Matches exactly what NIP-55 spec shows in its example code Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
This commit is contained in:
parent
0b6af1bcdf
commit
6f303d1956
3 changed files with 40 additions and 36 deletions
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "skate-spots",
|
||||
"private": true,
|
||||
"version": "1.0.28",
|
||||
"version": "1.0.29",
|
||||
"type": "module",
|
||||
"homepage": "https://skate-spots.com",
|
||||
"scripts": {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
v1.0.28 — Amber login fixed for real
|
||||
v1.0.29 — Amber login fixed (properly this time)
|
||||
|
||||
- Fixed "malformed request" error when logging in with Amber (NIP-55)
|
||||
- Switched to correct Android intent: URL format that browsers require
|
||||
- Previous versions used nostrsigner: directly which only works for native apps, not web/PWA
|
||||
- Root cause found: callbackUrl was being double-encoded inside the URL builder, arriving garbled at Amber and causing "malformed request"
|
||||
- Reverted to clean nostrsigner: format per NIP-55 spec with only callbackUrl encoded once
|
||||
|
|
|
|||
|
|
@ -300,31 +300,36 @@ function buildCallbackUrl(): string {
|
|||
/**
|
||||
* Navigate to an Amber nostrsigner: URL.
|
||||
*
|
||||
* For web apps the correct Android intent format is:
|
||||
* intent:<payload>#Intent;scheme=nostrsigner;S.<key>=<value>;...;end
|
||||
* Per NIP-55 spec and Amber README, web clients use window.location.href
|
||||
* with a nostrsigner: URI. The params go in the query string unencoded
|
||||
* (except the callbackUrl which must be encodeURIComponent'd so Amber
|
||||
* can parse it as a single value).
|
||||
*
|
||||
* 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)
|
||||
* Correct format per NIP-55 spec:
|
||||
* nostrsigner:?type=get_public_key&compressionType=none&returnType=signature&callbackUrl=https://.../?event=
|
||||
*/
|
||||
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.
|
||||
* Build a nostrsigner: URL for Amber web usage.
|
||||
*
|
||||
* Format: intent:<payload>#Intent;scheme=nostrsigner;S.<key>=<val>;...;end
|
||||
* Per NIP-55 spec the format is:
|
||||
* nostrsigner:<payload>?type=<method>&compressionType=none&returnType=signature&callbackUrl=<encoded>
|
||||
*
|
||||
* The callbackUrl is the ONLY value that needs encodeURIComponent.
|
||||
* All other params are plain query string values.
|
||||
*/
|
||||
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`;
|
||||
function buildAmberUrl(payload: string, params: Record<string, string>): string {
|
||||
const query = Object.entries(params)
|
||||
.map(([k, v]) => {
|
||||
// callbackUrl must be encoded so Amber treats it as a single value
|
||||
if (k === 'callbackUrl') return `${k}=${encodeURIComponent(v)}`;
|
||||
return `${k}=${v}`;
|
||||
})
|
||||
.join('&');
|
||||
return `nostrsigner:${payload}?${query}`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -333,10 +338,10 @@ function buildIntentUrl(payload: string, params: Record<string, string>): string
|
|||
* the app reads it on visibilitychange.
|
||||
*/
|
||||
export function openAmberForLoginNoCallback(): void {
|
||||
const amberUrl = buildIntentUrl('', {
|
||||
const amberUrl = buildAmberUrl('', {
|
||||
type: 'get_public_key',
|
||||
compressionType: 'none',
|
||||
returnType: 'signature',
|
||||
type: 'get_public_key',
|
||||
});
|
||||
openAmberUrl(amberUrl);
|
||||
}
|
||||
|
|
@ -363,11 +368,11 @@ export function openAmberForLogin(): void {
|
|||
const currentPath = window.location.hash.replace(/^#/, '') || '/';
|
||||
localStorage.setItem('amber_return_path', currentPath);
|
||||
|
||||
// Android intent format for web → Amber communication (per Amber README)
|
||||
const amberUrl = buildIntentUrl('', {
|
||||
// NIP-55 nostrsigner: URL — Amber opens, user approves, Amber calls callbackUrl
|
||||
const amberUrl = buildAmberUrl('', {
|
||||
type: 'get_public_key',
|
||||
compressionType: 'none',
|
||||
returnType: 'signature',
|
||||
type: 'get_public_key',
|
||||
callbackUrl: callbackUrl,
|
||||
});
|
||||
|
||||
|
|
@ -411,10 +416,10 @@ export function openAmberForSignEvent(event: Omit<NostrEvent, 'id' | 'sig'>): vo
|
|||
timestamp: Date.now(),
|
||||
});
|
||||
|
||||
const amberUrl = buildIntentUrl(encodeURIComponent(eventJson), {
|
||||
const amberUrl = buildAmberUrl(encodeURIComponent(eventJson), {
|
||||
type: 'sign_event',
|
||||
compressionType: 'none',
|
||||
returnType: 'event',
|
||||
type: 'sign_event',
|
||||
callbackUrl: callbackUrl,
|
||||
});
|
||||
|
||||
|
|
@ -436,11 +441,11 @@ export function openAmberForNip04Encrypt(pubkey: string, plaintext: string): voi
|
|||
timestamp: Date.now(),
|
||||
});
|
||||
|
||||
const amberUrl = buildIntentUrl(encodeURIComponent(plaintext), {
|
||||
const amberUrl = buildAmberUrl(encodeURIComponent(plaintext), {
|
||||
type: 'nip04_encrypt',
|
||||
pubKey: pubkey,
|
||||
compressionType: 'none',
|
||||
returnType: 'signature',
|
||||
type: 'nip04_encrypt',
|
||||
callbackUrl: callbackUrl,
|
||||
});
|
||||
|
||||
|
|
@ -462,11 +467,11 @@ export function openAmberForNip04Decrypt(pubkey: string, ciphertext: string): vo
|
|||
timestamp: Date.now(),
|
||||
});
|
||||
|
||||
const amberUrl = buildIntentUrl(encodeURIComponent(ciphertext), {
|
||||
const amberUrl = buildAmberUrl(encodeURIComponent(ciphertext), {
|
||||
type: 'nip04_decrypt',
|
||||
pubKey: pubkey,
|
||||
compressionType: 'none',
|
||||
returnType: 'signature',
|
||||
type: 'nip04_decrypt',
|
||||
callbackUrl: callbackUrl,
|
||||
});
|
||||
|
||||
|
|
@ -488,11 +493,11 @@ export function openAmberForNip44Encrypt(pubkey: string, plaintext: string): voi
|
|||
timestamp: Date.now(),
|
||||
});
|
||||
|
||||
const amberUrl = buildIntentUrl(encodeURIComponent(plaintext), {
|
||||
const amberUrl = buildAmberUrl(encodeURIComponent(plaintext), {
|
||||
type: 'nip44_encrypt',
|
||||
pubKey: pubkey,
|
||||
compressionType: 'none',
|
||||
returnType: 'signature',
|
||||
type: 'nip44_encrypt',
|
||||
callbackUrl: callbackUrl,
|
||||
});
|
||||
|
||||
|
|
@ -514,11 +519,11 @@ export function openAmberForNip44Decrypt(pubkey: string, ciphertext: string): vo
|
|||
timestamp: Date.now(),
|
||||
});
|
||||
|
||||
const amberUrl = buildIntentUrl(encodeURIComponent(ciphertext), {
|
||||
const amberUrl = buildAmberUrl(encodeURIComponent(ciphertext), {
|
||||
type: 'nip44_decrypt',
|
||||
pubKey: pubkey,
|
||||
compressionType: 'none',
|
||||
returnType: 'signature',
|
||||
type: 'nip44_decrypt',
|
||||
callbackUrl: callbackUrl,
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue