fix(routing): prevent domain stacking and remove Try Demo button
- Fix server-side redirect to always use canonical rspace.online as base domain instead of deriving from hostClean (prevents demo.rspace.rspace.online) - Fix bare-domain check to exact match instead of .includes() (prevents stacked subdomains from triggering bare-domain routing) - Fix client-side rspaceNavUrl to guard against reserved subdomain names being used as space subdomains (e.g. rspace.rspace.online) - Fix identity component _navUrl with same canonical domain guards - Remove "Try Demo" header button from all shell rendering functions - Remove demo-btn CSS styles - Fix pre-existing SchemaType error in Gemini tool declarations Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
dd46905e12
commit
22b5c00a13
|
|
@ -2029,7 +2029,7 @@ app.post("/api/trips/ai-prompt", async (c) => {
|
|||
|
||||
const geminiModel = genAI.getGenerativeModel({
|
||||
model: GEMINI_MODELS[model],
|
||||
tools: [{ functionDeclarations: allDeclarations }],
|
||||
tools: [{ functionDeclarations: allDeclarations as any }],
|
||||
systemInstruction: systemPrompt || "You are a travel planning assistant.",
|
||||
});
|
||||
|
||||
|
|
@ -3284,7 +3284,8 @@ const server = Bun.serve<WSData>({
|
|||
}
|
||||
|
||||
// ── Bare-domain routing: rspace.online/{...} ──
|
||||
if (!subdomain && hostClean.includes("rspace.online")) {
|
||||
// Only match canonical bare domain, not stacked subdomains like rspace.rspace.online
|
||||
if (!subdomain && (hostClean === "rspace.online" || hostClean === "www.rspace.online")) {
|
||||
// Top-level routes that must bypass module rewriting
|
||||
if (url.pathname.startsWith("/rtasks/check/")) {
|
||||
return app.fetch(req);
|
||||
|
|
@ -3349,9 +3350,8 @@ const server = Bun.serve<WSData>({
|
|||
// Page navigation: redirect to canonical subdomain URL
|
||||
const space = firstSegment;
|
||||
const rest = "/" + pathSegments.slice(1).join("/");
|
||||
const baseDomain = hostClean.replace(/^www\./, "");
|
||||
return Response.redirect(
|
||||
`${proto}//${space}.${baseDomain}${rest}${url.search}`, 301
|
||||
`${proto}//${space}.rspace.online${rest}${url.search}`, 301
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -173,7 +173,6 @@ export function renderShell(opts: ShellOptions): string {
|
|||
...m,
|
||||
enabled: !enabledModules || enabledModules.includes(m.id) || m.id === "rspace",
|
||||
})));
|
||||
const shellDemoUrl = `https://demo.rspace.online/${escapeAttr(moduleId)}`;
|
||||
|
||||
return versionAssetUrls(`<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
|
@ -241,7 +240,6 @@ export function renderShell(opts: ShellOptions): string {
|
|||
<rstack-mi></rstack-mi>
|
||||
</div>
|
||||
<div class="rstack-header__right">
|
||||
<a class="rstack-header__demo-btn" ${spaceSlug === "demo" ? 'data-hide' : ''} href="${shellDemoUrl}">Try Demo</a>
|
||||
<rstack-offline-indicator></rstack-offline-indicator>
|
||||
<rstack-comment-bell></rstack-comment-bell>
|
||||
<rstack-notification-bell></rstack-notification-bell>
|
||||
|
|
@ -439,23 +437,6 @@ export function renderShell(opts: ShellOptions): string {
|
|||
_switcher?.setModules(window.__rspaceModuleList);
|
||||
_switcher?.setAllModules(window.__rspaceAllModules);
|
||||
|
||||
// ── "Try Demo" button visibility ──
|
||||
// Hidden when logged in. When logged out, shown everywhere except demo.rspace.online
|
||||
// (bare rspace.online rewrites to demo internally but still shows the button).
|
||||
(function() {
|
||||
var btn = document.querySelector('.rstack-header__demo-btn');
|
||||
if (!btn) return;
|
||||
function update() {
|
||||
var loggedIn = false;
|
||||
try { loggedIn = !!localStorage.getItem('encryptid_session'); } catch(e) {}
|
||||
if (loggedIn) { btn.setAttribute('data-hide', ''); return; }
|
||||
var host = window.location.host.split(':')[0];
|
||||
if (host === 'demo.rspace.online') { btn.setAttribute('data-hide', ''); }
|
||||
else { btn.removeAttribute('data-hide'); }
|
||||
}
|
||||
update();
|
||||
document.addEventListener('auth-change', update);
|
||||
})();
|
||||
|
||||
// ── Welcome tour (guided feature walkthrough for first-time visitors) ──
|
||||
(function() {
|
||||
|
|
@ -2065,7 +2046,6 @@ export function renderModuleLanding(opts: ModuleLandingOptions): string {
|
|||
<a href="/" style="display:flex;align-items:center;margin-right:4px"><img src="/favicon.png" alt="rSpace" class="rstack-header__logo"></a>
|
||||
<rstack-app-switcher current="${escapeAttr(mod.id)}"></rstack-app-switcher>
|
||||
<rstack-space-switcher current="" name="Spaces"></rstack-space-switcher>
|
||||
<a class="rstack-header__demo-btn" href="${demoUrl}">Try Demo</a>
|
||||
</div>
|
||||
<div class="rstack-header__center">
|
||||
<rstack-mi></rstack-mi>
|
||||
|
|
@ -2082,17 +2062,6 @@ export function renderModuleLanding(opts: ModuleLandingOptions): string {
|
|||
navigator.serviceWorker.register("/sw.js").catch(() => {});
|
||||
}
|
||||
document.querySelector('rstack-app-switcher')?.setModules(${moduleListJSON});
|
||||
function _updateDemoBtn() {
|
||||
var btn = document.querySelector('.rstack-header__demo-btn');
|
||||
if (!btn) return;
|
||||
try {
|
||||
var raw = localStorage.getItem('encryptid_session');
|
||||
if (raw && JSON.parse(raw)?.accessToken) { btn.setAttribute('data-hide', ''); }
|
||||
else { btn.removeAttribute('data-hide'); }
|
||||
} catch(e) {}
|
||||
}
|
||||
_updateDemoBtn();
|
||||
document.addEventListener('auth-change', _updateDemoBtn);
|
||||
try {
|
||||
var raw = localStorage.getItem('encryptid_session');
|
||||
if (raw) {
|
||||
|
|
@ -2412,7 +2381,6 @@ export function renderSubPageInfo(opts: SubPageInfoOptions): string {
|
|||
<a href="/" style="display:flex;align-items:center;margin-right:4px"><img src="/favicon.png" alt="rSpace" class="rstack-header__logo"></a>
|
||||
<rstack-app-switcher current="${escapeAttr(mod.id)}"></rstack-app-switcher>
|
||||
<rstack-space-switcher current="" name="Spaces"></rstack-space-switcher>
|
||||
<a class="rstack-header__demo-btn" href="${demoUrl}">Try Demo</a>
|
||||
</div>
|
||||
<div class="rstack-header__center">
|
||||
<rstack-mi></rstack-mi>
|
||||
|
|
@ -2429,17 +2397,6 @@ export function renderSubPageInfo(opts: SubPageInfoOptions): string {
|
|||
navigator.serviceWorker.register("/sw.js").catch(() => {});
|
||||
}
|
||||
document.querySelector('rstack-app-switcher')?.setModules(${moduleListJSON});
|
||||
function _updateDemoBtn() {
|
||||
var btn = document.querySelector('.rstack-header__demo-btn');
|
||||
if (!btn) return;
|
||||
try {
|
||||
var raw = localStorage.getItem('encryptid_session');
|
||||
if (raw && JSON.parse(raw)?.accessToken) { btn.setAttribute('data-hide', ''); }
|
||||
else { btn.removeAttribute('data-hide'); }
|
||||
} catch(e) {}
|
||||
}
|
||||
_updateDemoBtn();
|
||||
document.addEventListener('auth-change', _updateDemoBtn);
|
||||
try {
|
||||
var raw = localStorage.getItem('encryptid_session');
|
||||
if (raw) {
|
||||
|
|
|
|||
|
|
@ -333,13 +333,18 @@ function _getCurrentModule(): string {
|
|||
}
|
||||
function _navUrl(space: string, moduleId: string): string {
|
||||
const h = window.location.host.split(":")[0].split(".");
|
||||
const onSub = h.length >= 3 && h.slice(-2).join(".") === "rspace.online" && !_RESERVED.includes(h[0]);
|
||||
const proto = window.location.protocol;
|
||||
const BASE = "rspace.online";
|
||||
const onSub = h.length >= 3 && h.slice(-2).join(".") === BASE && !_RESERVED.includes(h[0]);
|
||||
if (onSub) {
|
||||
if (h[0] === space) return "/" + moduleId;
|
||||
return window.location.protocol + "//" + space + "." + h.slice(-2).join(".") + "/" + moduleId;
|
||||
if (_RESERVED.includes(space)) return proto + "//" + BASE + "/" + moduleId;
|
||||
return proto + "//" + space + "." + BASE + "/" + moduleId;
|
||||
}
|
||||
if (window.location.host.includes("rspace.online") && !window.location.host.startsWith("www")) {
|
||||
return window.location.protocol + "//" + space + ".rspace.online/" + moduleId;
|
||||
const host = window.location.host.split(":")[0];
|
||||
if (host === BASE || host === "www." + BASE || host.endsWith("." + BASE)) {
|
||||
if (space === "demo" || _RESERVED.includes(space)) return "/" + moduleId;
|
||||
return proto + "//" + space + "." + BASE + "/" + moduleId;
|
||||
}
|
||||
return "/" + space + "/" + moduleId;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,18 +68,24 @@ export function getCurrentModule(): string {
|
|||
* On localhost: uses /{space}/{moduleId}.
|
||||
*/
|
||||
export function rspaceNavUrl(space: string, moduleId: string): string {
|
||||
const hostParts = window.location.host.split(":")[0].split(".");
|
||||
const host = window.location.host.split(":")[0];
|
||||
const hostParts = host.split(".");
|
||||
const proto = window.location.protocol;
|
||||
|
||||
// Always use canonical base domain to prevent stacking (e.g. rspace.rspace.online)
|
||||
const BASE = "rspace.online";
|
||||
|
||||
const onSubdomain =
|
||||
hostParts.length >= 3 &&
|
||||
hostParts.slice(-2).join(".") === "rspace.online" &&
|
||||
hostParts.slice(-2).join(".") === BASE &&
|
||||
!RESERVED_SUBDOMAINS.includes(hostParts[0]);
|
||||
|
||||
// Standalone r*.online domains → redirect to rspace.online for navigation
|
||||
if (isStandaloneDomain()) {
|
||||
if (space === "demo") {
|
||||
return `${window.location.protocol}//demo.rspace.online/${moduleId}`;
|
||||
return `${proto}//demo.${BASE}/${moduleId}`;
|
||||
}
|
||||
return `${window.location.protocol}//${space}.rspace.online/${moduleId}`;
|
||||
return `${proto}//${space}.${BASE}/${moduleId}`;
|
||||
}
|
||||
|
||||
if (onSubdomain) {
|
||||
|
|
@ -87,19 +93,26 @@ export function rspaceNavUrl(space: string, moduleId: string): string {
|
|||
if (hostParts[0] === space) {
|
||||
return `/${moduleId}`;
|
||||
}
|
||||
// Different space → switch subdomain
|
||||
const baseDomain = hostParts.slice(-2).join(".");
|
||||
return `${window.location.protocol}//${space}.${baseDomain}/${moduleId}`;
|
||||
// Guard: reserved words can't be subdomains — treat as demo
|
||||
if (RESERVED_SUBDOMAINS.includes(space)) {
|
||||
return `${proto}//${BASE}/${moduleId}`;
|
||||
}
|
||||
// Different space → switch subdomain (always use canonical base)
|
||||
return `${proto}//${space}.${BASE}/${moduleId}`;
|
||||
}
|
||||
|
||||
// Bare domain (rspace.online)
|
||||
if (isBareDomain()) {
|
||||
// Bare domain (rspace.online) or any non-standard rspace.online host
|
||||
if (isBareDomain() || host.endsWith(`.${BASE}`)) {
|
||||
// Default space → stay on bare domain: /{moduleId}
|
||||
if (space === "demo") {
|
||||
return `/${moduleId}`;
|
||||
}
|
||||
// Guard: reserved words can't be subdomains
|
||||
if (RESERVED_SUBDOMAINS.includes(space)) {
|
||||
return `/${moduleId}`;
|
||||
}
|
||||
// Explicit space → switch to subdomain
|
||||
return `${window.location.protocol}//${space}.rspace.online/${moduleId}`;
|
||||
return `${proto}//${space}.${BASE}/${moduleId}`;
|
||||
}
|
||||
|
||||
// Localhost/dev
|
||||
|
|
|
|||
|
|
@ -62,25 +62,6 @@ body {
|
|||
z-index: 2;
|
||||
}
|
||||
|
||||
.rstack-header__demo-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 5px 14px;
|
||||
border-radius: 6px;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
transition: background 0.15s, opacity 0.15s;
|
||||
background: var(--rs-gradient-cta);
|
||||
color: #fff;
|
||||
box-shadow: 0 1px 4px rgba(20, 184, 166, 0.25);
|
||||
}
|
||||
.rstack-header__demo-btn:hover {
|
||||
opacity: 0.88;
|
||||
}
|
||||
/* Hide the demo button when already on demo space */
|
||||
.rstack-header__demo-btn[data-hide] { display: none; }
|
||||
|
||||
.rstack-header__logo {
|
||||
width: 28px;
|
||||
|
|
@ -403,10 +384,6 @@ body.rstack-sidebar-open #toolbar {
|
|||
gap: 6px;
|
||||
margin-left: auto;
|
||||
}
|
||||
.rstack-header__demo-btn {
|
||||
padding: 4px 10px;
|
||||
font-size: 0.72rem;
|
||||
}
|
||||
.rstack-header__brand {
|
||||
font-size: 1rem;
|
||||
gap: 6px;
|
||||
|
|
|
|||
Loading…
Reference in New Issue