aboutsummaryrefslogtreecommitdiffstats
path: root/src/app.js
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-10-04 13:02:57 -0700
committerAdam Soltys <[email protected]>2026-10-04 13:02:57 -0700
commit53a4ad1602f7ce0398809bf15fd5196884e2c1a0 (patch)
tree2385f4deea7deeda91b04508291feb8099a80fa7 /src/app.js
parentb0fdec8eef365805f7a3a57bd5ec5563472785f9 (diff)
downloadcoinosv3-53a4ad1602f7ce0398809bf15fd5196884e2c1a0.tar.gz
coinosv3-53a4ad1602f7ce0398809bf15fd5196884e2c1a0.zip
/migrate: a link that opens the coinos.io account move
Fresh devices reach it through the wizard (in place of the Spending offer); an existing wallet gets the move page on its own once it can mint a payment address, and leaves it straight back to the wallet. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/app.js')
-rw-r--r--src/app.js57
1 files changed, 53 insertions, 4 deletions
diff --git a/src/app.js b/src/app.js
index 28cf9aa..b574062 100644
--- a/src/app.js
+++ b/src/app.js
@@ -3515,6 +3515,20 @@ function vaultScreen() {
// wallet brings it back. The current step is persisted so a refresh (or the
// legacy-migration round trip) resumes exactly where it left off.
const ONB_STEP_KEY = 'btc-wallet-onb-step';
+// Set by the /migrate link (read at boot, next to /pos): open the coinos.io
+// migration page as soon as this device can offer it.
+const MIGRATE_LINK_KEY = 'btc-wallet-migrate-link';
+function migrateLinkPending() {
+ try {
+ const at = Number(localStorage.getItem(MIGRATE_LINK_KEY));
+ if (at && Date.now() - at < 86_400_000) return true;
+ localStorage.removeItem(MIGRATE_LINK_KEY);
+ } catch {}
+ return false;
+}
+function endMigrateLink() {
+ try { localStorage.removeItem(MIGRATE_LINK_KEY); } catch {}
+}
function onbInProgress() {
try { return !!localStorage.getItem(ONB_STEP_KEY); } catch { return false; }
}
@@ -3592,8 +3606,8 @@ function onboardScreen() {
// migrate page, so coming back must offer "I'm done, continue" — the
// in-memory flag died with the navigation and left them looping.
ui.onb = saved === 'legacy-went'
- ? { step: 'legacy', wentToLegacy: true }
- : { step: saved || 'welcome' };
+ ? { step: 'legacy', wentToLegacy: true, fromLink: migrateLinkPending() }
+ : { step: saved || 'welcome', fromLink: saved === 'legacy' && migrateLinkPending() };
if (saved === 'signin') ui.nostrLoginOpen = true; // reopen the login card
}
const o = ui.onb;
@@ -3626,6 +3640,9 @@ function onboardScreen() {
return null; // the caller falls through to the wallet itself
}
}
+ // Arriving by the /migrate link: the coinos.io move it asked for, in place
+ // of the Spending offer. Coming back from it is the ordinary wizard.
+ if (o.step === 'spend' && migrateLinkPending()) { endMigrateLink(); o.step = 'legacy'; }
if (o.step === 'spend') {
const addr = featureHook('namesAddress');
if (o.enterAddr === undefined) o.enterAddr = addr || null;
@@ -3692,7 +3709,12 @@ function onboardScreen() {
class: 'btn-primary btn-block', style: 'padding:14px',
disabled: !!o.legacyBusy, onClick: goLegacy,
}, o.legacyBusy ? h('span', { class: 'spinner sm' }) : t('onbLegacyGo')),
- h('button', { class: 'btn-block', style: 'padding:14px', onClick: () => { o.step = 'spend'; render(); } },
+ h('button', { class: 'btn-block', style: 'padding:14px', onClick: () => {
+ // an existing wallet sent here by the /migrate link has nothing
+ // left to set up: out of the wizard, back to the wallet
+ if (o.fromLink) { endMigrateLink(); leaveWizard(); } else o.step = 'spend';
+ render();
+ } },
o.wentToLegacy || claiming ? t('onbLegacyDone') : t('back')),
ui.onbError ? h('div', { class: 'notice err' }, ui.onbError) : null,
]);
@@ -3956,7 +3978,10 @@ function claimMigratedName() {
// along instead of re-offering the page that was just completed. A
// null enterAddr lets the spend step see the claimed name as change
// and advance on its own.
- if (ui.onb && ui.onb.step === 'legacy') { ui.onb.step = 'spend'; ui.onb.enterAddr = null; }
+ if (ui.onb && ui.onb.step === 'legacy') {
+ if (ui.onb.fromLink) { endMigrateLink(); leaveWizard(); }
+ else { ui.onb.step = 'spend'; ui.onb.enterAddr = null; }
+ }
toast(t('migrateClaimed', { name }));
render();
})
@@ -3972,7 +3997,17 @@ function claimMigratedName() {
attempt();
}
+function leaveWizard() {
+ ui.onb = null;
+ try { localStorage.removeItem(ONB_STEP_KEY); } catch {}
+}
+
function walletScreen() {
+ // The /migrate link on a device that already has a wallet: the wizard's
+ // migrate page, on its own, once the wallet can mint a payment address.
+ if (!ui.onb && !onbInProgress() && activeAccount() && migrateLinkPending() && canOfferSpending()) {
+ ui.onb = { step: 'legacy', fromLink: true };
+ }
if (ui.onb || onbInProgress()) {
const s = onboardScreen();
if (s) return s; // null = the wizard just handed over; fall through
@@ -5941,6 +5976,20 @@ const BOOT_POS = (() => {
} catch { return false; }
})();
if (BOOT_POS) ui.posAtBoot = true;
+// v3.coinos.io/migrate: a link to hand someone who hasn't found the
+// coinos.io move in the UI. Stashed (not just flagged) because the page it
+// leads to needs an open wallet and a payment address — a fresh device gets
+// there through the whole wizard, a locked one through its unlock. A day
+// later the link's intent is stale and is dropped unannounced.
+if ((() => {
+ try {
+ if (!/^\/migrate\/?$/i.test(location.pathname)) return false;
+ history.replaceState(null, '', '/' + (location.search || ''));
+ return true;
+ } catch { return false; }
+})()) {
+ try { localStorage.setItem(MIGRATE_LINK_KEY, String(Date.now())); } catch {}
+}
const INTENT_URI = (() => {
try {
const u = new URLSearchParams(location.search).get('u');