rivet-design 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config/evaluateFlags.d.ts +7 -0
- package/dist/config/evaluateFlags.d.ts.map +1 -0
- package/dist/config/evaluateFlags.js +27 -0
- package/dist/config/evaluateFlags.js.map +1 -0
- package/dist/config/flags.d.ts +25 -5
- package/dist/config/flags.d.ts.map +1 -1
- package/dist/config/flags.js +22 -18
- package/dist/config/flags.js.map +1 -1
- package/dist/demo/sessionRuntime.d.ts +2 -0
- package/dist/demo/sessionRuntime.d.ts.map +1 -0
- package/dist/demo/sessionRuntime.js +48 -0
- package/dist/demo/sessionRuntime.js.map +1 -0
- package/dist/hosted-demo.d.ts +2 -0
- package/dist/hosted-demo.d.ts.map +1 -0
- package/dist/hosted-demo.js +80 -0
- package/dist/hosted-demo.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +110 -3
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +35 -28
- package/dist/mcp/server.js.map +1 -1
- package/dist/proxy-middleware/proxy-config.js +1 -1
- package/dist/routes/demo.d.ts +33 -0
- package/dist/routes/demo.d.ts.map +1 -0
- package/dist/routes/demo.js +180 -0
- package/dist/routes/demo.js.map +1 -0
- package/dist/routes/git.d.ts +3 -1
- package/dist/routes/git.d.ts.map +1 -1
- package/dist/routes/git.js +15 -1
- package/dist/routes/git.js.map +1 -1
- package/dist/routes/modifications.d.ts +7 -0
- package/dist/routes/modifications.d.ts.map +1 -1
- package/dist/routes/modifications.js +81 -4
- package/dist/routes/modifications.js.map +1 -1
- package/dist/routes/static.d.ts.map +1 -1
- package/dist/routes/static.js +17 -2
- package/dist/routes/static.js.map +1 -1
- package/dist/server.d.ts +20 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +530 -35
- package/dist/server.js.map +1 -1
- package/dist/services/AuthService.d.ts +1 -1
- package/dist/services/AuthService.js +1 -1
- package/dist/services/CSSTokenWriter.js +1 -1
- package/dist/services/CommentVariationService.js +1 -1
- package/dist/services/ConfigManager.d.ts.map +1 -1
- package/dist/services/ConfigManager.js +13 -0
- package/dist/services/ConfigManager.js.map +1 -1
- package/dist/services/FeatureFlagService.d.ts +18 -0
- package/dist/services/FeatureFlagService.d.ts.map +1 -0
- package/dist/services/FeatureFlagService.js +62 -0
- package/dist/services/FeatureFlagService.js.map +1 -0
- package/dist/services/HostedDemoAuthSessionService.d.ts +42 -0
- package/dist/services/HostedDemoAuthSessionService.d.ts.map +1 -0
- package/dist/services/HostedDemoAuthSessionService.js +179 -0
- package/dist/services/HostedDemoAuthSessionService.js.map +1 -0
- package/dist/services/HostedDemoAuthSessionStore.d.ts +43 -0
- package/dist/services/HostedDemoAuthSessionStore.d.ts.map +1 -0
- package/dist/services/HostedDemoAuthSessionStore.js +90 -0
- package/dist/services/HostedDemoAuthSessionStore.js.map +1 -0
- package/dist/services/HostedDemoSessionService.d.ts +91 -0
- package/dist/services/HostedDemoSessionService.d.ts.map +1 -0
- package/dist/services/HostedDemoSessionService.js +568 -0
- package/dist/services/HostedDemoSessionService.js.map +1 -0
- package/dist/services/HostedDemoSessionStore.d.ts +49 -0
- package/dist/services/HostedDemoSessionStore.d.ts.map +1 -0
- package/dist/services/HostedDemoSessionStore.js +90 -0
- package/dist/services/HostedDemoSessionStore.js.map +1 -0
- package/dist/services/ProjectDetectionService.d.ts +2 -2
- package/dist/services/ProjectDetectionService.d.ts.map +1 -1
- package/dist/services/ProjectDetectionService.js +26 -9
- package/dist/services/ProjectDetectionService.js.map +1 -1
- package/dist/services/RequestAuthContext.d.ts +8 -0
- package/dist/services/RequestAuthContext.d.ts.map +1 -0
- package/dist/services/RequestAuthContext.js +14 -0
- package/dist/services/RequestAuthContext.js.map +1 -0
- package/dist/services/SessionBridgeService.d.ts +3 -2
- package/dist/services/SessionBridgeService.d.ts.map +1 -1
- package/dist/services/SessionBridgeService.js +6 -8
- package/dist/services/SessionBridgeService.js.map +1 -1
- package/dist/services/TailwindConfigWriter.js +1 -1
- package/dist/services/TelemetryService.d.ts +95 -0
- package/dist/services/TelemetryService.d.ts.map +1 -1
- package/dist/services/TelemetryService.js +198 -0
- package/dist/services/TelemetryService.js.map +1 -1
- package/dist/services/accessTokenRefresh.d.ts +36 -0
- package/dist/services/accessTokenRefresh.d.ts.map +1 -0
- package/dist/services/accessTokenRefresh.js +102 -0
- package/dist/services/accessTokenRefresh.js.map +1 -0
- package/dist/services/agent/AgentCore.d.ts +1 -1
- package/dist/services/agent/AgentCore.js +2 -2
- package/dist/services/agent/AgentCore.js.map +1 -1
- package/dist/services/agent/AgentModService.d.ts +1 -1
- package/dist/services/agent/AgentModService.js +1 -1
- package/dist/services/hostedDemoSessionAuthRefresh.d.ts +18 -0
- package/dist/services/hostedDemoSessionAuthRefresh.d.ts.map +1 -0
- package/dist/services/hostedDemoSessionAuthRefresh.js +39 -0
- package/dist/services/hostedDemoSessionAuthRefresh.js.map +1 -0
- package/dist/utils/shouldRecordHostedDemoSessionAction.d.ts +8 -0
- package/dist/utils/shouldRecordHostedDemoSessionAction.d.ts.map +1 -0
- package/dist/utils/shouldRecordHostedDemoSessionAction.js +27 -0
- package/dist/utils/shouldRecordHostedDemoSessionAction.js.map +1 -0
- package/dist/utils/skills/claude-skill.d.ts +2 -2
- package/dist/utils/skills/claude-skill.d.ts.map +1 -1
- package/dist/utils/skills/claude-skill.js +29 -10
- package/dist/utils/skills/claude-skill.js.map +1 -1
- package/dist/utils/skills/cursor-rules.d.ts +2 -2
- package/dist/utils/skills/cursor-rules.d.ts.map +1 -1
- package/dist/utils/skills/cursor-rules.js +12 -8
- package/dist/utils/skills/cursor-rules.js.map +1 -1
- package/package.json +4 -2
- package/src/ui/dist/assets/logo.png +0 -0
- package/src/ui/dist/assets/main-BxL1kNtz.css +1 -0
- package/src/ui/dist/assets/{main-Dnm69Obb.js → main-BxYkrTpy.js} +138 -134
- package/src/ui/dist/assets/rivet.svg +3 -0
- package/src/ui/dist/fonts/Goldman-Bold.woff2 +0 -0
- package/src/ui/dist/fonts/Goldman-Regular.woff2 +0 -0
- package/src/ui/dist/index.html +3 -3
- package/src/ui/dist/assets/main-BsJYpJMo.css +0 -1
package/dist/server.js
CHANGED
|
@@ -54,8 +54,18 @@ const proxy_config_1 = require("./proxy-middleware/proxy-config");
|
|
|
54
54
|
const StaticFileService_1 = require("./services/StaticFileService");
|
|
55
55
|
const static_1 = require("./routes/static");
|
|
56
56
|
const ConfigManager_1 = require("./services/ConfigManager");
|
|
57
|
-
const
|
|
57
|
+
const evaluateFlags_1 = require("./config/evaluateFlags");
|
|
58
|
+
const FeatureFlagService_1 = require("./services/FeatureFlagService");
|
|
58
59
|
const mcp_1 = require("./routes/mcp");
|
|
60
|
+
const demo_1 = require("./routes/demo");
|
|
61
|
+
const HostedDemoSessionService_1 = require("./services/HostedDemoSessionService");
|
|
62
|
+
const HostedDemoSessionStore_1 = require("./services/HostedDemoSessionStore");
|
|
63
|
+
const http_proxy_middleware_1 = require("http-proxy-middleware");
|
|
64
|
+
const HostedDemoAuthSessionService_1 = require("./services/HostedDemoAuthSessionService");
|
|
65
|
+
const HostedDemoAuthSessionStore_1 = require("./services/HostedDemoAuthSessionStore");
|
|
66
|
+
const shouldRecordHostedDemoSessionAction_1 = require("./utils/shouldRecordHostedDemoSessionAction");
|
|
67
|
+
const hostedDemoSessionAuthRefresh_1 = require("./services/hostedDemoSessionAuthRefresh");
|
|
68
|
+
const RequestAuthContext_1 = require("./services/RequestAuthContext");
|
|
59
69
|
const log = (0, index_core_1.createLogger)('RivetServer');
|
|
60
70
|
/**
|
|
61
71
|
* Resolve UI dist path for both development and packaged (desktop app) environments
|
|
@@ -85,14 +95,17 @@ exports.DIST_UI_PATH = resolveUiPath();
|
|
|
85
95
|
const startServer = async (options) => {
|
|
86
96
|
const { userPort, userHost, telemetry, projectPath, framework, staticEntry, styleFramework, tailwindConfig, sessionBridge, mcpEditor, skipProcessHandlers, } = options;
|
|
87
97
|
const userPortWithFallback = userPort ?? _1.DEFAULT_USER_PORT;
|
|
88
|
-
const userHostWithFallback = userHost ?? '
|
|
98
|
+
const userHostWithFallback = userHost ?? 'localhost';
|
|
99
|
+
const isDemoModeEnabled = options.demoMode?.enabled ?? false;
|
|
100
|
+
const requiresDemoAuth = options.demoMode?.requireSignedInUsers ?? true;
|
|
101
|
+
const isGitEnabled = options.isGitEnabled ?? !isDemoModeEnabled;
|
|
89
102
|
if (process.env.SENTRY_DSN) {
|
|
90
103
|
Sentry.init({
|
|
91
104
|
dsn: process.env.SENTRY_DSN,
|
|
92
105
|
environment: process.env.NODE_ENV ?? 'development',
|
|
93
106
|
});
|
|
94
107
|
}
|
|
95
|
-
const sessionService = new index_core_1.SessionService(projectPath,
|
|
108
|
+
const sessionService = new index_core_1.SessionService(projectPath, isGitEnabled);
|
|
96
109
|
// Initialize git session
|
|
97
110
|
try {
|
|
98
111
|
await sessionService.initializeSession();
|
|
@@ -111,12 +124,14 @@ const startServer = async (options) => {
|
|
|
111
124
|
if (!skipProcessHandlers) {
|
|
112
125
|
const shutdownAndExit = async (code) => {
|
|
113
126
|
log.info('Server shutting down, cleaning up...');
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
127
|
+
const results = await Promise.allSettled([
|
|
128
|
+
telemetry?.shutdown(),
|
|
129
|
+
(0, FeatureFlagService_1.getFeatureFlagService)().shutdown(),
|
|
130
|
+
]);
|
|
131
|
+
const labels = ['telemetry', 'feature flags'];
|
|
132
|
+
for (let i = 0; i < results.length; i++) {
|
|
133
|
+
if (results[i].status === 'rejected') {
|
|
134
|
+
log.error(`Failed to cleanup ${labels[i]}:`, results[i].reason);
|
|
120
135
|
}
|
|
121
136
|
}
|
|
122
137
|
process.exit(code);
|
|
@@ -136,24 +151,43 @@ const startServer = async (options) => {
|
|
|
136
151
|
}
|
|
137
152
|
const listenPort = options.rivetPort ?? _1.DEFAULT_PORT;
|
|
138
153
|
const app = (0, express_1.default)();
|
|
154
|
+
const corsOrigins = new Set([
|
|
155
|
+
`http://localhost:${listenPort}`,
|
|
156
|
+
`http://127.0.0.1:${listenPort}`,
|
|
157
|
+
]);
|
|
158
|
+
const rawCorsOrigins = process.env.RIVET_CORS_ORIGINS;
|
|
159
|
+
if (rawCorsOrigins) {
|
|
160
|
+
rawCorsOrigins
|
|
161
|
+
.split(',')
|
|
162
|
+
.map((origin) => origin.trim())
|
|
163
|
+
.filter((origin) => origin.length > 0)
|
|
164
|
+
.forEach((origin) => corsOrigins.add(origin));
|
|
165
|
+
}
|
|
166
|
+
if (isDemoModeEnabled) {
|
|
167
|
+
corsOrigins.add('https://rivet.design');
|
|
168
|
+
corsOrigins.add('https://demo.rivet.design');
|
|
169
|
+
}
|
|
139
170
|
// CORS configuration for local development
|
|
140
171
|
app.use((0, cors_1.default)({
|
|
141
|
-
origin: [
|
|
142
|
-
`http://localhost:${listenPort}`,
|
|
143
|
-
`http://127.0.0.1:${listenPort}`,
|
|
144
|
-
],
|
|
172
|
+
origin: [...corsOrigins],
|
|
145
173
|
credentials: true,
|
|
146
174
|
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
|
147
175
|
allowedHeaders: ['Content-Type', 'Authorization'],
|
|
148
176
|
}));
|
|
149
|
-
|
|
177
|
+
const jsonBodyParser = express_1.default.json({ limit: '50mb' });
|
|
178
|
+
app.use((req, res, next) => {
|
|
179
|
+
if (req.originalUrl.startsWith('/try/')) {
|
|
180
|
+
next();
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
jsonBodyParser(req, res, next);
|
|
184
|
+
});
|
|
150
185
|
// Request logging middleware
|
|
151
186
|
app.use((req, res, next) => {
|
|
152
187
|
log.debug(`${req.method} ${req.originalUrl}`);
|
|
153
188
|
next();
|
|
154
189
|
});
|
|
155
|
-
|
|
156
|
-
app.use('/rivet', express_1.default.static(exports.DIST_UI_PATH, {
|
|
190
|
+
const uiStaticOptions = {
|
|
157
191
|
setHeaders: (res) => {
|
|
158
192
|
// Prevent aggressive caching of the UI bundle during development
|
|
159
193
|
if (process.env.NODE_ENV !== 'production') {
|
|
@@ -162,21 +196,376 @@ const startServer = async (options) => {
|
|
|
162
196
|
res.setHeader('Expires', '0');
|
|
163
197
|
}
|
|
164
198
|
},
|
|
165
|
-
}
|
|
199
|
+
};
|
|
200
|
+
// Serve hashed UI assets from root so both / and /rivet entrypoints can load them.
|
|
201
|
+
app.use('/assets', express_1.default.static(path_1.default.join(exports.DIST_UI_PATH, 'assets'), uiStaticOptions));
|
|
202
|
+
// Serve public font files used by auth and hosted demo UI.
|
|
203
|
+
app.use('/fonts', express_1.default.static(path_1.default.join(exports.DIST_UI_PATH, 'fonts'), uiStaticOptions));
|
|
204
|
+
// Serve favicon at root for hosted/static deployments.
|
|
205
|
+
app.get('/favicon.ico', (_req, res) => {
|
|
206
|
+
res.sendFile('favicon.ico', { root: exports.DIST_UI_PATH });
|
|
207
|
+
});
|
|
208
|
+
// Serve Rivet UI at /rivet for non-static modes.
|
|
209
|
+
if (framework !== 'static') {
|
|
210
|
+
app.use('/rivet', express_1.default.static(exports.DIST_UI_PATH, uiStaticOptions));
|
|
211
|
+
}
|
|
166
212
|
log.info(`Serving UI assets from: ${exports.DIST_UI_PATH}`);
|
|
167
|
-
// Static mode -
|
|
213
|
+
// Static mode - use root entrypoint for Rivet UI (no dev server to proxy to)
|
|
168
214
|
if (framework === 'static') {
|
|
169
215
|
app.get('/', (req, res) => {
|
|
170
|
-
log.debug(`${framework} mode -
|
|
171
|
-
res.
|
|
216
|
+
log.debug(`${framework} mode - serving Rivet at root`);
|
|
217
|
+
res.sendFile('index.html', { root: exports.DIST_UI_PATH });
|
|
172
218
|
});
|
|
173
219
|
}
|
|
174
220
|
// For web frameworks, root route falls through to catch-all proxy (no special handling needed)
|
|
221
|
+
const getBearerTokenFromHeader = (authorizationHeader) => {
|
|
222
|
+
if (typeof authorizationHeader !== 'string') {
|
|
223
|
+
return undefined;
|
|
224
|
+
}
|
|
225
|
+
if (!authorizationHeader.startsWith('Bearer ')) {
|
|
226
|
+
return undefined;
|
|
227
|
+
}
|
|
228
|
+
const token = authorizationHeader.slice('Bearer '.length).trim();
|
|
229
|
+
return token.length > 0 ? token : undefined;
|
|
230
|
+
};
|
|
231
|
+
/**
|
|
232
|
+
* @effect Hydrates request auth context from Authorization header for all modes.
|
|
233
|
+
* @deps Runs per request and reads req.headers.authorization.
|
|
234
|
+
*/
|
|
235
|
+
app.use((req, _res, next) => {
|
|
236
|
+
const headerToken = getBearerTokenFromHeader(req.headers.authorization);
|
|
237
|
+
if (!headerToken) {
|
|
238
|
+
next();
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
(0, RequestAuthContext_1.runWithRequestAuthContext)({ token: headerToken }, () => {
|
|
242
|
+
next();
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
const demoAuthSessionService = isDemoModeEnabled
|
|
246
|
+
? new HostedDemoAuthSessionService_1.HostedDemoAuthSessionService({
|
|
247
|
+
store: options.demoMode?.authRedisUrl
|
|
248
|
+
? new HostedDemoAuthSessionStore_1.RedisHostedDemoAuthSessionStore({
|
|
249
|
+
redisUrl: options.demoMode.authRedisUrl,
|
|
250
|
+
keyPrefix: options.demoMode.authRedisKeyPrefix ??
|
|
251
|
+
'rivet:hosted-demo:auth-session',
|
|
252
|
+
})
|
|
253
|
+
: undefined,
|
|
254
|
+
})
|
|
255
|
+
: null;
|
|
256
|
+
if (demoAuthSessionService) {
|
|
257
|
+
await demoAuthSessionService.initialize();
|
|
258
|
+
}
|
|
259
|
+
if (demoAuthSessionService) {
|
|
260
|
+
app.use(async (req, res, next) => {
|
|
261
|
+
try {
|
|
262
|
+
const sessionId = demoAuthSessionService.getOrCreateSessionId(req, res);
|
|
263
|
+
const auth = await (0, hostedDemoSessionAuthRefresh_1.resolveHostedDemoSessionAuthWithRefresh)({
|
|
264
|
+
demoAuthSessionService,
|
|
265
|
+
sessionId,
|
|
266
|
+
res,
|
|
267
|
+
getProxyUrl: () => (0, ConfigManager_1.getConfigManager)().getProxyUrl(),
|
|
268
|
+
});
|
|
269
|
+
const headerToken = getBearerTokenFromHeader(req.headers.authorization);
|
|
270
|
+
res.locals.demoAuthSessionId = sessionId;
|
|
271
|
+
(0, RequestAuthContext_1.runWithRequestAuthContext)({
|
|
272
|
+
token: auth?.token ?? headerToken,
|
|
273
|
+
refreshToken: auth?.refreshToken,
|
|
274
|
+
email: auth?.email,
|
|
275
|
+
}, () => {
|
|
276
|
+
next();
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
catch (error) {
|
|
280
|
+
next(error);
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
const isDemoRequestAuthenticated = (req) => {
|
|
285
|
+
if (!isDemoModeEnabled || !requiresDemoAuth) {
|
|
286
|
+
return true;
|
|
287
|
+
}
|
|
288
|
+
return Boolean(demoAuthSessionService?.getAuthForRequest(req));
|
|
289
|
+
};
|
|
290
|
+
const getDemoRequestOwnerUserId = (req) => {
|
|
291
|
+
if (!requiresDemoAuth) {
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
const cookieAuthEmail = demoAuthSessionService
|
|
295
|
+
?.getAuthForRequest(req)
|
|
296
|
+
?.email?.trim()
|
|
297
|
+
.toLowerCase();
|
|
298
|
+
if (cookieAuthEmail) {
|
|
299
|
+
return cookieAuthEmail;
|
|
300
|
+
}
|
|
301
|
+
const email = (0, ConfigManager_1.getConfigManager)().getEmail();
|
|
302
|
+
if (!email) {
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
return email.trim().toLowerCase();
|
|
306
|
+
};
|
|
307
|
+
const isDemoAuthExemptApiPath = (pathName) => {
|
|
308
|
+
if (pathName === '/config' || pathName === '/health' || pathName === '/auth/store') {
|
|
309
|
+
return true;
|
|
310
|
+
}
|
|
311
|
+
if (pathName === '/demo/session' || pathName.startsWith('/demo/session/')) {
|
|
312
|
+
return true;
|
|
313
|
+
}
|
|
314
|
+
return false;
|
|
315
|
+
};
|
|
316
|
+
const isHostedRootApiPath = (pathName) => {
|
|
317
|
+
if (pathName === '/config' ||
|
|
318
|
+
pathName === '/health' ||
|
|
319
|
+
pathName === '/auth/store' ||
|
|
320
|
+
pathName === '/support') {
|
|
321
|
+
return true;
|
|
322
|
+
}
|
|
323
|
+
if (pathName === '/demo/session' || pathName.startsWith('/demo/session/')) {
|
|
324
|
+
return true;
|
|
325
|
+
}
|
|
326
|
+
return false;
|
|
327
|
+
};
|
|
328
|
+
let demoSessionService = null;
|
|
329
|
+
if (isDemoModeEnabled && options.demoMode?.allowSessionProvisioning !== false) {
|
|
330
|
+
const sessionMetadataRedisUrl = options.demoMode?.sessionMetadataRedisUrl ??
|
|
331
|
+
options.demoMode?.authRedisUrl;
|
|
332
|
+
const sessionMetadataStore = sessionMetadataRedisUrl
|
|
333
|
+
? new HostedDemoSessionStore_1.RedisHostedDemoSessionStore({
|
|
334
|
+
redisUrl: sessionMetadataRedisUrl,
|
|
335
|
+
keyPrefix: options.demoMode?.sessionMetadataRedisKeyPrefix ??
|
|
336
|
+
'rivet:hosted-demo:demo-session',
|
|
337
|
+
})
|
|
338
|
+
: undefined;
|
|
339
|
+
demoSessionService = new HostedDemoSessionService_1.HostedDemoSessionService({
|
|
340
|
+
repoPath: projectPath,
|
|
341
|
+
telemetry,
|
|
342
|
+
templateProjectPath: options.demoMode?.templateProjectPath ?? 'examples/microsoft-paint',
|
|
343
|
+
worktreesRootPath: options.demoMode?.worktreesRootPath,
|
|
344
|
+
sessionTtlMs: options.demoMode?.sessionTtlMs,
|
|
345
|
+
sessionIdleTtlMs: options.demoMode?.sessionIdleTtlMs,
|
|
346
|
+
maxActiveSessions: options.demoMode?.maxActiveSessions,
|
|
347
|
+
baseRivetPort: options.demoMode?.baseRivetPort,
|
|
348
|
+
publicBaseUrl: options.demoMode?.publicBaseUrl,
|
|
349
|
+
sessionMetadataStore,
|
|
350
|
+
});
|
|
351
|
+
await demoSessionService.initialize();
|
|
352
|
+
}
|
|
353
|
+
if (isDemoModeEnabled && requiresDemoAuth) {
|
|
354
|
+
app.use('/api', (req, res, next) => {
|
|
355
|
+
if (isDemoAuthExemptApiPath(req.path)) {
|
|
356
|
+
next();
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
if (!isDemoRequestAuthenticated(req)) {
|
|
360
|
+
res.status(401).json({ error: 'Authentication required' });
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
next();
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
if (demoSessionService) {
|
|
367
|
+
const activeSessionApiProxy = (0, http_proxy_middleware_1.createProxyMiddleware)({
|
|
368
|
+
changeOrigin: true,
|
|
369
|
+
pathRewrite: (pathName) => `/api${pathName}`,
|
|
370
|
+
on: {
|
|
371
|
+
proxyReq: (proxyReq, req) => {
|
|
372
|
+
(0, http_proxy_middleware_1.fixRequestBody)(proxyReq, req);
|
|
373
|
+
},
|
|
374
|
+
},
|
|
375
|
+
router: (req) => {
|
|
376
|
+
const activeDemoSessionId = demoAuthSessionService?.getActiveDemoSessionIdForRequest(req) ?? null;
|
|
377
|
+
if (!activeDemoSessionId) {
|
|
378
|
+
return undefined;
|
|
379
|
+
}
|
|
380
|
+
return demoSessionService.getSessionProxyTarget(activeDemoSessionId) ?? undefined;
|
|
381
|
+
},
|
|
382
|
+
});
|
|
383
|
+
app.use('/api', (req, res, next) => {
|
|
384
|
+
if (isHostedRootApiPath(req.path)) {
|
|
385
|
+
next();
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
const activeDemoSessionId = demoAuthSessionService?.getActiveDemoSessionIdForRequest(req) ?? null;
|
|
389
|
+
if (!activeDemoSessionId) {
|
|
390
|
+
res.status(409).json({
|
|
391
|
+
error: 'No active demo session. Call /api/demo/session first.',
|
|
392
|
+
});
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
const sessionOwnerUserId = demoSessionService.getSessionOwnerUserId(activeDemoSessionId);
|
|
396
|
+
const requestOwnerUserId = getDemoRequestOwnerUserId(req);
|
|
397
|
+
if (requiresDemoAuth &&
|
|
398
|
+
sessionOwnerUserId &&
|
|
399
|
+
requestOwnerUserId &&
|
|
400
|
+
sessionOwnerUserId !== requestOwnerUserId) {
|
|
401
|
+
res.status(403).json({ error: 'Session access denied' });
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
const auth = demoAuthSessionService?.getAuthForRequest(req);
|
|
405
|
+
if (auth?.token) {
|
|
406
|
+
req.headers.authorization = `Bearer ${auth.token}`;
|
|
407
|
+
}
|
|
408
|
+
void (async () => {
|
|
409
|
+
try {
|
|
410
|
+
await demoSessionService.ensureRuntimeReady(activeDemoSessionId);
|
|
411
|
+
}
|
|
412
|
+
catch (error) {
|
|
413
|
+
const message = error instanceof Error ? error.message : '';
|
|
414
|
+
if (message === 'Session not found') {
|
|
415
|
+
demoAuthSessionService?.clearActiveDemoSessionIdForRequest(req, res);
|
|
416
|
+
if (!res.headersSent) {
|
|
417
|
+
res.status(409).json({
|
|
418
|
+
error: 'No active demo session. Call /api/demo/session first.',
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
log.warn('Hosted demo runtime rehydration failed for active session API', error);
|
|
424
|
+
if (!res.headersSent) {
|
|
425
|
+
res.status(503).json({
|
|
426
|
+
error: 'Demo session runtime unavailable',
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
const target = demoSessionService.getSessionProxyTarget(activeDemoSessionId);
|
|
432
|
+
if (!target) {
|
|
433
|
+
demoAuthSessionService?.clearActiveDemoSessionIdForRequest(req, res);
|
|
434
|
+
if (!res.headersSent) {
|
|
435
|
+
res.status(409).json({
|
|
436
|
+
error: 'No active demo session. Call /api/demo/session first.',
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
if ((0, shouldRecordHostedDemoSessionAction_1.shouldRecordHostedDemoSessionAction)(req)) {
|
|
442
|
+
try {
|
|
443
|
+
await demoSessionService.recordSessionAction(activeDemoSessionId);
|
|
444
|
+
}
|
|
445
|
+
catch (error) {
|
|
446
|
+
log.warn('Failed to persist hosted demo session action timestamp', error);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
activeSessionApiProxy(req, res, next);
|
|
450
|
+
})();
|
|
451
|
+
});
|
|
452
|
+
}
|
|
175
453
|
// API routes for Rivet functionality
|
|
176
454
|
app.use('/api', (0, components_1.createComponentRouter)(projectPath, telemetry));
|
|
177
455
|
app.use('/api', (0, modifications_1.createModificationRouter)(projectPath, telemetry));
|
|
178
456
|
app.use('/api', (0, design_1.createDesignRouter)(projectPath));
|
|
179
|
-
|
|
457
|
+
if (!isGitEnabled) {
|
|
458
|
+
log.info('Git routes disabled for this server instance');
|
|
459
|
+
}
|
|
460
|
+
app.use('/api', (0, git_1.createGitRouter)(sessionService, projectPath, { isGitEnabled }));
|
|
461
|
+
let trySessionProxy = null;
|
|
462
|
+
if (demoSessionService) {
|
|
463
|
+
app.use('/api', (0, demo_1.createDemoRouter)(demoSessionService, {
|
|
464
|
+
requireSignedInUsers: requiresDemoAuth,
|
|
465
|
+
getActiveDemoSessionIdForRequest: demoAuthSessionService
|
|
466
|
+
? (req) => demoAuthSessionService.getActiveDemoSessionIdForRequest(req)
|
|
467
|
+
: undefined,
|
|
468
|
+
storeActiveDemoSessionIdForRequest: demoAuthSessionService
|
|
469
|
+
? (req, res, demoSessionId) => demoAuthSessionService.storeActiveDemoSessionIdForRequest(req, res, demoSessionId)
|
|
470
|
+
: undefined,
|
|
471
|
+
clearActiveDemoSessionIdForRequest: demoAuthSessionService
|
|
472
|
+
? (req, res) => demoAuthSessionService.clearActiveDemoSessionIdForRequest(req, res)
|
|
473
|
+
: undefined,
|
|
474
|
+
telemetry,
|
|
475
|
+
deploymentEnv: process.env.NODE_ENV ?? 'development',
|
|
476
|
+
}));
|
|
477
|
+
const mountedTrySessionProxy = (0, http_proxy_middleware_1.createProxyMiddleware)({
|
|
478
|
+
changeOrigin: true,
|
|
479
|
+
ws: true,
|
|
480
|
+
router: (req) => {
|
|
481
|
+
const sessionIdParam = req.params.sessionId;
|
|
482
|
+
const sessionId = Array.isArray(sessionIdParam)
|
|
483
|
+
? sessionIdParam[0]
|
|
484
|
+
: sessionIdParam;
|
|
485
|
+
return demoSessionService.getSessionProxyTarget(sessionId) ?? undefined;
|
|
486
|
+
},
|
|
487
|
+
});
|
|
488
|
+
trySessionProxy = mountedTrySessionProxy;
|
|
489
|
+
// Browsers resolve default favicon relative to the document URL under /try/:id/,
|
|
490
|
+
// so requests hit this path instead of /. Serve the Rivet UI favicon here too.
|
|
491
|
+
app.get('/try/:sessionId/favicon.ico', (_req, res) => {
|
|
492
|
+
res.sendFile('favicon.ico', { root: exports.DIST_UI_PATH });
|
|
493
|
+
});
|
|
494
|
+
app.use('/try/:sessionId', (req, res, next) => {
|
|
495
|
+
if (!isDemoRequestAuthenticated(req)) {
|
|
496
|
+
if (req.accepts('html')) {
|
|
497
|
+
res.redirect('/');
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
res.status(401).json({ error: 'Authentication required' });
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
const sessionIdParam = req.params.sessionId;
|
|
504
|
+
const sessionId = Array.isArray(sessionIdParam)
|
|
505
|
+
? sessionIdParam[0]
|
|
506
|
+
: sessionIdParam;
|
|
507
|
+
const sessionOwnerUserId = demoSessionService.getSessionOwnerUserId(sessionId);
|
|
508
|
+
const requestOwnerUserId = getDemoRequestOwnerUserId(req);
|
|
509
|
+
if (requiresDemoAuth &&
|
|
510
|
+
sessionOwnerUserId &&
|
|
511
|
+
requestOwnerUserId &&
|
|
512
|
+
sessionOwnerUserId !== requestOwnerUserId) {
|
|
513
|
+
res.status(403).json({ error: 'Session access denied' });
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
const auth = demoAuthSessionService?.getAuthForRequest(req);
|
|
517
|
+
if (auth?.token) {
|
|
518
|
+
req.headers.authorization = `Bearer ${auth.token}`;
|
|
519
|
+
}
|
|
520
|
+
void (async () => {
|
|
521
|
+
try {
|
|
522
|
+
await demoSessionService.ensureRuntimeReady(sessionId);
|
|
523
|
+
}
|
|
524
|
+
catch (error) {
|
|
525
|
+
const message = error instanceof Error ? error.message : '';
|
|
526
|
+
if (message === 'Session not found') {
|
|
527
|
+
demoAuthSessionService?.clearActiveDemoSessionIdForRequest(req, res);
|
|
528
|
+
telemetry?.trackTryoutProxySessionMissing({
|
|
529
|
+
demoSessionId: sessionId,
|
|
530
|
+
deploymentEnv: process.env.NODE_ENV ?? 'development',
|
|
531
|
+
});
|
|
532
|
+
if (!res.headersSent) {
|
|
533
|
+
res.status(404).json({ error: 'Session not found' });
|
|
534
|
+
}
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
log.warn('Hosted demo runtime rehydration failed for try session', error);
|
|
538
|
+
if (!res.headersSent) {
|
|
539
|
+
res.status(503).json({
|
|
540
|
+
error: 'Demo session runtime unavailable',
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
const target = demoSessionService.getSessionProxyTarget(sessionId);
|
|
546
|
+
if (!target) {
|
|
547
|
+
demoAuthSessionService?.clearActiveDemoSessionIdForRequest(req, res);
|
|
548
|
+
telemetry?.trackTryoutProxySessionMissing({
|
|
549
|
+
demoSessionId: sessionId,
|
|
550
|
+
deploymentEnv: process.env.NODE_ENV ?? 'development',
|
|
551
|
+
});
|
|
552
|
+
if (!res.headersSent) {
|
|
553
|
+
res.status(404).json({ error: 'Session not found' });
|
|
554
|
+
}
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
if ((0, shouldRecordHostedDemoSessionAction_1.shouldRecordHostedDemoSessionAction)(req)) {
|
|
558
|
+
try {
|
|
559
|
+
await demoSessionService.recordSessionAction(sessionId);
|
|
560
|
+
}
|
|
561
|
+
catch (error) {
|
|
562
|
+
log.warn('Failed to persist hosted demo session action timestamp', error);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
mountedTrySessionProxy(req, res, next);
|
|
566
|
+
})();
|
|
567
|
+
});
|
|
568
|
+
}
|
|
180
569
|
app.use('/api', (0, tokens_1.createTokenRouter)(projectPath, styleFramework, tailwindConfig));
|
|
181
570
|
app.use('/api', (0, support_1.createSupportRouter)(telemetry));
|
|
182
571
|
// Static routes (only for static projects)
|
|
@@ -197,28 +586,80 @@ const startServer = async (options) => {
|
|
|
197
586
|
});
|
|
198
587
|
});
|
|
199
588
|
// Config endpoint
|
|
200
|
-
app.get('/api/config', (req, res) => {
|
|
589
|
+
app.get('/api/config', async (req, res) => {
|
|
201
590
|
const configManager = (0, ConfigManager_1.getConfigManager)();
|
|
591
|
+
const demoSessionId = res.locals.demoAuthSessionId;
|
|
592
|
+
const demoAuth = demoAuthSessionService?.getAuthForSessionId(demoSessionId ?? '');
|
|
593
|
+
const isAuthenticatedInDemoMode = Boolean(demoAuth?.token);
|
|
594
|
+
const isAuthenticated = demoAuthSessionService
|
|
595
|
+
? isAuthenticatedInDemoMode
|
|
596
|
+
: configManager.isAuthenticated();
|
|
597
|
+
const email = demoAuthSessionService ? demoAuth?.email : configManager.getEmail();
|
|
598
|
+
const activeDemoSessionId = demoAuth?.activeDemoSessionId ?? null;
|
|
599
|
+
const activeSession = activeDemoSessionId
|
|
600
|
+
? demoSessionService?.getSession(activeDemoSessionId)
|
|
601
|
+
: null;
|
|
602
|
+
const featureFlags = await (0, evaluateFlags_1.getFeatureFlags)();
|
|
202
603
|
res.json({
|
|
203
|
-
agentModeAvailable:
|
|
204
|
-
email
|
|
604
|
+
agentModeAvailable: isAuthenticated,
|
|
605
|
+
email,
|
|
606
|
+
activeDemoSessionId,
|
|
205
607
|
isMCPSession: sessionBridge?.isActive() ?? false,
|
|
206
608
|
mcpEditor: mcpEditor ?? null,
|
|
207
|
-
featureFlags
|
|
208
|
-
projectPath,
|
|
609
|
+
featureFlags,
|
|
610
|
+
projectPath: activeSession?.projectPath ?? projectPath,
|
|
611
|
+
demoMode: isDemoModeEnabled,
|
|
209
612
|
});
|
|
210
613
|
});
|
|
211
|
-
// Store auth credentials
|
|
212
|
-
//
|
|
213
|
-
// but the local ConfigManager also needs the email and tokens so that
|
|
214
|
-
// /api/config returns the email on subsequent page loads.
|
|
614
|
+
// Store auth credentials after browser-based OAuth completion.
|
|
615
|
+
// Hosted demo mode stores auth by browser session; non-demo keeps local config behavior.
|
|
215
616
|
app.post('/api/auth/store', (req, res) => {
|
|
216
617
|
const { email, token, refreshToken } = req.body ?? {};
|
|
217
618
|
if (!email || !token) {
|
|
619
|
+
if (demoAuthSessionService) {
|
|
620
|
+
telemetry?.trackTryoutAuthStoreFailed({
|
|
621
|
+
reason: 'missing_email_or_token',
|
|
622
|
+
statusCode: 400,
|
|
623
|
+
deploymentEnv: process.env.NODE_ENV ?? 'development',
|
|
624
|
+
});
|
|
625
|
+
}
|
|
218
626
|
return res.status(400).json({ error: 'Missing email or token' });
|
|
219
627
|
}
|
|
220
|
-
|
|
221
|
-
|
|
628
|
+
try {
|
|
629
|
+
if (demoAuthSessionService) {
|
|
630
|
+
const sessionId = res.locals.demoAuthSessionId;
|
|
631
|
+
const demoSessionId = sessionId
|
|
632
|
+
? demoAuthSessionService.storeAuthForSessionId(sessionId, res, {
|
|
633
|
+
token,
|
|
634
|
+
email,
|
|
635
|
+
refreshToken,
|
|
636
|
+
})
|
|
637
|
+
: demoAuthSessionService.storeAuthForRequest(req, res, {
|
|
638
|
+
token,
|
|
639
|
+
email,
|
|
640
|
+
refreshToken,
|
|
641
|
+
});
|
|
642
|
+
telemetry?.trackTryoutAuthStoreCompleted({
|
|
643
|
+
demoSessionId,
|
|
644
|
+
hasRefreshToken: Boolean(refreshToken),
|
|
645
|
+
deploymentEnv: process.env.NODE_ENV ?? 'development',
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
else {
|
|
649
|
+
const configManager = (0, ConfigManager_1.getConfigManager)();
|
|
650
|
+
configManager.setAuth(token, email, refreshToken);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
catch (error) {
|
|
654
|
+
if (demoAuthSessionService) {
|
|
655
|
+
telemetry?.trackTryoutAuthStoreFailed({
|
|
656
|
+
reason: error instanceof Error ? error.message : 'auth_store_failed',
|
|
657
|
+
statusCode: 500,
|
|
658
|
+
deploymentEnv: process.env.NODE_ENV ?? 'development',
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
return res.status(500).json({ error: 'Failed to store auth credentials' });
|
|
662
|
+
}
|
|
222
663
|
telemetry?.identifyUser();
|
|
223
664
|
log.info(`Stored auth for ${email} via browser OAuth`);
|
|
224
665
|
res.json({ success: true });
|
|
@@ -253,6 +694,12 @@ const startServer = async (options) => {
|
|
|
253
694
|
}
|
|
254
695
|
// Global error handler — catches errors passed via next(err) in route handlers
|
|
255
696
|
app.use((err, req, res, _next) => {
|
|
697
|
+
const requestAborted = err.code === 'ECONNABORTED' ||
|
|
698
|
+
err.type === 'request.aborted';
|
|
699
|
+
if (requestAborted) {
|
|
700
|
+
log.debug('Request aborted by client');
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
256
703
|
log.error('Unhandled route error:', err);
|
|
257
704
|
telemetry?.captureException(err, 'express_error_handler');
|
|
258
705
|
if (!res.headersSent) {
|
|
@@ -279,8 +726,46 @@ const startServer = async (options) => {
|
|
|
279
726
|
});
|
|
280
727
|
// Handle WebSocket upgrade requests
|
|
281
728
|
server.on('upgrade', (req, socket, head) => {
|
|
282
|
-
const pathname = req.url;
|
|
729
|
+
const pathname = req.url?.split('?')[0] ?? '';
|
|
283
730
|
log.debug(`WebSocket upgrade request for: ${pathname}`);
|
|
731
|
+
const trySessionMatch = /^\/try\/([^/]+)/.exec(pathname);
|
|
732
|
+
if (trySessionMatch && trySessionProxy?.upgrade && demoSessionService) {
|
|
733
|
+
const sessionId = trySessionMatch[1];
|
|
734
|
+
void (async () => {
|
|
735
|
+
try {
|
|
736
|
+
await demoSessionService.ensureRuntimeReady(sessionId);
|
|
737
|
+
}
|
|
738
|
+
catch {
|
|
739
|
+
socket.destroy();
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
const target = demoSessionService.getSessionProxyTarget(sessionId);
|
|
743
|
+
if (!target) {
|
|
744
|
+
socket.destroy();
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
747
|
+
if (requiresDemoAuth) {
|
|
748
|
+
const auth = demoAuthSessionService?.getAuthForRequest(req);
|
|
749
|
+
if (!auth?.token) {
|
|
750
|
+
log.debug('Rejecting unauthenticated hosted demo upgrade request');
|
|
751
|
+
socket.destroy();
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
const sessionOwnerUserId = demoSessionService.getSessionOwnerUserId(sessionId);
|
|
755
|
+
const requestOwnerUserId = auth.email?.trim().toLowerCase() ?? null;
|
|
756
|
+
if (sessionOwnerUserId &&
|
|
757
|
+
requestOwnerUserId &&
|
|
758
|
+
sessionOwnerUserId !== requestOwnerUserId) {
|
|
759
|
+
log.debug('Rejecting non-owner hosted demo upgrade request');
|
|
760
|
+
socket.destroy();
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
log.debug('Proxying upgrade to hosted demo session for HMR');
|
|
765
|
+
trySessionProxy.upgrade(req, socket, head);
|
|
766
|
+
})();
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
284
769
|
// Proxy to user's dev server for HMR
|
|
285
770
|
if (framework !== 'static' && userDevProxy?.upgrade) {
|
|
286
771
|
log.debug('Proxying upgrade to user dev server for HMR');
|
|
@@ -295,10 +780,20 @@ const startServer = async (options) => {
|
|
|
295
780
|
return {
|
|
296
781
|
close: () => new Promise((resolve, reject) => {
|
|
297
782
|
httpServer.close((err) => {
|
|
298
|
-
if (err)
|
|
783
|
+
if (err) {
|
|
299
784
|
reject(err);
|
|
300
|
-
|
|
785
|
+
return;
|
|
786
|
+
}
|
|
787
|
+
void (async () => {
|
|
788
|
+
if (demoSessionService) {
|
|
789
|
+
await demoSessionService.shutdown();
|
|
790
|
+
}
|
|
791
|
+
demoAuthSessionService?.shutdown();
|
|
792
|
+
if (telemetry) {
|
|
793
|
+
await telemetry.shutdown();
|
|
794
|
+
}
|
|
301
795
|
resolve();
|
|
796
|
+
})();
|
|
302
797
|
});
|
|
303
798
|
// Destroy all active connections so close() resolves immediately
|
|
304
799
|
for (const socket of activeSockets) {
|