Fix Amber "Waiting…" hang on GrapheneOS: unblock setPackageName + auto-inject <queries> (v1.1.9)
Two concrete root causes for the eternal "Waiting for Amber…": 1. setPackageName could hang forever on GrapheneOS. getPlugin() awaited it with no timeout, so getPublicKey() (and its 90s timeout) was never even reached. Now setPackageName races a 3s timeout and continues regardless — the Amber package is passed explicitly on every call anyway, so it's non-essential. 2. AndroidManifest.xml had no <queries> block. On Android 11+/GrapheneOS the OS silently blocks the nostrsigner intent without it, so Amber never opens. Added scripts/inject-amber-queries.mjs (idempotent) and wired it into android:init / android:sync / android:build so the block is guaranteed present in every APK, surviving cap sync regeneration. Bump version to 1.1.9. Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
This commit is contained in:
parent
67597dc767
commit
07699592da
3 changed files with 81 additions and 16 deletions
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "skate-spots",
|
||||
"private": true,
|
||||
"version": "1.1.8",
|
||||
"version": "1.1.9",
|
||||
"type": "module",
|
||||
"homepage": "https://skate-spots.com",
|
||||
"scripts": {
|
||||
|
|
@ -9,11 +9,11 @@
|
|||
"build": "npm i --silent && vite build -l error && cp dist/index.html dist/404.html && echo 'Project built successfully!'",
|
||||
"test": "npm i --silent && tsc --noEmit && eslint && vitest run --reporter=dot --silent && vite build -l error && cp dist/index.html dist/404.html && echo 'All tests passed!'",
|
||||
"deploy": "npm i --silent && npm run build && npx -y nostr-deploy-cli deploy --skip-setup",
|
||||
"android:init": "npm run android:build:apk && npx cap add android && npx cap sync android",
|
||||
"android:sync": "npm run android:build:apk && npx cap sync android && node scripts/sync-android-version.mjs",
|
||||
"android:init": "npm run android:build:apk && npx cap add android && npx cap sync android && node scripts/inject-amber-queries.mjs",
|
||||
"android:sync": "npm run android:build:apk && npx cap sync android && node scripts/sync-android-version.mjs && node scripts/inject-amber-queries.mjs",
|
||||
"android:open": "npx cap open android",
|
||||
"android:version": "node scripts/sync-android-version.mjs",
|
||||
"android:build": "npm run android:build:apk && npx cap sync android && node scripts/sync-android-version.mjs && cd android && ./gradlew assembleRelease",
|
||||
"android:build": "npm run android:build:apk && npx cap sync android && node scripts/sync-android-version.mjs && node scripts/inject-amber-queries.mjs && cd android && ./gradlew assembleRelease",
|
||||
"android:build:apk": "CAPACITOR_BUILD=1 vite build && sed -i 's|<script type=\"module\" src=\"/shakespeare_tailwind[^\"]*\\.js\"></script>||g' dist/index.html && sed -i 's|<style type=\"text/tailwindcss\">|<style>|g' dist/index.html && cp dist/index.html dist/404.html"
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
|
|||
59
scripts/inject-amber-queries.mjs
Normal file
59
scripts/inject-amber-queries.mjs
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* Inject the Amber (NIP-55) <queries> block into the Android manifest.
|
||||
*
|
||||
* WHY THIS EXISTS
|
||||
* ───────────────
|
||||
* On Android 11+ (and GrapheneOS enforces this strictly), an app can only see
|
||||
* or launch another app if it declares a matching <queries> entry. Without it,
|
||||
* the nostrsigner intent that opens Amber is SILENTLY blocked by the OS and the
|
||||
* native signer call hangs forever — the classic "Waiting for Amber…" bug.
|
||||
*
|
||||
* Capacitor regenerates android/app/src/main/AndroidManifest.xml on `cap add`
|
||||
* and can touch it on sync, so hand-editing is fragile. This script is
|
||||
* idempotent and runs automatically as part of the android build, guaranteeing
|
||||
* the block is always present in the APK we ship.
|
||||
*
|
||||
* If android/ hasn't been generated yet (fresh checkout), it exits quietly.
|
||||
*/
|
||||
import { readFileSync, writeFileSync, existsSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname, join } from 'node:path';
|
||||
|
||||
const root = join(dirname(fileURLToPath(import.meta.url)), '..');
|
||||
const manifestPath = join(root, 'android', 'app', 'src', 'main', 'AndroidManifest.xml');
|
||||
|
||||
if (!existsSync(manifestPath)) {
|
||||
console.log('[inject-amber-queries] android/ not generated yet — skipping.');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
let manifest = readFileSync(manifestPath, 'utf8');
|
||||
|
||||
if (manifest.includes('android:scheme="nostrsigner"')) {
|
||||
console.log('[inject-amber-queries] <queries> already present — nothing to do.');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const queriesBlock = ` <!-- REQUIRED for Amber (NIP-55) on Android 11+ / GrapheneOS.
|
||||
Without this the OS blocks the nostrsigner intent and the signer call
|
||||
hangs forever ("Waiting for Amber"). Injected by scripts/inject-amber-queries.mjs -->
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<data android:scheme="nostrsigner" />
|
||||
</intent>
|
||||
<package android:name="com.greenart7c3.nostrsigner" />
|
||||
</queries>
|
||||
`;
|
||||
|
||||
// Insert right before the closing </manifest> tag.
|
||||
if (!manifest.includes('</manifest>')) {
|
||||
console.error('[inject-amber-queries] No closing </manifest> tag found — aborting.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
manifest = manifest.replace('</manifest>', `${queriesBlock}</manifest>`);
|
||||
writeFileSync(manifestPath, manifest);
|
||||
|
||||
console.log('[inject-amber-queries] ✓ Injected Amber <queries> block into AndroidManifest.xml');
|
||||
|
|
@ -105,19 +105,25 @@ function getNativeSigner(): NostrSignerNative | null {
|
|||
// every sign/encrypt op is unnecessary bridge chatter and, on some builds, a
|
||||
// repeated setPackageName can itself wedge. We cache the promise so concurrent
|
||||
// callers await the same setup.
|
||||
let packageNameSet: Promise<void> | null = null;
|
||||
function ensurePackageName(plugin: NostrSignerNative): Promise<void> {
|
||||
if (!packageNameSet) {
|
||||
packageNameSet = plugin
|
||||
.setPackageName({ packageName: AMBER_PACKAGE })
|
||||
.catch((e) => {
|
||||
// setPackageName can be a no-op that throws on some builds; log but
|
||||
// don't block signing, and don't cache the failure.
|
||||
console.warn('[CapacitorAmberSigner] setPackageName warning:', e);
|
||||
packageNameSet = null;
|
||||
});
|
||||
let packageNameSet = false;
|
||||
async function ensurePackageName(plugin: NostrSignerNative): Promise<void> {
|
||||
if (packageNameSet) return;
|
||||
// CRITICAL: setPackageName MUST NOT be able to wedge the login flow. On some
|
||||
// devices (GrapheneOS in particular) this bridge call can hang and never
|
||||
// resolve — and because getPlugin() awaited it, getPublicKey() was never even
|
||||
// reached, so its 90s timeout never started → eternal "Waiting for Amber".
|
||||
// We race it against a short timeout and simply continue regardless: Amber's
|
||||
// package is also passed explicitly on every getPublicKey/sign/encrypt call,
|
||||
// so setPackageName is a nice-to-have, not a hard requirement.
|
||||
try {
|
||||
await Promise.race([
|
||||
plugin.setPackageName({ packageName: AMBER_PACKAGE }),
|
||||
new Promise<void>((resolve) => setTimeout(resolve, 3000)),
|
||||
]);
|
||||
} catch (e) {
|
||||
console.warn('[CapacitorAmberSigner] setPackageName warning (continuing):', e);
|
||||
}
|
||||
return packageNameSet;
|
||||
packageNameSet = true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in a new issue