From 8987451cf602ae66e287e39c3de5d44c3f8438c2 Mon Sep 17 00:00:00 2001 From: martbost Date: Fri, 18 Sep 2026 11:25:34 -0500 Subject: [PATCH] Carry the placement choice with the referral too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Auditing the rest of the prospect paths after Terry's leak. Every entry point funnels through /join/ — the Telegram Mini App redirects there, member page-builder pages link there, the public /my/ share panel links there — so the cookie covers all of them. One gap left: ?direct=1 is the inviter's explicit decision to take the join under themselves rather than route it down their leg, and only the id was being remembered. A prospect who opened a direct link and then wandered through the nav came back through the moving link instead — the opposite of what the shared link asked for. The flag now rides along in its own cookie, and both /start and /join-now prefer an explicit choice over the position's default. Co-Authored-By: Claude Opus 5 (1M context) --- public/join-now.js | 5 ++++- public/start.js | 9 +++++++-- server.js | 9 ++++++++- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/public/join-now.js b/public/join-now.js index eb7a454..20d74df 100644 --- a/public/join-now.js +++ b/public/join-now.js @@ -59,9 +59,12 @@ fetch('/api/public/config').then(function(r){return r.json()}).then(function(c){ // someone's /join/ page. Without this a prospect who wandered through the nav // enrolled under the company rotation and the inviter lost the referral. if(!ref){ try{ var ck=document.cookie.match(/(?:^|;\s*)rmc_ref=(\d{1,15})/); if(ck)ref=ck[1]; }catch(e){} } + // ...and the placement choice the invite link carried, for the same reason. + var directFlag=qp.get('direct'); + if(directFlag!=='1'&&directFlag!=='0'){ try{ var cd=document.cookie.match(/(?:^|;\s*)rmc_direct=([01])/); if(cd)directFlag=cd[1]; }catch(e){} } // Direct link (?direct=1): opt out of rotation — register straight under // the inviter (spillover in their team), never the next-to-qualify member. - if(ref&&/^\d{1,15}$/.test(ref)&&qp.get('direct')==='1'){ + if(ref&&/^\d{1,15}$/.test(ref)&&directFlag==='1'){ sponsorId=String(ref); $('sponsorLine').innerHTML='You’ll join directly under #'+ref+' — a spillover placement in their team.'; return; } if(ref&&/^\d{1,15}$/.test(ref)){ diff --git a/public/start.js b/public/start.js index d4b53ab..62b9a4e 100644 --- a/public/start.js +++ b/public/start.js @@ -26,7 +26,12 @@ async function load(){ // Route exactly the way that member's own invite page would: their moving link sends // the join to the next position in their leg, unless their position is set to take // direct placements. Same rules, so the two pages can never disagree. - const t=(s.directDefault||!s.joinTarget)?{id:s.id,referralUrl:s.referralUrl,directCount:s.directCount}:s.joinTarget; + // an explicit ?direct=1/0 from the invite link wins over the position's default + const dq=(function(){try{const q=new URLSearchParams(location.search).get('direct'); + if(q==='1'||q==='0')return q; + const m=document.cookie.match(/(?:^|;\s*)rmc_direct=([01])/);return m?m[1]:null;}catch(e){return null}})(); + const goDirect=dq==='1'||(dq!=='0'&&(s.directDefault||!s.joinTarget)); + const t=goDirect?{id:s.id,referralUrl:s.referralUrl,directCount:s.directCount}:s.joinTarget; currentSponsor={id:t.id,name:null,directs:(t.directCount!==undefined?t.directCount:0),goal:2,referralUrl:t.referralUrl}; // say whose team this is, so it never reads as an anonymous "current placement" const badge=document.querySelector('.live-badge'); @@ -59,7 +64,7 @@ async function load(){ // carry the inviter through to enrolment — /join-now already knows how to place a // ?ref properly (direct placement or next open spot in their leg). Without this the // button dropped the referral on the floor and enrolled under the company rotation. - document.getElementById('joinButton').href='/join-now'+(ref?('?ref='+encodeURIComponent(ref)):''); + document.getElementById('joinButton').href='/join-now'+(ref?('?ref='+encodeURIComponent(ref)+(dq?('&direct='+dq):'')):''); {const dl=document.getElementById('dappJoinLink');if(dl&¤tSponsor.referralUrl){dl.href=currentSponsor.referralUrl;dl.classList.remove('hidden');}} // the company-queue line only means anything on the company-rotation path // only on the company-rotation path — the invited path already wrote its own line here diff --git a/server.js b/server.js index c0d3659..678f356 100644 --- a/server.js +++ b/server.js @@ -1793,7 +1793,14 @@ function serveMemberPage(req,res,file,kind,id){ // to a stranger. The invite is the referral, so it has to outlive the one page it landed // on. 30 days, Lax so it survives a normal click-through from anywhere. if(kind==='join'&&/^\d{1,15}$/.test(String(id))){ - head['Set-Cookie']='rmc_ref='+id+'; Path=/; Max-Age=2592000; SameSite=Lax'+(IS_PROD?'; Secure':''); + const attrs='; Path=/; Max-Age=2592000; SameSite=Lax'+(IS_PROD?'; Secure':''); + const ck=['rmc_ref='+id+attrs]; + // ?direct=1/0 is the inviter's explicit choice about placement, so it has to travel with + // the referral. Without it a prospect who wandered off the invite page would come back + // through the moving link instead — the opposite of what the shared link asked for. + const dq=new URL(req.url,'http://x').searchParams.get('direct'); + if(dq==='1'||dq==='0')ck.push('rmc_direct='+dq+attrs); + head['Set-Cookie']=ck; } res.writeHead(200,securityHeaders(head)); res.end(html);