Mini App join funnel: startapp sponsor attribution + wallet-app handoff

- /app decodes startapp=<sponsorId>[_<angle>] -> unlinked visitors land on the
  sponsor squeeze page /join/<ref>?src=miniapp (linked members still go to
  their dashboard); no-invite prospects get a gold Join button -> /join-now
- tg-app.js: sync __rmcInTg flag; external links route out of the webview via
  openLink/openTelegramLink (wallet deep links reach the wallet app reliably)
- join-now.js in the webview leads with the MetaMask/Trust deep links (no
  injected wallet can exist there); ref + src survive into the wallet browser
- tgbot: links command + weekly digest include the t.me/<bot>/<short>?startapp
  Telegram-native invite once config.miniAppShortName is set (new PATCH key)
- Synced chat.js canned answer + AI prompt (prospects can join from the app)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-08-23 06:32:51 -05:00
parent cd361a1eaa
commit f876351391
8 changed files with 60 additions and 7 deletions
+15 -1
View File
@@ -14,6 +14,7 @@
var url = location.origin + '/my';
if (tg && tg.openLink) tg.openLink(url); else location.href = url;
});
on('btn-join', function () { location.href = '/join-now?src=miniapp'; });
on('btn-browse', function () { location.href = '/start'; });
on('btn-retry', function () { location.reload(); });
@@ -24,12 +25,25 @@
try { tg.expand(); } catch (e) {}
try { tg.setHeaderColor('#071421'); tg.setBackgroundColor('#071421'); } catch (e) {}
// Invite attribution: t.me/<bot>/<short>?startapp=<sponsorId>[_<angle>]
// routes a prospect to the sponsor's personalized squeeze page. The webview
// session keeps ?src=miniapp so the join records where it came from.
var ref = null, angle = null;
try {
var sp = (tg.initDataUnsafe && tg.initDataUnsafe.start_param) || '';
var m = /^(\d{1,15})(?:[_-]([a-z]{2,12}))?$/.exec(sp);
if (m) { ref = m[1]; angle = m[2] || null; }
} catch (e) {}
fetch('/api/public/tg-webapp-auth', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ initData: tg.initData })
}).then(function (r) { return r.json(); }).then(function (d) {
if (d && d.ok && d.linked) { location.replace('/my/' + d.id); return; }
if (d && d.ok) { show('st-unlinked'); return; }
if (d && d.ok) {
if (ref) { location.replace('/join/' + ref + '?src=miniapp' + (angle ? '&v=' + angle : '')); return; }
show('st-unlinked'); return;
}
var el = document.getElementById('err-detail');
if (el && d && d.error) el.textContent = d.error;
show('st-error');