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({
|
const geminiModel = genAI.getGenerativeModel({
|
||||||
model: GEMINI_MODELS[model],
|
model: GEMINI_MODELS[model],
|
||||||
tools: [{ functionDeclarations: allDeclarations }],
|
tools: [{ functionDeclarations: allDeclarations as any }],
|
||||||
systemInstruction: systemPrompt || "You are a travel planning assistant.",
|
systemInstruction: systemPrompt || "You are a travel planning assistant.",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -3284,7 +3284,8 @@ const server = Bun.serve<WSData>({
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Bare-domain routing: rspace.online/{...} ──
|
// ── 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
|
// Top-level routes that must bypass module rewriting
|
||||||
if (url.pathname.startsWith("/rtasks/check/")) {
|
if (url.pathname.startsWith("/rtasks/check/")) {
|
||||||
return app.fetch(req);
|
return app.fetch(req);
|
||||||
|
|
@ -3349,9 +3350,8 @@ const server = Bun.serve<WSData>({
|
||||||
// Page navigation: redirect to canonical subdomain URL
|
// Page navigation: redirect to canonical subdomain URL
|
||||||
const space = firstSegment;
|
const space = firstSegment;
|
||||||
const rest = "/" + pathSegments.slice(1).join("/");
|
const rest = "/" + pathSegments.slice(1).join("/");
|
||||||
const baseDomain = hostClean.replace(/^www\./, "");
|
|
||||||
return Response.redirect(
|
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,
|
...m,
|
||||||
enabled: !enabledModules || enabledModules.includes(m.id) || m.id === "rspace",
|
enabled: !enabledModules || enabledModules.includes(m.id) || m.id === "rspace",
|
||||||
})));
|
})));
|
||||||
const shellDemoUrl = `https://demo.rspace.online/${escapeAttr(moduleId)}`;
|
|
||||||
|
|
||||||
return versionAssetUrls(`<!DOCTYPE html>
|
return versionAssetUrls(`<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
@ -241,7 +240,6 @@ export function renderShell(opts: ShellOptions): string {
|
||||||
<rstack-mi></rstack-mi>
|
<rstack-mi></rstack-mi>
|
||||||
</div>
|
</div>
|
||||||
<div class="rstack-header__right">
|
<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-offline-indicator></rstack-offline-indicator>
|
||||||
<rstack-comment-bell></rstack-comment-bell>
|
<rstack-comment-bell></rstack-comment-bell>
|
||||||
<rstack-notification-bell></rstack-notification-bell>
|
<rstack-notification-bell></rstack-notification-bell>
|
||||||
|
|
@ -439,23 +437,6 @@ export function renderShell(opts: ShellOptions): string {
|
||||||
_switcher?.setModules(window.__rspaceModuleList);
|
_switcher?.setModules(window.__rspaceModuleList);
|
||||||
_switcher?.setAllModules(window.__rspaceAllModules);
|
_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) ──
|
// ── Welcome tour (guided feature walkthrough for first-time visitors) ──
|
||||||
(function() {
|
(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>
|
<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-app-switcher current="${escapeAttr(mod.id)}"></rstack-app-switcher>
|
||||||
<rstack-space-switcher current="" name="Spaces"></rstack-space-switcher>
|
<rstack-space-switcher current="" name="Spaces"></rstack-space-switcher>
|
||||||
<a class="rstack-header__demo-btn" href="${demoUrl}">Try Demo</a>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="rstack-header__center">
|
<div class="rstack-header__center">
|
||||||
<rstack-mi></rstack-mi>
|
<rstack-mi></rstack-mi>
|
||||||
|
|
@ -2082,17 +2062,6 @@ export function renderModuleLanding(opts: ModuleLandingOptions): string {
|
||||||
navigator.serviceWorker.register("/sw.js").catch(() => {});
|
navigator.serviceWorker.register("/sw.js").catch(() => {});
|
||||||
}
|
}
|
||||||
document.querySelector('rstack-app-switcher')?.setModules(${moduleListJSON});
|
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 {
|
try {
|
||||||
var raw = localStorage.getItem('encryptid_session');
|
var raw = localStorage.getItem('encryptid_session');
|
||||||
if (raw) {
|
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>
|
<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-app-switcher current="${escapeAttr(mod.id)}"></rstack-app-switcher>
|
||||||
<rstack-space-switcher current="" name="Spaces"></rstack-space-switcher>
|
<rstack-space-switcher current="" name="Spaces"></rstack-space-switcher>
|
||||||
<a class="rstack-header__demo-btn" href="${demoUrl}">Try Demo</a>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="rstack-header__center">
|
<div class="rstack-header__center">
|
||||||
<rstack-mi></rstack-mi>
|
<rstack-mi></rstack-mi>
|
||||||
|
|
@ -2429,17 +2397,6 @@ export function renderSubPageInfo(opts: SubPageInfoOptions): string {
|
||||||
navigator.serviceWorker.register("/sw.js").catch(() => {});
|
navigator.serviceWorker.register("/sw.js").catch(() => {});
|
||||||
}
|
}
|
||||||
document.querySelector('rstack-app-switcher')?.setModules(${moduleListJSON});
|
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 {
|
try {
|
||||||
var raw = localStorage.getItem('encryptid_session');
|
var raw = localStorage.getItem('encryptid_session');
|
||||||
if (raw) {
|
if (raw) {
|
||||||
|
|
|
||||||
|
|
@ -333,13 +333,18 @@ function _getCurrentModule(): string {
|
||||||
}
|
}
|
||||||
function _navUrl(space: string, moduleId: string): string {
|
function _navUrl(space: string, moduleId: string): string {
|
||||||
const h = window.location.host.split(":")[0].split(".");
|
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 (onSub) {
|
||||||
if (h[0] === space) return "/" + moduleId;
|
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")) {
|
const host = window.location.host.split(":")[0];
|
||||||
return window.location.protocol + "//" + space + ".rspace.online/" + moduleId;
|
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;
|
return "/" + space + "/" + moduleId;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -68,18 +68,24 @@ export function getCurrentModule(): string {
|
||||||
* On localhost: uses /{space}/{moduleId}.
|
* On localhost: uses /{space}/{moduleId}.
|
||||||
*/
|
*/
|
||||||
export function rspaceNavUrl(space: string, moduleId: string): string {
|
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 =
|
const onSubdomain =
|
||||||
hostParts.length >= 3 &&
|
hostParts.length >= 3 &&
|
||||||
hostParts.slice(-2).join(".") === "rspace.online" &&
|
hostParts.slice(-2).join(".") === BASE &&
|
||||||
!RESERVED_SUBDOMAINS.includes(hostParts[0]);
|
!RESERVED_SUBDOMAINS.includes(hostParts[0]);
|
||||||
|
|
||||||
// Standalone r*.online domains → redirect to rspace.online for navigation
|
// Standalone r*.online domains → redirect to rspace.online for navigation
|
||||||
if (isStandaloneDomain()) {
|
if (isStandaloneDomain()) {
|
||||||
if (space === "demo") {
|
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) {
|
if (onSubdomain) {
|
||||||
|
|
@ -87,19 +93,26 @@ export function rspaceNavUrl(space: string, moduleId: string): string {
|
||||||
if (hostParts[0] === space) {
|
if (hostParts[0] === space) {
|
||||||
return `/${moduleId}`;
|
return `/${moduleId}`;
|
||||||
}
|
}
|
||||||
// Different space → switch subdomain
|
// Guard: reserved words can't be subdomains — treat as demo
|
||||||
const baseDomain = hostParts.slice(-2).join(".");
|
if (RESERVED_SUBDOMAINS.includes(space)) {
|
||||||
return `${window.location.protocol}//${space}.${baseDomain}/${moduleId}`;
|
return `${proto}//${BASE}/${moduleId}`;
|
||||||
|
}
|
||||||
|
// Different space → switch subdomain (always use canonical base)
|
||||||
|
return `${proto}//${space}.${BASE}/${moduleId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bare domain (rspace.online)
|
// Bare domain (rspace.online) or any non-standard rspace.online host
|
||||||
if (isBareDomain()) {
|
if (isBareDomain() || host.endsWith(`.${BASE}`)) {
|
||||||
// Default space → stay on bare domain: /{moduleId}
|
// Default space → stay on bare domain: /{moduleId}
|
||||||
if (space === "demo") {
|
if (space === "demo") {
|
||||||
return `/${moduleId}`;
|
return `/${moduleId}`;
|
||||||
}
|
}
|
||||||
|
// Guard: reserved words can't be subdomains
|
||||||
|
if (RESERVED_SUBDOMAINS.includes(space)) {
|
||||||
|
return `/${moduleId}`;
|
||||||
|
}
|
||||||
// Explicit space → switch to subdomain
|
// Explicit space → switch to subdomain
|
||||||
return `${window.location.protocol}//${space}.rspace.online/${moduleId}`;
|
return `${proto}//${space}.${BASE}/${moduleId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Localhost/dev
|
// Localhost/dev
|
||||||
|
|
|
||||||
|
|
@ -62,25 +62,6 @@ body {
|
||||||
z-index: 2;
|
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 {
|
.rstack-header__logo {
|
||||||
width: 28px;
|
width: 28px;
|
||||||
|
|
@ -403,10 +384,6 @@ body.rstack-sidebar-open #toolbar {
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
.rstack-header__demo-btn {
|
|
||||||
padding: 4px 10px;
|
|
||||||
font-size: 0.72rem;
|
|
||||||
}
|
|
||||||
.rstack-header__brand {
|
.rstack-header__brand {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue