diff options
| author | Adam Soltys <[email protected]> | 2026-09-12 16:59:10 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-09-12 16:59:10 -0700 |
| commit | 8fe3d0d28d5f15643c1737809c5f1773cb8f9f94 (patch) | |
| tree | 2241df66a3115b3261d6671dd0a7fbcf6d12d236 /android/app/src/main | |
| parent | 7fcad2ddfbaae862d150430b27674606ebca299e (diff) | |
| download | coinosv3-8fe3d0d28d5f15643c1737809c5f1773cb8f9f94.tar.gz coinosv3-8fe3d0d28d5f15643c1737809c5f1773cb8f9f94.zip | |
Don't trust the first distributor on the phone — three were installed.
ntfy went onto a real phone and the handshake ran: our app broadcast REGISTER,
ntfy answered with an endpoint (https://ntfy.sh/up…), the app stored it, and
the web app took it off the launch URL and kept it. That was the link I'd said
was unproven.
It also turned up something the sandbox never would. The phone had THREE apps
answering the distributor action — Element's and Yakihonne's internal ones
alongside ntfy — and both of those exist to serve their own app, not anyone
else's. Picking the first would have registered with a distributor that will
never hand us an endpoint, and there'd be nothing to show for it.
The spec's answer is to let the user choose, and this app has no settings
screen to choose in, so it works through them instead: ask one, and if no
endpoint has arrived by the next launch, ask the next. A refusal moves on
immediately. Whoever answers is remembered for good.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_019mrt228Upn9nNCwGixzSLx
Diffstat (limited to 'android/app/src/main')
| -rw-r--r-- | android/app/src/main/java/io/coinos/app/Push.java | 38 | ||||
| -rw-r--r-- | android/app/src/main/java/io/coinos/app/PushReceiver.java | 7 |
2 files changed, 39 insertions, 6 deletions
diff --git a/android/app/src/main/java/io/coinos/app/Push.java b/android/app/src/main/java/io/coinos/app/Push.java index 1c4bee5..925693a 100644 --- a/android/app/src/main/java/io/coinos/app/Push.java +++ b/android/app/src/main/java/io/coinos/app/Push.java @@ -52,6 +52,10 @@ public final class Push { private static final String KEY_DIST = "distributor"; private static final String KEY_ENDPOINT = "endpoint"; private static final String KEY_SENT = "endpointSent"; + private static final String KEY_TRY = "tryIndex"; + private static final String KEY_TRY_AT = "tryAt"; + /** How long a distributor gets to answer before we try the next one. */ + private static final long TRY_MS = 60_000; static SharedPreferences prefs(Context c) { return c.getSharedPreferences(PREFS, Context.MODE_PRIVATE); @@ -81,17 +85,41 @@ public final class Push { return out; } + /** + * Which distributor to ask. + * + * <p>Not simply the first one installed. Several apps embed a distributor + * for their own use and export it anyway — a phone here had three, and the + * first two were Element's and Yakihonne's internal ones, which will never + * hand an endpoint to anybody else. The spec's answer is to let the user + * choose, but this app has no settings screen to choose in, so it works + * through them instead: ask one, and if no endpoint has arrived by the next + * launch, ask the next. Whoever answers is remembered for good. + */ static String distributor(Context c) { - String saved = prefs(c).getString(KEY_DIST, null); + SharedPreferences p = prefs(c); List<String> all = distributors(c); - // a distributor that's been uninstalled is not a distributor - if (saved != null && all.contains(saved)) return saved; if (all.isEmpty()) return null; - String pick = all.get(0); - prefs(c).edit().putString(KEY_DIST, pick).apply(); + String saved = p.getString(KEY_DIST, null); + // one that answered, and is still installed, is the one we keep + if (saved != null && all.contains(saved) && p.getString(KEY_ENDPOINT, null) != null) return saved; + // otherwise the one we're currently trying, moving on if it's had its + // chance and produced nothing + int idx = p.getInt(KEY_TRY, 0); + long since = p.getLong(KEY_TRY_AT, 0); + if (saved != null && since > 0 && System.currentTimeMillis() - since > TRY_MS) idx++; + if (idx >= all.size()) idx = 0; + String pick = all.get(idx); + p.edit().putString(KEY_DIST, pick).putInt(KEY_TRY, idx).putLong(KEY_TRY_AT, System.currentTimeMillis()).apply(); return pick; } + /** Give up on the current distributor; the next call picks another. */ + static void nextDistributor(Context c) { + SharedPreferences p = prefs(c); + p.edit().putInt(KEY_TRY, p.getInt(KEY_TRY, 0) + 1).putLong(KEY_TRY_AT, 0).remove(KEY_DIST).apply(); + } + static String endpoint(Context c) { return prefs(c).getString(KEY_ENDPOINT, null); } diff --git a/android/app/src/main/java/io/coinos/app/PushReceiver.java b/android/app/src/main/java/io/coinos/app/PushReceiver.java index 508c331..ce3e3f0 100644 --- a/android/app/src/main/java/io/coinos/app/PushReceiver.java +++ b/android/app/src/main/java/io/coinos/app/PushReceiver.java @@ -36,7 +36,12 @@ public class PushReceiver extends BroadcastReceiver { if (endpoint != null && !endpoint.isEmpty()) Push.setEndpoint(context, endpoint); break; } - case Push.ACTION_REGISTRATION_FAILED: + case Push.ACTION_REGISTRATION_FAILED: { + // this one won't have us — try the next one at the next opportunity + Push.nextDistributor(context); + Push.register(context); + break; + } case Push.ACTION_UNREGISTERED: { Push.prefs(context).edit().remove("endpoint").putBoolean("endpointSent", false).apply(); break; |
