@farm.js/preview-gateway 0.1.18 → 0.1.20
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/{index-DeizJwbc.d.ts → index-D6cgJNEe.d.ts} +26 -5
- package/dist/index.d.ts +26 -5
- package/dist/index.js +94 -31
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -176,17 +176,35 @@ interface PreviewGatewayResponse {
|
|
|
176
176
|
encoding?: "base64";
|
|
177
177
|
}
|
|
178
178
|
interface PreviewGatewayStore {
|
|
179
|
-
|
|
179
|
+
/**
|
|
180
|
+
* Stores a session and claims its name atomically. The claim succeeds when
|
|
181
|
+
* the name is free, already belongs to this session, or still belongs to
|
|
182
|
+
* `options.replaceSessionId` (a stale session the gateway decided to take
|
|
183
|
+
* over). Return `false` when another session holds the name; nothing may
|
|
184
|
+
* be stored in that case. Stores that resolve `undefined` are treated as
|
|
185
|
+
* having claimed the name.
|
|
186
|
+
*/
|
|
187
|
+
createSession(session: PreviewGatewaySession, ttlMs: number, options?: PreviewGatewayCreateSessionOptions): Promise<boolean | void>;
|
|
180
188
|
getSessionByName(name: string): Promise<PreviewGatewaySession | undefined>;
|
|
181
189
|
getSessionById(id: string): Promise<PreviewGatewaySession | undefined>;
|
|
182
190
|
touchSession(session: PreviewGatewaySession, ttlMs: number): Promise<void>;
|
|
183
191
|
enqueueRequest(sessionId: string, request: PreviewGatewayRequest, ttlMs: number): Promise<void>;
|
|
184
192
|
takeRequests(sessionId: string, limit: number): Promise<PreviewGatewayRequest[]>;
|
|
193
|
+
/**
|
|
194
|
+
* Puts requests taken by a poll that could not deliver them back at the
|
|
195
|
+
* head of the queue, in their original order. Stores without it fall back
|
|
196
|
+
* to `enqueueRequest`, which keeps the requests but not their position.
|
|
197
|
+
*/
|
|
198
|
+
requeueRequests?(sessionId: string, requests: PreviewGatewayRequest[], ttlMs: number): Promise<void>;
|
|
185
199
|
saveResponse(sessionId: string, requestId: string, response: PreviewGatewayResponse, ttlMs: number): Promise<void>;
|
|
186
200
|
getResponse(sessionId: string, requestId: string): Promise<PreviewGatewayResponse | undefined>;
|
|
187
201
|
deleteResponse(sessionId: string, requestId: string): Promise<void>;
|
|
188
202
|
deleteSession(session: PreviewGatewaySession): Promise<void>;
|
|
189
203
|
}
|
|
204
|
+
interface PreviewGatewayCreateSessionOptions {
|
|
205
|
+
/** A stale session whose name the new session may take over. */
|
|
206
|
+
replaceSessionId?: string;
|
|
207
|
+
}
|
|
190
208
|
interface PreviewGatewayObserver {
|
|
191
209
|
session?(event: {
|
|
192
210
|
grantId?: string;
|
|
@@ -227,12 +245,13 @@ declare class MemoryPreviewGatewayStore implements PreviewGatewayStore {
|
|
|
227
245
|
private names;
|
|
228
246
|
private queues;
|
|
229
247
|
private responses;
|
|
230
|
-
createSession(session: PreviewGatewaySession): Promise<
|
|
248
|
+
createSession(session: PreviewGatewaySession, _ttlMs: number, options?: PreviewGatewayCreateSessionOptions): Promise<boolean>;
|
|
231
249
|
getSessionByName(name: string): Promise<PreviewGatewaySession | undefined>;
|
|
232
250
|
getSessionById(id: string): Promise<PreviewGatewaySession | undefined>;
|
|
233
251
|
touchSession(session: PreviewGatewaySession, _ttlMs: number): Promise<void>;
|
|
234
252
|
enqueueRequest(sessionId: string, request: PreviewGatewayRequest): Promise<void>;
|
|
235
253
|
takeRequests(sessionId: string, limit: number): Promise<PreviewGatewayRequest[]>;
|
|
254
|
+
requeueRequests(sessionId: string, requests: PreviewGatewayRequest[]): Promise<void>;
|
|
236
255
|
saveResponse(sessionId: string, requestId: string, response: PreviewGatewayResponse): Promise<void>;
|
|
237
256
|
getResponse(sessionId: string, requestId: string): Promise<PreviewGatewayResponse | undefined>;
|
|
238
257
|
deleteResponse(sessionId: string, requestId: string): Promise<void>;
|
|
@@ -245,16 +264,18 @@ declare class RedisRestPreviewGatewayStore implements PreviewGatewayStore {
|
|
|
245
264
|
token: string;
|
|
246
265
|
prefix?: string;
|
|
247
266
|
});
|
|
248
|
-
createSession(session: PreviewGatewaySession, ttlMs: number): Promise<
|
|
267
|
+
createSession(session: PreviewGatewaySession, ttlMs: number, options?: PreviewGatewayCreateSessionOptions): Promise<boolean>;
|
|
249
268
|
getSessionByName(name: string): Promise<PreviewGatewaySession | undefined>;
|
|
250
269
|
getSessionById(id: string): Promise<PreviewGatewaySession | undefined>;
|
|
251
270
|
touchSession(session: PreviewGatewaySession, ttlMs: number): Promise<void>;
|
|
252
271
|
enqueueRequest(sessionId: string, request: PreviewGatewayRequest, ttlMs: number): Promise<void>;
|
|
253
272
|
takeRequests(sessionId: string, limit: number): Promise<PreviewGatewayRequest[]>;
|
|
273
|
+
requeueRequests(sessionId: string, requests: PreviewGatewayRequest[], ttlMs: number): Promise<void>;
|
|
254
274
|
saveResponse(sessionId: string, requestId: string, response: PreviewGatewayResponse, ttlMs: number): Promise<void>;
|
|
255
275
|
getResponse(sessionId: string, requestId: string): Promise<PreviewGatewayResponse | undefined>;
|
|
256
276
|
deleteResponse(sessionId: string, requestId: string): Promise<void>;
|
|
257
277
|
deleteSession(session: PreviewGatewaySession): Promise<void>;
|
|
278
|
+
private claimName;
|
|
258
279
|
private command;
|
|
259
280
|
private key;
|
|
260
281
|
private sessionKey;
|
|
@@ -263,5 +284,5 @@ declare class RedisRestPreviewGatewayStore implements PreviewGatewayStore {
|
|
|
263
284
|
private responseKey;
|
|
264
285
|
}
|
|
265
286
|
//#endregion
|
|
266
|
-
export { MemoryPreviewGatewayStore, NodePreviewGatewayHandler, type PreviewAccountClaims, type PreviewAccountIdentity, PreviewAuthError, type PreviewAuthExchangeRateLimitResult, type PreviewAuthExchangeRateLimiter, PreviewGatewayHandler, PreviewGatewayObserver, PreviewGatewayOptions, PreviewGatewayRequest, PreviewGatewayResponse, PreviewGatewaySession, PreviewGatewayStore, type PreviewManagedAuthOptions, PreviewSessionAccess, type PreviewTunnelGrant, type PreviewTunnelGrantClaims, RedisRestPreviewGatewayStore, createNodePreviewGatewayHandler, createPreviewGatewayHandler, createPreviewGatewayStoreFromEnv, exchangeGitHubPreviewToken, getPreviewAuthPublicConfig, issuePreviewTunnelGrant, readPreviewBearerToken, verifyPreviewAccountToken, verifyPreviewTunnelGrant };
|
|
267
|
-
//# sourceMappingURL=index-
|
|
287
|
+
export { MemoryPreviewGatewayStore, NodePreviewGatewayHandler, type PreviewAccountClaims, type PreviewAccountIdentity, PreviewAuthError, type PreviewAuthExchangeRateLimitResult, type PreviewAuthExchangeRateLimiter, PreviewGatewayCreateSessionOptions, PreviewGatewayHandler, PreviewGatewayObserver, PreviewGatewayOptions, PreviewGatewayRequest, PreviewGatewayResponse, PreviewGatewaySession, PreviewGatewayStore, type PreviewManagedAuthOptions, PreviewSessionAccess, type PreviewTunnelGrant, type PreviewTunnelGrantClaims, RedisRestPreviewGatewayStore, createNodePreviewGatewayHandler, createPreviewGatewayHandler, createPreviewGatewayStoreFromEnv, exchangeGitHubPreviewToken, getPreviewAuthPublicConfig, issuePreviewTunnelGrant, readPreviewBearerToken, verifyPreviewAccountToken, verifyPreviewTunnelGrant };
|
|
288
|
+
//# sourceMappingURL=index-D6cgJNEe.d.ts.map
|
package/dist/index.d.ts
CHANGED
|
@@ -176,17 +176,35 @@ interface PreviewGatewayResponse {
|
|
|
176
176
|
encoding?: "base64";
|
|
177
177
|
}
|
|
178
178
|
interface PreviewGatewayStore {
|
|
179
|
-
|
|
179
|
+
/**
|
|
180
|
+
* Stores a session and claims its name atomically. The claim succeeds when
|
|
181
|
+
* the name is free, already belongs to this session, or still belongs to
|
|
182
|
+
* `options.replaceSessionId` (a stale session the gateway decided to take
|
|
183
|
+
* over). Return `false` when another session holds the name; nothing may
|
|
184
|
+
* be stored in that case. Stores that resolve `undefined` are treated as
|
|
185
|
+
* having claimed the name.
|
|
186
|
+
*/
|
|
187
|
+
createSession(session: PreviewGatewaySession, ttlMs: number, options?: PreviewGatewayCreateSessionOptions): Promise<boolean | void>;
|
|
180
188
|
getSessionByName(name: string): Promise<PreviewGatewaySession | undefined>;
|
|
181
189
|
getSessionById(id: string): Promise<PreviewGatewaySession | undefined>;
|
|
182
190
|
touchSession(session: PreviewGatewaySession, ttlMs: number): Promise<void>;
|
|
183
191
|
enqueueRequest(sessionId: string, request: PreviewGatewayRequest, ttlMs: number): Promise<void>;
|
|
184
192
|
takeRequests(sessionId: string, limit: number): Promise<PreviewGatewayRequest[]>;
|
|
193
|
+
/**
|
|
194
|
+
* Puts requests taken by a poll that could not deliver them back at the
|
|
195
|
+
* head of the queue, in their original order. Stores without it fall back
|
|
196
|
+
* to `enqueueRequest`, which keeps the requests but not their position.
|
|
197
|
+
*/
|
|
198
|
+
requeueRequests?(sessionId: string, requests: PreviewGatewayRequest[], ttlMs: number): Promise<void>;
|
|
185
199
|
saveResponse(sessionId: string, requestId: string, response: PreviewGatewayResponse, ttlMs: number): Promise<void>;
|
|
186
200
|
getResponse(sessionId: string, requestId: string): Promise<PreviewGatewayResponse | undefined>;
|
|
187
201
|
deleteResponse(sessionId: string, requestId: string): Promise<void>;
|
|
188
202
|
deleteSession(session: PreviewGatewaySession): Promise<void>;
|
|
189
203
|
}
|
|
204
|
+
interface PreviewGatewayCreateSessionOptions {
|
|
205
|
+
/** A stale session whose name the new session may take over. */
|
|
206
|
+
replaceSessionId?: string;
|
|
207
|
+
}
|
|
190
208
|
interface PreviewGatewayObserver {
|
|
191
209
|
session?(event: {
|
|
192
210
|
grantId?: string;
|
|
@@ -227,12 +245,13 @@ declare class MemoryPreviewGatewayStore implements PreviewGatewayStore {
|
|
|
227
245
|
private names;
|
|
228
246
|
private queues;
|
|
229
247
|
private responses;
|
|
230
|
-
createSession(session: PreviewGatewaySession): Promise<
|
|
248
|
+
createSession(session: PreviewGatewaySession, _ttlMs: number, options?: PreviewGatewayCreateSessionOptions): Promise<boolean>;
|
|
231
249
|
getSessionByName(name: string): Promise<PreviewGatewaySession | undefined>;
|
|
232
250
|
getSessionById(id: string): Promise<PreviewGatewaySession | undefined>;
|
|
233
251
|
touchSession(session: PreviewGatewaySession, _ttlMs: number): Promise<void>;
|
|
234
252
|
enqueueRequest(sessionId: string, request: PreviewGatewayRequest): Promise<void>;
|
|
235
253
|
takeRequests(sessionId: string, limit: number): Promise<PreviewGatewayRequest[]>;
|
|
254
|
+
requeueRequests(sessionId: string, requests: PreviewGatewayRequest[]): Promise<void>;
|
|
236
255
|
saveResponse(sessionId: string, requestId: string, response: PreviewGatewayResponse): Promise<void>;
|
|
237
256
|
getResponse(sessionId: string, requestId: string): Promise<PreviewGatewayResponse | undefined>;
|
|
238
257
|
deleteResponse(sessionId: string, requestId: string): Promise<void>;
|
|
@@ -245,16 +264,18 @@ declare class RedisRestPreviewGatewayStore implements PreviewGatewayStore {
|
|
|
245
264
|
token: string;
|
|
246
265
|
prefix?: string;
|
|
247
266
|
});
|
|
248
|
-
createSession(session: PreviewGatewaySession, ttlMs: number): Promise<
|
|
267
|
+
createSession(session: PreviewGatewaySession, ttlMs: number, options?: PreviewGatewayCreateSessionOptions): Promise<boolean>;
|
|
249
268
|
getSessionByName(name: string): Promise<PreviewGatewaySession | undefined>;
|
|
250
269
|
getSessionById(id: string): Promise<PreviewGatewaySession | undefined>;
|
|
251
270
|
touchSession(session: PreviewGatewaySession, ttlMs: number): Promise<void>;
|
|
252
271
|
enqueueRequest(sessionId: string, request: PreviewGatewayRequest, ttlMs: number): Promise<void>;
|
|
253
272
|
takeRequests(sessionId: string, limit: number): Promise<PreviewGatewayRequest[]>;
|
|
273
|
+
requeueRequests(sessionId: string, requests: PreviewGatewayRequest[], ttlMs: number): Promise<void>;
|
|
254
274
|
saveResponse(sessionId: string, requestId: string, response: PreviewGatewayResponse, ttlMs: number): Promise<void>;
|
|
255
275
|
getResponse(sessionId: string, requestId: string): Promise<PreviewGatewayResponse | undefined>;
|
|
256
276
|
deleteResponse(sessionId: string, requestId: string): Promise<void>;
|
|
257
277
|
deleteSession(session: PreviewGatewaySession): Promise<void>;
|
|
278
|
+
private claimName;
|
|
258
279
|
private command;
|
|
259
280
|
private key;
|
|
260
281
|
private sessionKey;
|
|
@@ -263,5 +284,5 @@ declare class RedisRestPreviewGatewayStore implements PreviewGatewayStore {
|
|
|
263
284
|
private responseKey;
|
|
264
285
|
}
|
|
265
286
|
//#endregion
|
|
266
|
-
export { MemoryPreviewGatewayStore, NodePreviewGatewayHandler, type PreviewAccountClaims, type PreviewAccountIdentity, PreviewAuthError, type PreviewAuthExchangeRateLimitResult, type PreviewAuthExchangeRateLimiter, PreviewGatewayHandler, PreviewGatewayObserver, PreviewGatewayOptions, PreviewGatewayRequest, PreviewGatewayResponse, PreviewGatewaySession, PreviewGatewayStore, type PreviewManagedAuthOptions, PreviewSessionAccess, type PreviewTunnelGrant, type PreviewTunnelGrantClaims, RedisRestPreviewGatewayStore, createNodePreviewGatewayHandler, createPreviewGatewayHandler, createPreviewGatewayStoreFromEnv, exchangeGitHubPreviewToken, getPreviewAuthPublicConfig, issuePreviewTunnelGrant, readPreviewBearerToken, verifyPreviewAccountToken, verifyPreviewTunnelGrant };
|
|
267
|
-
//# sourceMappingURL=index-
|
|
287
|
+
export { MemoryPreviewGatewayStore, NodePreviewGatewayHandler, type PreviewAccountClaims, type PreviewAccountIdentity, PreviewAuthError, type PreviewAuthExchangeRateLimitResult, type PreviewAuthExchangeRateLimiter, PreviewGatewayCreateSessionOptions, PreviewGatewayHandler, PreviewGatewayObserver, PreviewGatewayOptions, PreviewGatewayRequest, PreviewGatewayResponse, PreviewGatewaySession, PreviewGatewayStore, type PreviewManagedAuthOptions, PreviewSessionAccess, type PreviewTunnelGrant, type PreviewTunnelGrantClaims, RedisRestPreviewGatewayStore, createNodePreviewGatewayHandler, createPreviewGatewayHandler, createPreviewGatewayStoreFromEnv, exchangeGitHubPreviewToken, getPreviewAuthPublicConfig, issuePreviewTunnelGrant, readPreviewBearerToken, verifyPreviewAccountToken, verifyPreviewTunnelGrant };
|
|
288
|
+
//# sourceMappingURL=index-D6cgJNEe.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -304,7 +304,8 @@ function createPreviewGatewayHandler(options = {}) {
|
|
|
304
304
|
}, 200);
|
|
305
305
|
} catch (error) {
|
|
306
306
|
if (error instanceof GatewayHttpError || error instanceof PreviewAuthError) return text(error.message, error.status);
|
|
307
|
-
|
|
307
|
+
console.error("[farm-preview-gateway] Unexpected error:", error);
|
|
308
|
+
return text("Unexpected preview gateway error.", 500);
|
|
308
309
|
}
|
|
309
310
|
}, { async isPreviewNameClaimed(name) {
|
|
310
311
|
const claimed = sanitizePreviewName(name);
|
|
@@ -403,10 +404,13 @@ var MemoryPreviewGatewayStore = class {
|
|
|
403
404
|
this.queues = /* @__PURE__ */ new Map();
|
|
404
405
|
this.responses = /* @__PURE__ */ new Map();
|
|
405
406
|
}
|
|
406
|
-
async createSession(session) {
|
|
407
|
+
async createSession(session, _ttlMs, options = {}) {
|
|
408
|
+
const owner = this.names.get(session.name);
|
|
409
|
+
if (owner && owner !== session.id && owner !== options.replaceSessionId && this.sessions.has(owner)) return false;
|
|
407
410
|
this.sessions.set(session.id, session);
|
|
408
411
|
this.names.set(session.name, session.id);
|
|
409
412
|
this.queues.set(session.id, []);
|
|
413
|
+
return true;
|
|
410
414
|
}
|
|
411
415
|
async getSessionByName(name) {
|
|
412
416
|
const id = this.names.get(name);
|
|
@@ -435,6 +439,12 @@ var MemoryPreviewGatewayStore = class {
|
|
|
435
439
|
async takeRequests(sessionId, limit) {
|
|
436
440
|
return (this.queues.get(sessionId) || []).splice(0, limit);
|
|
437
441
|
}
|
|
442
|
+
async requeueRequests(sessionId, requests) {
|
|
443
|
+
if (!this.sessions.has(sessionId)) return;
|
|
444
|
+
const queue = this.queues.get(sessionId) || [];
|
|
445
|
+
queue.unshift(...requests);
|
|
446
|
+
this.queues.set(sessionId, queue);
|
|
447
|
+
}
|
|
438
448
|
async saveResponse(sessionId, requestId, response) {
|
|
439
449
|
this.responses.set(responseKey(sessionId, requestId), response);
|
|
440
450
|
}
|
|
@@ -450,14 +460,31 @@ var MemoryPreviewGatewayStore = class {
|
|
|
450
460
|
this.queues.delete(session.id);
|
|
451
461
|
}
|
|
452
462
|
};
|
|
463
|
+
const CLAIM_NAME_SCRIPT = `
|
|
464
|
+
local owner = redis.call("GET", KEYS[1])
|
|
465
|
+
if not owner or owner == ARGV[2] or (ARGV[1] ~= "" and owner == ARGV[1]) then
|
|
466
|
+
redis.call("SET", KEYS[1], ARGV[2], "PX", ARGV[3])
|
|
467
|
+
return 1
|
|
468
|
+
end
|
|
469
|
+
return 0
|
|
470
|
+
`;
|
|
453
471
|
var RedisRestPreviewGatewayStore = class {
|
|
454
472
|
constructor(options) {
|
|
455
473
|
this.options = options;
|
|
456
474
|
}
|
|
457
|
-
async createSession(session, ttlMs) {
|
|
475
|
+
async createSession(session, ttlMs, options = {}) {
|
|
458
476
|
await this.command("SET", this.sessionKey(session.id), JSON.stringify(session), "PX", ttlMs);
|
|
459
|
-
await this.
|
|
477
|
+
let claimed = await this.claimName(session, options.replaceSessionId, ttlMs);
|
|
478
|
+
if (!claimed) {
|
|
479
|
+
const owner = await this.command("GET", this.nameKey(session.name));
|
|
480
|
+
if (owner && !await this.command("EXISTS", this.sessionKey(owner))) claimed = await this.claimName(session, owner, ttlMs);
|
|
481
|
+
}
|
|
482
|
+
if (!claimed) {
|
|
483
|
+
await this.command("DEL", this.sessionKey(session.id));
|
|
484
|
+
return false;
|
|
485
|
+
}
|
|
460
486
|
await this.command("DEL", this.queueKey(session.id));
|
|
487
|
+
return true;
|
|
461
488
|
}
|
|
462
489
|
async getSessionByName(name) {
|
|
463
490
|
const id = await this.command("GET", this.nameKey(name));
|
|
@@ -471,8 +498,7 @@ var RedisRestPreviewGatewayStore = class {
|
|
|
471
498
|
const nextSession = { ...session };
|
|
472
499
|
const remainingTtlMs = Math.max(1, Math.min(ttlMs, session.expiresAt - Date.now()));
|
|
473
500
|
if (await this.command("SET", this.sessionKey(session.id), JSON.stringify(nextSession), "PX", remainingTtlMs, "XX") === null) return;
|
|
474
|
-
|
|
475
|
-
if (!nameOwner || nameOwner === session.id) await this.command("SET", this.nameKey(session.name), session.id, "PX", remainingTtlMs);
|
|
501
|
+
await this.claimName(session, void 0, remainingTtlMs);
|
|
476
502
|
await this.command("EXPIRE", this.queueKey(session.id), Math.ceil(remainingTtlMs / 1e3));
|
|
477
503
|
}
|
|
478
504
|
async enqueueRequest(sessionId, request, ttlMs) {
|
|
@@ -484,6 +510,12 @@ var RedisRestPreviewGatewayStore = class {
|
|
|
484
510
|
if (!values) return [];
|
|
485
511
|
return (Array.isArray(values) ? values : [values]).map((value) => JSON.parse(value));
|
|
486
512
|
}
|
|
513
|
+
async requeueRequests(sessionId, requests, ttlMs) {
|
|
514
|
+
if (!requests.length) return;
|
|
515
|
+
const values = requests.map((request) => JSON.stringify(request)).reverse();
|
|
516
|
+
await this.command("LPUSH", this.queueKey(sessionId), ...values);
|
|
517
|
+
await this.command("EXPIRE", this.queueKey(sessionId), Math.ceil(ttlMs / 1e3));
|
|
518
|
+
}
|
|
487
519
|
async saveResponse(sessionId, requestId, response, ttlMs) {
|
|
488
520
|
await this.command("SET", this.responseKey(sessionId, requestId), JSON.stringify(response), "PX", ttlMs);
|
|
489
521
|
}
|
|
@@ -499,6 +531,10 @@ var RedisRestPreviewGatewayStore = class {
|
|
|
499
531
|
if (await this.command("GET", this.nameKey(session.name)) === session.id) keys.push(this.nameKey(session.name));
|
|
500
532
|
await this.command("DEL", ...keys);
|
|
501
533
|
}
|
|
534
|
+
async claimName(session, replaceSessionId, ttlMs) {
|
|
535
|
+
const result = await this.command("EVAL", CLAIM_NAME_SCRIPT, 1, this.nameKey(session.name), replaceSessionId || "", session.id, Math.max(1, Math.ceil(ttlMs)));
|
|
536
|
+
return Number(result) === 1;
|
|
537
|
+
}
|
|
502
538
|
async command(...args) {
|
|
503
539
|
const response = await fetch(this.options.url, {
|
|
504
540
|
method: "POST",
|
|
@@ -562,32 +598,33 @@ async function createSession(request, store, config) {
|
|
|
562
598
|
expiresAt = Date.now() + clamp(Number(input.expiresInMs), 6e4, config.sessionTtlMs);
|
|
563
599
|
slidingExpiration = false;
|
|
564
600
|
}
|
|
601
|
+
let session;
|
|
565
602
|
for (let attempt = 0;; attempt++) {
|
|
566
603
|
const existing = await store.getSessionByName(name);
|
|
567
|
-
if (!existing || !isPreviewClientOnline(existing, config))
|
|
604
|
+
if (!existing || !isPreviewClientOnline(existing, config)) {
|
|
605
|
+
session = {
|
|
606
|
+
...grantId ? { grantId } : {},
|
|
607
|
+
...project ? { project } : {},
|
|
608
|
+
...keyId ? { keyId } : {},
|
|
609
|
+
...ownerId ? { ownerId } : {},
|
|
610
|
+
id: randomId("sess"),
|
|
611
|
+
name,
|
|
612
|
+
hostname: `${name}.${config.domain}`,
|
|
613
|
+
publicUrl: createPublicUrl(request, config, name),
|
|
614
|
+
token: randomId("token"),
|
|
615
|
+
localUrl: input.localUrl,
|
|
616
|
+
createdAt: Date.now(),
|
|
617
|
+
expiresAt,
|
|
618
|
+
...slidingExpiration ? { slidingExpiration: true } : {},
|
|
619
|
+
lastHeartbeatAt: Date.now()
|
|
620
|
+
};
|
|
621
|
+
await requireSessionAccess(config, session);
|
|
622
|
+
if (await store.createSession(session, Math.max(1, expiresAt - Date.now()), existing ? { replaceSessionId: existing.id } : void 0) !== false) break;
|
|
623
|
+
}
|
|
568
624
|
if (requestedName || config.auth) return text(`A Farm preview named "${name}" is already active. Choose another name or stop the running preview.`, 409);
|
|
569
625
|
if (attempt >= 4) return text("Could not allocate a unique preview name. Try again.", 503);
|
|
570
626
|
name = randomPreviewName();
|
|
571
627
|
}
|
|
572
|
-
const hostname = `${name}.${config.domain}`;
|
|
573
|
-
const session = {
|
|
574
|
-
...grantId ? { grantId } : {},
|
|
575
|
-
...project ? { project } : {},
|
|
576
|
-
...keyId ? { keyId } : {},
|
|
577
|
-
...ownerId ? { ownerId } : {},
|
|
578
|
-
id: randomId("sess"),
|
|
579
|
-
name,
|
|
580
|
-
hostname,
|
|
581
|
-
publicUrl: createPublicUrl(request, config, name),
|
|
582
|
-
token: randomId("token"),
|
|
583
|
-
localUrl: input.localUrl,
|
|
584
|
-
createdAt: Date.now(),
|
|
585
|
-
expiresAt,
|
|
586
|
-
...slidingExpiration ? { slidingExpiration: true } : {},
|
|
587
|
-
lastHeartbeatAt: Date.now()
|
|
588
|
-
};
|
|
589
|
-
await requireSessionAccess(config, session);
|
|
590
|
-
await store.createSession(session, Math.max(1, expiresAt - Date.now()));
|
|
591
628
|
reportSession(config, session, "connected");
|
|
592
629
|
return json({
|
|
593
630
|
id: session.id,
|
|
@@ -600,7 +637,7 @@ async function createSession(request, store, config) {
|
|
|
600
637
|
async function handleSessionRoute(request, url, store, config, route) {
|
|
601
638
|
const session = await requireSession(store, route.sessionId, readPreviewBearerToken(request) || url.searchParams.get("token"));
|
|
602
639
|
if (request.method !== "DELETE" || route.action) await requireSessionAccess(config, session);
|
|
603
|
-
if (request.method === "GET" && route.action === "requests") return await pollSessionRequests(url, store, config, session);
|
|
640
|
+
if (request.method === "GET" && route.action === "requests") return await pollSessionRequests(url, store, config, session, request.signal);
|
|
604
641
|
if (request.method === "POST" && route.action === "responses" && route.requestId) {
|
|
605
642
|
let response;
|
|
606
643
|
try {
|
|
@@ -656,27 +693,53 @@ function createOversizedPreviewResponse(maxBodyBytes) {
|
|
|
656
693
|
encoding: "base64"
|
|
657
694
|
};
|
|
658
695
|
}
|
|
659
|
-
async function pollSessionRequests(url, store, config, session) {
|
|
696
|
+
async function pollSessionRequests(url, store, config, session, signal) {
|
|
660
697
|
const waitMs = clamp(Number(url.searchParams.get("wait") || config.pollTimeoutMs), 1e3, 25e3);
|
|
661
698
|
const deadline = Date.now() + waitMs;
|
|
662
699
|
let nextAccessCheckAt = Date.now() + 1e3;
|
|
700
|
+
const abandoned = () => new Response(null, { status: 499 });
|
|
701
|
+
if (signal.aborted) return abandoned();
|
|
663
702
|
await markSessionOnline(store, config, session);
|
|
664
703
|
while (Date.now() < deadline) {
|
|
704
|
+
if (signal.aborted) return abandoned();
|
|
665
705
|
if (Date.now() >= nextAccessCheckAt) {
|
|
666
706
|
await requireSessionAccess(config, session);
|
|
667
707
|
nextAccessCheckAt = Date.now() + 1e3;
|
|
668
708
|
}
|
|
669
709
|
const requests = await store.takeRequests(session.id, DEFAULT_POLL_REQUEST_LIMIT);
|
|
670
710
|
if (requests.length) {
|
|
671
|
-
|
|
672
|
-
|
|
711
|
+
try {
|
|
712
|
+
await requireSessionAccess(config, session);
|
|
713
|
+
if (!signal.aborted) await markSessionOnline(store, config, session);
|
|
714
|
+
} catch (error) {
|
|
715
|
+
await requeuePreviewRequests(store, config, session, requests);
|
|
716
|
+
throw error;
|
|
717
|
+
}
|
|
718
|
+
if (signal.aborted) {
|
|
719
|
+
await requeuePreviewRequests(store, config, session, requests);
|
|
720
|
+
return abandoned();
|
|
721
|
+
}
|
|
673
722
|
return json({ requests });
|
|
674
723
|
}
|
|
675
|
-
|
|
724
|
+
try {
|
|
725
|
+
await setTimeout(config.pollIntervalMs, void 0, { signal });
|
|
726
|
+
} catch {
|
|
727
|
+
if (signal.aborted) return abandoned();
|
|
728
|
+
throw new Error("Preview request polling was interrupted.");
|
|
729
|
+
}
|
|
676
730
|
}
|
|
731
|
+
if (signal.aborted) return abandoned();
|
|
677
732
|
await markSessionOnline(store, config, session);
|
|
678
733
|
return new Response(null, { status: 204 });
|
|
679
734
|
}
|
|
735
|
+
async function requeuePreviewRequests(store, config, session, requests) {
|
|
736
|
+
const ttlMs = remainingSessionTtl(session, config);
|
|
737
|
+
if (store.requeueRequests) {
|
|
738
|
+
await store.requeueRequests(session.id, requests, ttlMs);
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
for (const request of requests) await store.enqueueRequest(session.id, request, ttlMs);
|
|
742
|
+
}
|
|
680
743
|
async function proxyPublicRequest(request, url, store, config, route) {
|
|
681
744
|
const session = await store.getSessionByName(route.name);
|
|
682
745
|
if (!session) return inactivePreviewResponse(request, route.name);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["DEFAULT_SESSION_TTL_MS","delay"],"sources":["../src/auth.ts","../src/index.ts"],"sourcesContent":["import { createHmac, randomUUID, timingSafeEqual } from \"node:crypto\";\n\nexport interface PreviewManagedAuthOptions {\n /** Secret used to sign account tokens and one-preview tunnel grants. */\n signingSecret: string;\n /** Public client id for the Farming Labs GitHub OAuth app. */\n githubClientId?: string;\n /** First-party device login. Omit to preserve GitHub account authentication. */\n deviceAuth?: {\n issuer: string;\n clientId: string;\n /** Resolve an API key or approved device credential on every new grant. */\n authorizeAccount(token: string): Promise<{\n subject: string;\n login: string;\n expiresAt?: number;\n keyId?: string;\n project?: string;\n } | null>;\n };\n /** Public console URL. Must not contain credentials or query parameters. */\n dashboardUrl?: string;\n accountTokenTtlMs?: number;\n defaultSessionTtlMs?: number;\n maxSessionTtlMs?: number;\n githubApiUrl?: string;\n fetch?: typeof fetch;\n /**\n * Shared abuse-control hook for provider-token exchange and, in device mode,\n * account credential verification on the tunnel-grant endpoint.\n * Implementations must not consume the request body. Failures reject the\n * exchange with a retryable 503 response instead of bypassing the limiter.\n */\n rateLimitExchange?: PreviewAuthExchangeRateLimiter;\n}\n\nexport interface PreviewAuthExchangeRateLimitResult {\n allowed: boolean;\n /** How long the caller should wait before retrying a rejected exchange. */\n retryAfterMs?: number;\n}\n\nexport type PreviewAuthExchangeRateLimiter = (\n request: Request,\n) => PreviewAuthExchangeRateLimitResult | Promise<PreviewAuthExchangeRateLimitResult>;\n\nexport interface PreviewAccountIdentity {\n /** Optional credential restriction; never taken from the grant request. */\n project?: string;\n /** Non-secret identifier supplied by the credential verifier, never the CLI. */\n keyId?: string;\n provider: \"github\" | \"device\";\n subject: string;\n login: string;\n name?: string;\n avatarUrl?: string;\n}\n\nexport interface PreviewAccountClaims extends PreviewAccountIdentity {\n kind: \"account\";\n issuedAt: number;\n expiresAt: number;\n}\n\nexport interface PreviewTunnelGrantClaims {\n /** Unique signed grant identity, used to revoke reconnects across transports. */\n nonce?: string;\n project?: string;\n keyId?: string;\n kind: \"tunnel\";\n /** Absent on older GitHub grants. */\n provider?: \"github\" | \"device\";\n subject: string;\n login: string;\n name: string;\n issuedAt: number;\n expiresAt: number;\n}\n\nexport interface PreviewTunnelGrant {\n token: string;\n name: string;\n expiresAt: number;\n}\n\nconst TOKEN_PREFIX = \"farm_preview_v1\";\nconst DEFAULT_ACCOUNT_TOKEN_TTL_MS = 1000 * 60 * 60 * 24 * 7;\nconst DEFAULT_SESSION_TTL_MS = 1000 * 60 * 60;\nconst DEFAULT_MAX_SESSION_TTL_MS = 1000 * 60 * 60 * 24;\nconst MIN_SESSION_TTL_MS = 1000 * 60;\nconst MAX_CLOCK_SKEW_MS = 1000 * 60 * 5;\n\nexport function getPreviewAuthPublicConfig(options: PreviewManagedAuthOptions) {\n validatePreviewAuthOptions(options);\n const durations = resolvePreviewDurations(options);\n return {\n enabled: true as const,\n controlAuth: \"bearer\" as const,\n ...(options.deviceAuth\n ? {\n provider: \"device\" as const,\n clientId: options.deviceAuth.clientId,\n issuer: options.deviceAuth.issuer,\n }\n : { provider: \"github\" as const, clientId: options.githubClientId, scope: \"read:user\" }),\n ...(options.dashboardUrl ? { dashboardUrl: options.dashboardUrl } : {}),\n defaultSessionTtlMs: durations.defaultSessionTtlMs,\n maxSessionTtlMs: durations.maxSessionTtlMs,\n };\n}\n\nexport async function exchangeGitHubPreviewToken(\n providerToken: string,\n options: PreviewManagedAuthOptions,\n): Promise<{ token: string; expiresAt: number; user: PreviewAccountIdentity }> {\n validatePreviewAuthOptions(options);\n if (options.deviceAuth)\n throw new PreviewAuthError(400, \"Use the configured device sign-in flow.\");\n if (!providerToken || providerToken.length > 4096) {\n throw new PreviewAuthError(400, \"A GitHub access token is required.\");\n }\n\n const request = options.fetch || fetch;\n const response = await request(`${options.githubApiUrl || \"https://api.github.com\"}/user`, {\n headers: {\n accept: \"application/vnd.github+json\",\n authorization: `Bearer ${providerToken}`,\n \"user-agent\": \"farm-preview-gateway\",\n \"x-github-api-version\": \"2026-03-10\",\n },\n signal: AbortSignal.timeout(10_000),\n });\n if (!response.ok) {\n await response.body?.cancel();\n throw new PreviewAuthError(401, \"GitHub could not verify this Farm Preview login.\");\n }\n\n const profile = (await response.json()) as {\n id?: number;\n login?: string;\n name?: string | null;\n avatar_url?: string;\n };\n if (!Number.isSafeInteger(profile.id) || !profile.login) {\n throw new PreviewAuthError(502, \"GitHub returned an invalid user profile.\");\n }\n\n const now = Date.now();\n const expiresAt = now + (options.accountTokenTtlMs ?? DEFAULT_ACCOUNT_TOKEN_TTL_MS);\n const user: PreviewAccountIdentity = {\n provider: \"github\",\n subject: String(profile.id),\n login: profile.login,\n ...(profile.name ? { name: profile.name } : {}),\n ...(profile.avatar_url ? { avatarUrl: profile.avatar_url } : {}),\n };\n const claims: PreviewAccountClaims = {\n kind: \"account\",\n ...user,\n issuedAt: now,\n expiresAt,\n };\n\n return {\n token: signPreviewClaims(claims, options.signingSecret),\n expiresAt,\n user,\n };\n}\n\nexport function verifyPreviewAccountToken(\n token: string,\n options: Pick<PreviewManagedAuthOptions, \"signingSecret\">,\n): PreviewAccountClaims {\n const claims = verifyPreviewClaims(token, options.signingSecret);\n if (\n claims.kind !== \"account\" ||\n claims.provider !== \"github\" ||\n typeof claims.subject !== \"string\" ||\n typeof claims.login !== \"string\"\n ) {\n throw new PreviewAuthError(401, \"The Farm Preview account token is invalid.\");\n }\n return claims as unknown as PreviewAccountClaims;\n}\n\nexport function issuePreviewTunnelGrant(\n account: PreviewAccountClaims,\n input: { name: string; project?: string; expiresInMs?: number },\n options: PreviewManagedAuthOptions,\n): PreviewTunnelGrant {\n validatePreviewAuthOptions(options);\n const durations = resolvePreviewDurations(options);\n if (\n input.project !== undefined &&\n (typeof input.project !== \"string\" || !/^[a-z0-9][a-z0-9-]{0,62}$/.test(input.project))\n )\n throw new PreviewAuthError(400, \"Preview project must be a lowercase slug of 1–63 characters.\");\n if (\n input.expiresInMs !== undefined &&\n (!Number.isSafeInteger(input.expiresInMs) || input.expiresInMs <= 0)\n ) {\n throw new PreviewAuthError(400, \"Preview expiry must be a positive integer.\");\n }\n const requestedTtlMs = input.expiresInMs ?? durations.defaultSessionTtlMs;\n if (account.project !== undefined && account.project !== (input.project ?? input.name))\n throw new PreviewAuthError(403, \"This API key is restricted to another preview project.\");\n const ttlMs = Math.max(MIN_SESSION_TTL_MS, Math.min(durations.maxSessionTtlMs, requestedTtlMs));\n const issuedAt = Date.now();\n const expiresAt = Math.min(issuedAt + ttlMs, account.expiresAt);\n if (expiresAt <= issuedAt) throw new PreviewAuthError(401, \"The account credential has expired.\");\n const claims: PreviewTunnelGrantClaims & { nonce: string } = {\n kind: \"tunnel\",\n provider: account.provider,\n subject: account.subject,\n login: account.login,\n name: input.name,\n project: input.project ?? input.name,\n ...(account.keyId ? { keyId: account.keyId } : {}),\n issuedAt,\n expiresAt,\n nonce: randomUUID(),\n };\n return {\n token: signPreviewClaims(claims, options.signingSecret),\n name: input.name,\n expiresAt,\n };\n}\n\nexport function verifyPreviewTunnelGrant(\n token: string,\n options: Pick<PreviewManagedAuthOptions, \"signingSecret\"> & { name?: string },\n): PreviewTunnelGrantClaims {\n const claims = verifyPreviewClaims(token, options.signingSecret);\n if (\n claims.kind !== \"tunnel\" ||\n typeof claims.subject !== \"string\" ||\n typeof claims.login !== \"string\" ||\n typeof claims.name !== \"string\" ||\n (claims.nonce !== undefined &&\n (typeof claims.nonce !== \"string\" || !/^[\\w-]{1,128}$/.test(claims.nonce))) ||\n (claims.project !== undefined &&\n (typeof claims.project !== \"string\" || !/^[a-z0-9][a-z0-9-]{0,62}$/.test(claims.project))) ||\n (claims.keyId !== undefined &&\n (typeof claims.keyId !== \"string\" || !/^[\\w-]{1,128}$/.test(claims.keyId))) ||\n (options.name !== undefined && claims.name !== options.name)\n ) {\n throw new PreviewAuthError(401, \"The Farm Preview tunnel grant is invalid.\");\n }\n return claims as unknown as PreviewTunnelGrantClaims;\n}\n\nexport function readPreviewBearerToken(request: Request): string | undefined {\n const authorization = request.headers.get(\"authorization\");\n if (!authorization || !/^Bearer /i.test(authorization)) return undefined;\n const token = authorization.slice(7).trim();\n return token || undefined;\n}\n\nexport class PreviewAuthError extends Error {\n constructor(\n public status: number,\n message: string,\n ) {\n super(message);\n }\n}\n\nfunction resolvePreviewDurations(options: PreviewManagedAuthOptions) {\n const maxSessionTtlMs = Math.max(\n MIN_SESSION_TTL_MS,\n options.maxSessionTtlMs ?? DEFAULT_MAX_SESSION_TTL_MS,\n );\n return {\n maxSessionTtlMs,\n defaultSessionTtlMs: Math.min(\n maxSessionTtlMs,\n Math.max(MIN_SESSION_TTL_MS, options.defaultSessionTtlMs ?? DEFAULT_SESSION_TTL_MS),\n ),\n };\n}\n\nfunction validatePreviewAuthOptions(options: PreviewManagedAuthOptions) {\n if (Buffer.byteLength(options.signingSecret) < 32) {\n throw new Error(\"Farm Preview auth signingSecret must contain at least 32 bytes.\");\n }\n if (!options.githubClientId && !options.deviceAuth) {\n throw new Error(\"Farm Preview auth requires a GitHub OAuth client id or deviceAuth.\");\n }\n if (options.deviceAuth) {\n validatePublicAuthUrl(options.deviceAuth.issuer);\n if (!options.deviceAuth.clientId)\n throw new Error(\"Farm Preview deviceAuth requires a clientId.\");\n }\n if (options.dashboardUrl) validatePublicAuthUrl(options.dashboardUrl);\n for (const [name, value] of [\n [\"accountTokenTtlMs\", options.accountTokenTtlMs],\n [\"defaultSessionTtlMs\", options.defaultSessionTtlMs],\n [\"maxSessionTtlMs\", options.maxSessionTtlMs],\n ] as const) {\n if (value !== undefined && (!Number.isSafeInteger(value) || value <= 0)) {\n throw new Error(`Farm Preview auth ${name} must be a positive integer.`);\n }\n }\n}\n\nfunction validatePublicAuthUrl(value: string) {\n const url = new URL(value);\n const local = [\"localhost\", \"127.0.0.1\", \"[::1]\"].includes(url.hostname);\n if (\n (url.protocol !== \"https:\" && !(local && url.protocol === \"http:\")) ||\n url.username ||\n url.password ||\n url.search ||\n url.hash\n ) {\n throw new Error(\n \"Farm Preview auth URLs must use HTTPS (HTTP only for loopback development), without credentials, queries or fragments.\",\n );\n }\n}\n\nexport async function authorizePreviewAccount(\n token: string,\n options: PreviewManagedAuthOptions,\n): Promise<PreviewAccountClaims> {\n if (!options.deviceAuth) return verifyPreviewAccountToken(token, options);\n if (!token || token.length > 4096) throw new PreviewAuthError(401, \"Invalid preview credential.\");\n let account;\n try {\n account = await options.deviceAuth.authorizeAccount(token);\n } catch {\n throw new PreviewAuthError(503, \"Farm Preview authentication is temporarily unavailable.\");\n }\n const now = Date.now();\n if (\n !account ||\n typeof account.subject !== \"string\" ||\n !account.subject ||\n typeof account.login !== \"string\" ||\n !account.login ||\n (account.project !== undefined &&\n (typeof account.project !== \"string\" ||\n !/^[a-z0-9][a-z0-9-]{0,62}$/.test(account.project))) ||\n (account.keyId !== undefined &&\n (typeof account.keyId !== \"string\" || !/^[\\w-]{1,128}$/.test(account.keyId))) ||\n (account.expiresAt !== undefined &&\n (!Number.isSafeInteger(account.expiresAt) || account.expiresAt <= now))\n ) {\n throw new PreviewAuthError(401, \"The Farm.js Infrastructure credential is invalid or expired.\");\n }\n return {\n kind: \"account\",\n provider: \"device\",\n subject: account.subject,\n login: account.login,\n ...(account.keyId ? { keyId: account.keyId } : {}),\n ...(account.project ? { project: account.project } : {}),\n issuedAt: now,\n expiresAt: account.expiresAt ?? now + DEFAULT_ACCOUNT_TOKEN_TTL_MS,\n };\n}\n\nfunction signPreviewClaims(claims: object, secret: string) {\n const payload = Buffer.from(JSON.stringify(claims)).toString(\"base64url\");\n const signature = createHmac(\"sha256\", secret)\n .update(`${TOKEN_PREFIX}.${payload}`)\n .digest(\"base64url\");\n return `${TOKEN_PREFIX}.${payload}.${signature}`;\n}\n\nfunction verifyPreviewClaims(token: string, secret: string): Record<string, unknown> {\n if (!token || token.length > 16_384) {\n throw new PreviewAuthError(401, \"The Farm Preview credential is invalid.\");\n }\n const [prefix, payload, presentedSignature, ...rest] = token.split(\".\");\n if (prefix !== TOKEN_PREFIX || !payload || !presentedSignature || rest.length) {\n throw new PreviewAuthError(401, \"The Farm Preview credential is invalid.\");\n }\n\n const expectedSignature = createHmac(\"sha256\", secret)\n .update(`${TOKEN_PREFIX}.${payload}`)\n .digest();\n let presented: Buffer;\n try {\n presented = Buffer.from(presentedSignature, \"base64url\");\n } catch {\n throw new PreviewAuthError(401, \"The Farm Preview credential is invalid.\");\n }\n if (\n presented.length !== expectedSignature.length ||\n !timingSafeEqual(presented, expectedSignature)\n ) {\n throw new PreviewAuthError(401, \"The Farm Preview credential is invalid.\");\n }\n\n let claims: unknown;\n try {\n claims = JSON.parse(Buffer.from(payload, \"base64url\").toString(\"utf8\"));\n } catch {\n throw new PreviewAuthError(401, \"The Farm Preview credential is invalid.\");\n }\n if (\n typeof claims !== \"object\" ||\n claims === null ||\n Array.isArray(claims) ||\n !(\"issuedAt\" in claims) ||\n !(\"expiresAt\" in claims) ||\n !Number.isSafeInteger(claims.issuedAt) ||\n !Number.isSafeInteger(claims.expiresAt) ||\n Number(claims.issuedAt) > Date.now() + MAX_CLOCK_SKEW_MS ||\n Number(claims.expiresAt) <= Date.now()\n ) {\n throw new PreviewAuthError(401, \"The Farm Preview credential has expired or is invalid.\");\n }\n return claims as Record<string, unknown>;\n}\n","import type { IncomingHttpHeaders, IncomingMessage, ServerResponse } from \"node:http\";\nimport { setTimeout as delay } from \"node:timers/promises\";\nimport {\n authorizePreviewAccount,\n exchangeGitHubPreviewToken,\n getPreviewAuthPublicConfig,\n issuePreviewTunnelGrant,\n PreviewAuthError,\n readPreviewBearerToken,\n verifyPreviewAccountToken,\n verifyPreviewTunnelGrant,\n type PreviewAuthExchangeRateLimitResult,\n type PreviewManagedAuthOptions,\n} from \"./auth.js\";\n\nexport {\n exchangeGitHubPreviewToken,\n getPreviewAuthPublicConfig,\n issuePreviewTunnelGrant,\n PreviewAuthError,\n readPreviewBearerToken,\n verifyPreviewAccountToken,\n verifyPreviewTunnelGrant,\n type PreviewAccountClaims,\n type PreviewAuthExchangeRateLimiter,\n type PreviewAuthExchangeRateLimitResult,\n type PreviewAccountIdentity,\n type PreviewManagedAuthOptions,\n type PreviewTunnelGrant,\n type PreviewTunnelGrantClaims,\n} from \"./auth.js\";\n\nexport interface PreviewSessionAccess {\n id: string;\n name: string;\n ownerId?: string;\n project?: string;\n keyId?: string;\n grantId?: string;\n expiresAt?: number;\n}\n\nexport interface PreviewGatewayOptions {\n /** Required access control when supplied. Denials and errors fail closed, unlike telemetry. */\n authorizeSession?: (session: PreviewSessionAccess) => Promise<boolean>;\n /** Optional metadata-only telemetry. Failures never interrupt a preview. */\n observer?: PreviewGatewayObserver;\n domain?: string;\n baseUrl?: string;\n store?: PreviewGatewayStore;\n sessionTtlMs?: number;\n clientHeartbeatTimeoutMs?: number;\n requestTimeoutMs?: number;\n pollTimeoutMs?: number;\n pollIntervalMs?: number;\n maxBodyBytes?: number;\n maxResponseBodyBytes?: number;\n /** Managed account login and one-preview grant configuration. */\n auth?: PreviewManagedAuthOptions;\n}\n\nexport interface PreviewGatewaySession {\n grantId?: string;\n project?: string;\n keyId?: string;\n ownerId?: string;\n id: string;\n name: string;\n hostname: string;\n publicUrl: string;\n token: string;\n localUrl?: string;\n createdAt: number;\n expiresAt: number;\n /** Internal idle expiry marker. Missing means expiresAt is an absolute cap. */\n slidingExpiration?: true;\n lastHeartbeatAt?: number;\n}\n\nexport interface PreviewGatewayRequest {\n id: string;\n method: string;\n path: string;\n headers: Record<string, string>;\n body?: string | null;\n encoding?: \"base64\";\n createdAt: number;\n cancelled?: true;\n}\n\nexport interface PreviewGatewayResponse {\n status: number;\n // A header may carry multiple values (notably Set-Cookie: a login response\n // commonly sets a session cookie and a CSRF cookie). Those must survive as\n // an array through the store and back onto the public response.\n headers?: Record<string, string | string[]>;\n body?: string | null;\n encoding?: \"base64\";\n}\n\nexport interface PreviewGatewayStore {\n createSession(session: PreviewGatewaySession, ttlMs: number): Promise<void>;\n getSessionByName(name: string): Promise<PreviewGatewaySession | undefined>;\n getSessionById(id: string): Promise<PreviewGatewaySession | undefined>;\n touchSession(session: PreviewGatewaySession, ttlMs: number): Promise<void>;\n enqueueRequest(sessionId: string, request: PreviewGatewayRequest, ttlMs: number): Promise<void>;\n takeRequests(sessionId: string, limit: number): Promise<PreviewGatewayRequest[]>;\n saveResponse(\n sessionId: string,\n requestId: string,\n response: PreviewGatewayResponse,\n ttlMs: number,\n ): Promise<void>;\n getResponse(sessionId: string, requestId: string): Promise<PreviewGatewayResponse | undefined>;\n deleteResponse(sessionId: string, requestId: string): Promise<void>;\n deleteSession(session: PreviewGatewaySession): Promise<void>;\n}\n\ninterface PreviewGatewayRuntimeConfig {\n authorizeSession?: PreviewGatewayOptions[\"authorizeSession\"];\n observer?: PreviewGatewayObserver;\n domain: string;\n baseUrl?: string;\n sessionTtlMs: number;\n clientHeartbeatTimeoutMs: number;\n requestTimeoutMs: number;\n pollTimeoutMs: number;\n pollIntervalMs: number;\n maxBodyBytes: number;\n maxResponseBodyBytes: number;\n auth?: PreviewManagedAuthOptions;\n}\n\nexport interface PreviewGatewayObserver {\n session?(event: {\n grantId?: string;\n project?: string;\n keyId?: string;\n id: string;\n name: string;\n ownerId?: string;\n publicUrl: string;\n expiresAt?: number;\n state: \"connected\" | \"disconnected\";\n at: number;\n }): void | Promise<void>;\n request?(event: {\n sessionId: string;\n method: string;\n path: string;\n status: number;\n durationMs: number;\n at: number;\n }): void | Promise<void>;\n}\n\nfunction observe(callback: (() => void | Promise<void>) | undefined) {\n if (!callback) return;\n try {\n void Promise.resolve(callback()).catch(() => undefined);\n } catch {\n /* Optional telemetry. */\n }\n}\n\nfunction reportSession(\n config: PreviewGatewayRuntimeConfig,\n session: PreviewGatewaySession,\n state: \"connected\" | \"disconnected\",\n) {\n if (!config.observer?.session) return;\n observe(() =>\n config.observer!.session!({\n id: session.id,\n grantId: session.grantId,\n name: session.name,\n project: session.project,\n keyId: session.keyId,\n ownerId: session.ownerId,\n publicUrl: session.publicUrl,\n expiresAt: session.expiresAt,\n state,\n at: Date.now(),\n }),\n );\n}\n\nconst DEFAULT_DOMAIN = \"preview.farmjs.dev\";\nconst DEFAULT_SESSION_TTL_MS = 1000 * 60 * 30;\nconst DEFAULT_CLIENT_HEARTBEAT_TIMEOUT_MS = 1000 * 20;\nconst DEFAULT_REQUEST_TIMEOUT_MS = 25000;\nconst DEFAULT_POLL_TIMEOUT_MS = 15000;\nconst DEFAULT_POLL_INTERVAL_MS = 100;\nconst DEFAULT_MAX_BODY_BYTES = 1024 * 1024 * 5;\nconst DEFAULT_MAX_RESPONSE_BODY_BYTES = 1024 * 1024 * 5;\nconst DEFAULT_POLL_REQUEST_LIMIT = 50;\nconst HOP_BY_HOP_HEADERS = new Set([\n \"connection\",\n \"content-length\",\n \"keep-alive\",\n \"proxy-authenticate\",\n \"proxy-authorization\",\n \"te\",\n \"trailer\",\n \"transfer-encoding\",\n \"upgrade\",\n]);\n\nfunction getHopByHopHeaderNames(headers: Headers | Record<string, string | string[]>): Set<string> {\n const names = new Set(HOP_BY_HOP_HEADERS);\n const connection =\n headers instanceof Headers\n ? headers.get(\"connection\")\n : Object.entries(headers).find(([name]) => name.toLowerCase() === \"connection\")?.[1];\n const values = Array.isArray(connection) ? connection : connection ? [connection] : [];\n for (const value of values) {\n for (const name of value.split(\",\")) {\n const normalized = name.trim().toLowerCase();\n if (normalized) names.add(normalized);\n }\n }\n return names;\n}\n\n/**\n * A gateway request handler, plus the name-ownership probe another preview\n * transport needs before it claims the same public hostname.\n */\nexport type PreviewGatewayHandler = ((request: Request) => Promise<Response>) & {\n isPreviewNameClaimed(name: string): Promise<boolean>;\n};\n\nexport type NodePreviewGatewayHandler = ((\n req: IncomingMessage,\n res: ServerResponse,\n) => Promise<void>) & {\n isPreviewNameClaimed(name: string): Promise<boolean>;\n};\n\nexport function createPreviewGatewayHandler(\n options: PreviewGatewayOptions = {},\n): PreviewGatewayHandler {\n if (options.auth) getPreviewAuthPublicConfig(options.auth);\n const store = options.store || createPreviewGatewayStoreFromEnv();\n const config = {\n authorizeSession: options.authorizeSession,\n observer: options.observer,\n domain: normalizeDomain(options.domain || process.env.FARM_PREVIEW_DOMAIN || DEFAULT_DOMAIN),\n baseUrl: options.baseUrl || process.env.FARM_PREVIEW_GATEWAY_URL,\n sessionTtlMs: options.sessionTtlMs ?? DEFAULT_SESSION_TTL_MS,\n clientHeartbeatTimeoutMs:\n options.clientHeartbeatTimeoutMs ?? DEFAULT_CLIENT_HEARTBEAT_TIMEOUT_MS,\n requestTimeoutMs: options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS,\n pollTimeoutMs: options.pollTimeoutMs ?? DEFAULT_POLL_TIMEOUT_MS,\n pollIntervalMs: options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS,\n maxBodyBytes: options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES,\n maxResponseBodyBytes: options.maxResponseBodyBytes ?? DEFAULT_MAX_RESPONSE_BODY_BYTES,\n auth: options.auth,\n };\n\n const handlePreviewGatewayRequest = async function handlePreviewGatewayRequest(\n request: Request,\n ): Promise<Response> {\n try {\n const url = new URL(request.url);\n\n if (request.method === \"GET\" && url.pathname === \"/api/health\") {\n return json({\n ok: true,\n domain: config.domain,\n store: store.constructor.name || \"PreviewGatewayStore\",\n });\n }\n\n if (request.method === \"GET\" && url.pathname === \"/api/auth/config\") {\n return json(\n config.auth\n ? getPreviewAuthPublicConfig(config.auth)\n : {\n enabled: false,\n controlAuth: \"bearer\",\n defaultSessionTtlMs: config.sessionTtlMs,\n maxSessionTtlMs: config.sessionTtlMs,\n },\n );\n }\n\n if (request.method === \"POST\" && url.pathname === \"/api/auth/exchange\") {\n if (!config.auth) return text(\"Managed preview authentication is not configured.\", 404);\n const rateLimitResponse = await limitPreviewAccountExchange(request, config.auth);\n if (rateLimitResponse) return rateLimitResponse;\n return await exchangePreviewAccount(request, config.auth);\n }\n\n if (request.method === \"POST\" && url.pathname === \"/api/tunnel/grants\") {\n if (!config.auth) return text(\"Managed preview authentication is not configured.\", 404);\n if (config.auth.deviceAuth) {\n const limited = await limitPreviewAccountExchange(request, config.auth);\n if (limited) return limited;\n }\n return await createTunnelGrant(request, config.auth);\n }\n\n if (request.method === \"POST\" && url.pathname === \"/api/sessions\") {\n return await createSession(request, store, config);\n }\n\n const sessionRoute = matchSessionRoute(url.pathname);\n if (sessionRoute) {\n return await handleSessionRoute(request, url, store, config, sessionRoute);\n }\n\n const publicRoute = resolvePublicPreviewRoute(request, url, config.domain);\n if (publicRoute) {\n return await proxyPublicRequest(request, url, store, config, publicRoute);\n }\n\n return json(\n {\n ok: true,\n message: \"Farm Preview Gateway\",\n createSession: \"/api/sessions\",\n health: \"/api/health\",\n },\n 200,\n );\n } catch (error) {\n if (error instanceof GatewayHttpError || error instanceof PreviewAuthError) {\n return text(error.message, error.status);\n }\n return text(\n error instanceof Error ? error.message : \"Unexpected preview gateway error.\",\n 500,\n );\n }\n };\n\n // The persistent relay serves the same public hostnames from a separate name\n // namespace, and its route wins over this handler, so it has to be able to\n // ask whether a live polling session already owns a name before accepting a\n // claim for it.\n return Object.assign(handlePreviewGatewayRequest, {\n async isPreviewNameClaimed(name: string) {\n const claimed = sanitizePreviewName(name);\n if (!claimed) return false;\n const session = await store.getSessionByName(claimed);\n return Boolean(session && isPreviewClientOnline(session, config));\n },\n });\n}\n\nasync function limitPreviewAccountExchange(request: Request, auth: PreviewManagedAuthOptions) {\n if (!auth.rateLimitExchange) return undefined;\n let result: PreviewAuthExchangeRateLimitResult;\n try {\n result = await auth.rateLimitExchange(request);\n } catch {\n return new Response(\"Farm Preview login is temporarily unavailable. Try again shortly.\", {\n status: 503,\n headers: {\n \"cache-control\": \"no-store\",\n \"content-type\": \"text/plain; charset=utf-8\",\n \"retry-after\": \"1\",\n },\n });\n }\n if (result.allowed) return undefined;\n\n const retryAfterMs =\n Number.isFinite(result.retryAfterMs) && (result.retryAfterMs ?? 0) > 0\n ? result.retryAfterMs!\n : 1000;\n return new Response(\"Too many Farm Preview login attempts. Try again shortly.\", {\n status: 429,\n headers: {\n \"cache-control\": \"no-store\",\n \"content-type\": \"text/plain; charset=utf-8\",\n \"retry-after\": String(Math.max(1, Math.ceil(retryAfterMs / 1000))),\n },\n });\n}\n\nasync function exchangePreviewAccount(request: Request, auth: PreviewManagedAuthOptions) {\n const input = (await request.json().catch(() => ({}))) as {\n provider?: string;\n accessToken?: string;\n };\n if (input.provider !== \"github\") {\n throw new PreviewAuthError(400, \"Farm Preview currently supports GitHub sign-in.\");\n }\n return json(await exchangeGitHubPreviewToken(input.accessToken || \"\", auth));\n}\n\nasync function createTunnelGrant(request: Request, auth: PreviewManagedAuthOptions) {\n const accountToken = readPreviewBearerToken(request);\n if (!accountToken) {\n throw new PreviewAuthError(401, \"Sign in with Farm Preview before creating a tunnel grant.\");\n }\n const account = await authorizePreviewAccount(accountToken, auth);\n const input = (await request.json().catch(() => ({}))) as {\n name?: string;\n project?: string;\n expiresInMs?: number;\n };\n const name = sanitizePreviewName(input.name);\n if (!name) throw new PreviewAuthError(400, \"A valid preview name is required.\");\n\n return json(\n issuePreviewTunnelGrant(\n account,\n {\n name,\n project: input.project,\n ...(input.expiresInMs !== undefined ? { expiresInMs: input.expiresInMs } : {}),\n },\n auth,\n ),\n );\n}\n\nexport function createNodePreviewGatewayHandler(\n options: PreviewGatewayOptions = {},\n): NodePreviewGatewayHandler {\n const handler = createPreviewGatewayHandler(options);\n\n const nodePreviewGatewayHandler = async function nodePreviewGatewayHandler(\n req: IncomingMessage,\n res: ServerResponse,\n ) {\n const controller = new AbortController();\n const abort = () => controller.abort();\n const abortOnClose = () => {\n if (!res.writableEnded) abort();\n };\n req.once(\"aborted\", abort);\n res.once(\"close\", abortOnClose);\n try {\n const response = await handler(nodeRequestToWebRequest(req, controller.signal));\n if (controller.signal.aborted || res.destroyed) {\n await response.body?.cancel();\n return;\n }\n res.statusCode = response.status;\n // Headers.forEach folds repeated headers into one comma-joined value, which\n // corrupts multiple Set-Cookie. Emit those separately as an array so each\n // cookie becomes its own header line.\n const setCookies = response.headers.getSetCookie?.() ?? [];\n response.headers.forEach((value, key) => {\n if (key.toLowerCase() === \"set-cookie\") return;\n res.setHeader(key, value);\n });\n if (setCookies.length > 0) {\n res.setHeader(\"set-cookie\", setCookies);\n }\n res.end(Buffer.from(await response.arrayBuffer()));\n } finally {\n req.off(\"aborted\", abort);\n res.off(\"close\", abortOnClose);\n }\n };\n\n return Object.assign(nodePreviewGatewayHandler, {\n isPreviewNameClaimed: handler.isPreviewNameClaimed,\n });\n}\n\nexport function createPreviewGatewayStoreFromEnv(): PreviewGatewayStore {\n const redisUrl = process.env.UPSTASH_REDIS_REST_URL || process.env.KV_REST_API_URL;\n const redisToken = process.env.UPSTASH_REDIS_REST_TOKEN || process.env.KV_REST_API_TOKEN;\n\n if (redisUrl && redisToken) {\n return new RedisRestPreviewGatewayStore({\n url: redisUrl,\n token: redisToken,\n });\n }\n\n return new MemoryPreviewGatewayStore();\n}\n\nexport class MemoryPreviewGatewayStore implements PreviewGatewayStore {\n private sessions = new Map<string, PreviewGatewaySession>();\n private names = new Map<string, string>();\n private queues = new Map<string, PreviewGatewayRequest[]>();\n private responses = new Map<string, PreviewGatewayResponse>();\n\n async createSession(session: PreviewGatewaySession): Promise<void> {\n this.sessions.set(session.id, session);\n this.names.set(session.name, session.id);\n this.queues.set(session.id, []);\n }\n\n async getSessionByName(name: string): Promise<PreviewGatewaySession | undefined> {\n const id = this.names.get(name);\n return id ? this.getSessionById(id) : undefined;\n }\n\n async getSessionById(id: string): Promise<PreviewGatewaySession | undefined> {\n const session = this.sessions.get(id);\n if (!session) return undefined;\n if (session.expiresAt <= Date.now()) {\n await this.deleteSession(session);\n return undefined;\n }\n return session;\n }\n\n async touchSession(session: PreviewGatewaySession, _ttlMs: number): Promise<void> {\n // A concurrent delete (the preview was stopped) must win: a poll handler\n // that outlives the DELETE must never resurrect a session that is no\n // longer stored, or its name would look \"online\" and block a restart.\n if (!this.sessions.has(session.id)) return;\n this.sessions.set(session.id, session);\n const nameOwner = this.names.get(session.name);\n if (!nameOwner || nameOwner === session.id) {\n this.names.set(session.name, session.id);\n }\n }\n\n async enqueueRequest(sessionId: string, request: PreviewGatewayRequest): Promise<void> {\n const queue = this.queues.get(sessionId) || [];\n queue.push(request);\n this.queues.set(sessionId, queue);\n }\n\n async takeRequests(sessionId: string, limit: number): Promise<PreviewGatewayRequest[]> {\n const queue = this.queues.get(sessionId) || [];\n return queue.splice(0, limit);\n }\n\n async saveResponse(\n sessionId: string,\n requestId: string,\n response: PreviewGatewayResponse,\n ): Promise<void> {\n this.responses.set(responseKey(sessionId, requestId), response);\n }\n\n async getResponse(\n sessionId: string,\n requestId: string,\n ): Promise<PreviewGatewayResponse | undefined> {\n return this.responses.get(responseKey(sessionId, requestId));\n }\n\n async deleteResponse(sessionId: string, requestId: string): Promise<void> {\n this.responses.delete(responseKey(sessionId, requestId));\n }\n\n async deleteSession(session: PreviewGatewaySession): Promise<void> {\n this.sessions.delete(session.id);\n // The name may have been taken over by a newer session; only remove the\n // mapping if it still points at the session being deleted.\n if (this.names.get(session.name) === session.id) {\n this.names.delete(session.name);\n }\n this.queues.delete(session.id);\n }\n}\n\nexport class RedisRestPreviewGatewayStore implements PreviewGatewayStore {\n constructor(private options: { url: string; token: string; prefix?: string }) {}\n\n async createSession(session: PreviewGatewaySession, ttlMs: number): Promise<void> {\n await this.command(\"SET\", this.sessionKey(session.id), JSON.stringify(session), \"PX\", ttlMs);\n await this.command(\"SET\", this.nameKey(session.name), session.id, \"PX\", ttlMs);\n await this.command(\"DEL\", this.queueKey(session.id));\n }\n\n async getSessionByName(name: string): Promise<PreviewGatewaySession | undefined> {\n const id = await this.command<string | null>(\"GET\", this.nameKey(name));\n return id ? this.getSessionById(id) : undefined;\n }\n\n async getSessionById(id: string): Promise<PreviewGatewaySession | undefined> {\n const value = await this.command<string | null>(\"GET\", this.sessionKey(id));\n return value ? JSON.parse(value) : undefined;\n }\n\n async touchSession(session: PreviewGatewaySession, ttlMs: number): Promise<void> {\n const nextSession = { ...session };\n const remainingTtlMs = Math.max(1, Math.min(ttlMs, session.expiresAt - Date.now()));\n // XX only refreshes a session key that still exists. A poll handler that\n // outlives a DELETE must not recreate the session (nor its name mapping),\n // which would otherwise block reclaiming the name on restart.\n const result = await this.command<string | null>(\n \"SET\",\n this.sessionKey(session.id),\n JSON.stringify(nextSession),\n \"PX\",\n remainingTtlMs,\n \"XX\",\n );\n if (result === null) return;\n const nameOwner = await this.command<string | null>(\"GET\", this.nameKey(session.name));\n if (!nameOwner || nameOwner === session.id) {\n await this.command(\"SET\", this.nameKey(session.name), session.id, \"PX\", remainingTtlMs);\n }\n await this.command(\"EXPIRE\", this.queueKey(session.id), Math.ceil(remainingTtlMs / 1000));\n }\n\n async enqueueRequest(\n sessionId: string,\n request: PreviewGatewayRequest,\n ttlMs: number,\n ): Promise<void> {\n await this.command(\"RPUSH\", this.queueKey(sessionId), JSON.stringify(request));\n await this.command(\"EXPIRE\", this.queueKey(sessionId), Math.ceil(ttlMs / 1000));\n }\n\n async takeRequests(sessionId: string, limit: number): Promise<PreviewGatewayRequest[]> {\n const values = await this.command<string[] | string | null>(\n \"LPOP\",\n this.queueKey(sessionId),\n limit,\n );\n if (!values) return [];\n const list = Array.isArray(values) ? values : [values];\n return list.map((value) => JSON.parse(value));\n }\n\n async saveResponse(\n sessionId: string,\n requestId: string,\n response: PreviewGatewayResponse,\n ttlMs: number,\n ): Promise<void> {\n await this.command(\n \"SET\",\n this.responseKey(sessionId, requestId),\n JSON.stringify(response),\n \"PX\",\n ttlMs,\n );\n }\n\n async getResponse(\n sessionId: string,\n requestId: string,\n ): Promise<PreviewGatewayResponse | undefined> {\n const value = await this.command<string | null>(\"GET\", this.responseKey(sessionId, requestId));\n return value ? JSON.parse(value) : undefined;\n }\n\n async deleteResponse(sessionId: string, requestId: string): Promise<void> {\n await this.command(\"DEL\", this.responseKey(sessionId, requestId));\n }\n\n async deleteSession(session: PreviewGatewaySession): Promise<void> {\n const keys = [this.sessionKey(session.id), this.queueKey(session.id)];\n // The name may have been taken over by a newer session; only remove the\n // mapping if it still points at the session being deleted.\n const nameOwner = await this.command<string | null>(\"GET\", this.nameKey(session.name));\n if (nameOwner === session.id) {\n keys.push(this.nameKey(session.name));\n }\n await this.command(\"DEL\", ...keys);\n }\n\n private async command<T = unknown>(...args: Array<string | number>) {\n const response = await fetch(this.options.url, {\n method: \"POST\",\n headers: {\n authorization: `Bearer ${this.options.token}`,\n \"content-type\": \"application/json\",\n },\n body: JSON.stringify(args),\n });\n\n if (!response.ok) {\n throw new Error(\n `Preview gateway Redis command failed (${response.status}): ${await response.text()}`,\n );\n }\n\n const data = (await response.json()) as { result?: T; error?: string };\n if (data.error) {\n throw new Error(`Preview gateway Redis command failed: ${data.error}`);\n }\n return data.result as T;\n }\n\n private key(value: string) {\n return `${this.options.prefix || \"farm-preview\"}:${value}`;\n }\n\n private sessionKey(id: string) {\n return this.key(`session:${id}`);\n }\n\n private nameKey(name: string) {\n return this.key(`name:${name}`);\n }\n\n private queueKey(id: string) {\n return this.key(`queue:${id}`);\n }\n\n private responseKey(sessionId: string, requestId: string) {\n return this.key(`response:${sessionId}:${requestId}`);\n }\n}\n\nasync function createSession(\n request: Request,\n store: PreviewGatewayStore,\n config: PreviewGatewayRuntimeConfig,\n) {\n const input = (await request.json().catch(() => ({}))) as {\n name?: string;\n localUrl?: string;\n expiresInMs?: number;\n };\n const requestedName = sanitizePreviewName(input.name);\n let name = requestedName || randomPreviewName();\n let expiresAt = Date.now() + config.sessionTtlMs;\n let slidingExpiration = true;\n let ownerId: string | undefined;\n let project: string | undefined;\n let keyId: string | undefined;\n let grantId: string | undefined;\n\n if (config.auth) {\n const grantToken = readPreviewBearerToken(request);\n if (!grantToken) {\n throw new GatewayHttpError(401, \"Sign in with Farm Preview before creating a session.\");\n }\n try {\n const grant = verifyPreviewTunnelGrant(grantToken, {\n signingSecret: config.auth.signingSecret,\n ...(requestedName ? { name: requestedName } : {}),\n });\n name = grant.name;\n ownerId = `${grant.provider || \"github\"}:${grant.subject}`;\n project = grant.project ?? grant.name;\n keyId = grant.keyId;\n grantId = grant.nonce;\n expiresAt = grant.expiresAt;\n slidingExpiration = false;\n } catch (error) {\n if (error instanceof PreviewAuthError)\n throw new GatewayHttpError(error.status, error.message);\n throw error;\n }\n } else if (Number.isFinite(input.expiresInMs)) {\n expiresAt = Date.now() + clamp(Number(input.expiresInMs), 60_000, config.sessionTtlMs);\n slidingExpiration = false;\n }\n\n // A name that is still owned by a live session cannot be claimed by a new\n // one — otherwise any caller could repoint an active preview's public URL\n // to their own session. Stale sessions (missed heartbeats) may be taken\n // over, matching how public routing treats them.\n for (let attempt = 0; ; attempt++) {\n const existing = await store.getSessionByName(name);\n if (!existing || !isPreviewClientOnline(existing, config)) {\n break;\n }\n if (requestedName || config.auth) {\n return text(\n `A Farm preview named \"${name}\" is already active. Choose another name or stop the running preview.`,\n 409,\n );\n }\n if (attempt >= 4) {\n return text(\"Could not allocate a unique preview name. Try again.\", 503);\n }\n name = randomPreviewName();\n }\n\n const hostname = `${name}.${config.domain}`;\n const session: PreviewGatewaySession = {\n ...(grantId ? { grantId } : {}),\n ...(project ? { project } : {}),\n ...(keyId ? { keyId } : {}),\n ...(ownerId ? { ownerId } : {}),\n id: randomId(\"sess\"),\n name,\n hostname,\n publicUrl: createPublicUrl(request, config, name),\n token: randomId(\"token\"),\n localUrl: input.localUrl,\n createdAt: Date.now(),\n expiresAt,\n ...(slidingExpiration ? { slidingExpiration: true as const } : {}),\n lastHeartbeatAt: Date.now(),\n };\n\n await requireSessionAccess(config, session);\n await store.createSession(session, Math.max(1, expiresAt - Date.now()));\n reportSession(config, session, \"connected\");\n\n return json({\n id: session.id,\n name: session.name,\n token: session.token,\n publicUrl: session.publicUrl,\n ...(session.slidingExpiration ? {} : { expiresAt: session.expiresAt }),\n });\n}\n\nasync function handleSessionRoute(\n request: Request,\n url: URL,\n store: PreviewGatewayStore,\n config: PreviewGatewayRuntimeConfig,\n route: { sessionId: string; action?: string; requestId?: string },\n) {\n const session = await requireSession(\n store,\n route.sessionId,\n readPreviewBearerToken(request) || url.searchParams.get(\"token\"),\n );\n\n // Cleanup is still possible when the authority is unavailable or the grant is revoked.\n if (request.method !== \"DELETE\" || route.action) await requireSessionAccess(config, session);\n\n if (request.method === \"GET\" && route.action === \"requests\") {\n return await pollSessionRequests(url, store, config, session);\n }\n\n if (request.method === \"POST\" && route.action === \"responses\" && route.requestId) {\n let response: PreviewGatewayResponse;\n try {\n response = await parsePreviewResponse(request, config.maxResponseBodyBytes);\n } catch (error) {\n if (error instanceof GatewayHttpError && error.status === 413) {\n await store.saveResponse(\n session.id,\n route.requestId,\n createOversizedPreviewResponse(config.maxResponseBodyBytes),\n remainingSessionTtl(session, config),\n );\n }\n throw error;\n }\n await store.saveResponse(\n session.id,\n route.requestId,\n response,\n remainingSessionTtl(session, config),\n );\n await markSessionOnline(store, config, session);\n return json({ ok: true });\n }\n\n if (request.method === \"POST\" && route.action === \"heartbeat\") {\n await markSessionOnline(store, config, session);\n return json({\n ok: true,\n ...(session.slidingExpiration ? {} : { expiresAt: session.expiresAt }),\n });\n }\n\n if (request.method === \"DELETE\" && !route.action) {\n await store.deleteSession(session);\n reportSession(config, session, \"disconnected\");\n return json({ ok: true });\n }\n\n return text(\"Preview gateway route not found.\", 404);\n}\n\nasync function parsePreviewResponse(request: Request, maxBodyBytes: number) {\n const maxEncodedBytes = Math.ceil(maxBodyBytes / 3) * 4 + 64 * 1024;\n const contentLength = Number(request.headers.get(\"content-length\") || 0);\n if (Number.isFinite(contentLength) && contentLength > maxEncodedBytes) {\n await request.body?.cancel();\n throw new GatewayHttpError(413, \"Preview response body is too large.\");\n }\n\n const bytes = await readBodyWithLimit(\n request,\n maxEncodedBytes,\n \"Preview response body is too large.\",\n );\n let response: PreviewGatewayResponse;\n try {\n response = JSON.parse(bytes.toString(\"utf8\")) as PreviewGatewayResponse;\n } catch {\n throw new GatewayHttpError(400, \"Preview response body must be valid JSON.\");\n }\n\n if (getPreviewResponseBodySize(response) > maxBodyBytes) {\n throw new GatewayHttpError(413, \"Preview response body is too large.\");\n }\n return response;\n}\n\nfunction getPreviewResponseBodySize(response: PreviewGatewayResponse) {\n if (!response.body) return 0;\n return Buffer.byteLength(response.body, response.encoding === \"base64\" ? \"base64\" : \"utf8\");\n}\n\nfunction createOversizedPreviewResponse(maxBodyBytes: number): PreviewGatewayResponse {\n return {\n status: 502,\n headers: { \"content-type\": \"text/plain; charset=utf-8\" },\n body: Buffer.from(\n `The local preview response exceeded the ${maxBodyBytes} byte limit.`,\n ).toString(\"base64\"),\n encoding: \"base64\",\n };\n}\n\nasync function pollSessionRequests(\n url: URL,\n store: PreviewGatewayStore,\n config: PreviewGatewayRuntimeConfig,\n session: PreviewGatewaySession,\n) {\n const waitMs = clamp(Number(url.searchParams.get(\"wait\") || config.pollTimeoutMs), 1000, 25000);\n const deadline = Date.now() + waitMs;\n let nextAccessCheckAt = Date.now() + 1000;\n\n await markSessionOnline(store, config, session);\n\n while (Date.now() < deadline) {\n if (Date.now() >= nextAccessCheckAt) {\n await requireSessionAccess(config, session);\n nextAccessCheckAt = Date.now() + 1000;\n }\n const requests = await store.takeRequests(session.id, DEFAULT_POLL_REQUEST_LIMIT);\n if (requests.length) {\n await requireSessionAccess(config, session);\n await markSessionOnline(store, config, session);\n return json({ requests });\n }\n await delay(config.pollIntervalMs);\n }\n\n await markSessionOnline(store, config, session);\n return new Response(null, { status: 204 });\n}\n\nasync function proxyPublicRequest(\n request: Request,\n url: URL,\n store: PreviewGatewayStore,\n config: PreviewGatewayRuntimeConfig,\n route: { name: string; path: string },\n) {\n const session = await store.getSessionByName(route.name);\n if (!session) {\n return inactivePreviewResponse(request, route.name);\n }\n if (!isPreviewClientOnline(session, config)) {\n await store.deleteSession(session);\n reportSession(config, session, \"disconnected\");\n return inactivePreviewResponse(request, route.name);\n }\n\n await requireSessionAccess(config, session);\n\n const startedAt = Date.now();\n let status = 500;\n try {\n const previewRequest = await serializePreviewRequest(\n request,\n url,\n route.path,\n config.maxBodyBytes,\n );\n await store.enqueueRequest(session.id, previewRequest, remainingSessionTtl(session, config));\n await store.touchSession(session, remainingSessionTtl(session, config));\n\n const response = await waitForPreviewResponse(\n store,\n config,\n session,\n previewRequest.id,\n request.signal,\n );\n if (response === \"cancelled\") {\n status = 499;\n await store.enqueueRequest(\n session.id,\n {\n ...previewRequest,\n body: undefined,\n encoding: undefined,\n cancelled: true,\n createdAt: Date.now(),\n },\n remainingSessionTtl(session, config),\n );\n return new Response(null, { status: 499 });\n }\n if (response === \"stale\") {\n status = 503;\n return inactivePreviewResponse(request, route.name);\n }\n if (!response) {\n status = 504;\n return text(\"The local Farm preview did not respond before the gateway timed out.\", 504);\n }\n\n const headers = new Headers();\n const responseHopByHopHeaders = getHopByHopHeaderNames(response.headers || {});\n for (const [key, value] of Object.entries(response.headers || {})) {\n const normalized = key.toLowerCase();\n if (responseHopByHopHeaders.has(normalized) || normalized === \"content-encoding\") {\n continue;\n }\n // Append multi-valued headers (Set-Cookie) so every value reaches the\n // visitor; Headers.set would keep only the last.\n for (const single of Array.isArray(value) ? value : [value]) {\n headers.append(key, single);\n }\n }\n headers.set(\"cache-control\", \"no-store\");\n headers.set(\"x-farm-preview\", session.name);\n\n status = response.status || 200;\n return new Response(\n response.body\n ? Buffer.from(response.body, response.encoding === \"base64\" ? \"base64\" : \"utf8\")\n : null,\n {\n status,\n headers,\n },\n );\n } catch (error) {\n if (error instanceof GatewayHttpError) status = error.status;\n throw error;\n } finally {\n if (config.observer?.request)\n observe(() =>\n config.observer!.request!({\n sessionId: session.id,\n method: request.method,\n path: route.path.split(/[?#]/, 1)[0].slice(0, 2048),\n status,\n durationMs: Date.now() - startedAt,\n at: Date.now(),\n }),\n );\n }\n}\n\nasync function waitForPreviewResponse(\n store: PreviewGatewayStore,\n config: PreviewGatewayRuntimeConfig,\n session: PreviewGatewaySession,\n requestId: string,\n signal: AbortSignal,\n) {\n const deadline = Date.now() + config.requestTimeoutMs;\n let nextLivenessCheckAt = 0;\n\n while (Date.now() < deadline) {\n if (signal.aborted) return \"cancelled\";\n if (Date.now() >= nextLivenessCheckAt) {\n await requireSessionAccess(config, session);\n const latestSession = await store.getSessionById(session.id);\n if (!latestSession || !isPreviewClientOnline(latestSession, config)) {\n if (latestSession) await store.deleteSession(latestSession);\n return \"stale\";\n }\n nextLivenessCheckAt = Date.now() + 1000;\n }\n\n const response = await store.getResponse(session.id, requestId);\n if (response) {\n await requireSessionAccess(config, session);\n await store.deleteResponse(session.id, requestId);\n return response;\n }\n try {\n await delay(config.pollIntervalMs, undefined, { signal });\n } catch {\n if (signal.aborted) return \"cancelled\";\n throw new Error(\"Preview response polling was interrupted.\");\n }\n }\n\n return undefined;\n}\n\nasync function requireSessionAccess(\n config: PreviewGatewayRuntimeConfig,\n session: PreviewGatewaySession,\n) {\n if (!config.authorizeSession) return;\n const { id, name, ownerId, project, keyId, grantId, expiresAt } = session;\n let allowed;\n try {\n allowed = await config.authorizeSession({\n id,\n name,\n ownerId,\n project,\n keyId,\n grantId,\n expiresAt,\n });\n } catch {\n throw new GatewayHttpError(503, \"Preview access verification is temporarily unavailable.\");\n }\n if (allowed !== true) throw new GatewayHttpError(403, \"This preview is no longer authorized.\");\n}\n\nasync function markSessionOnline(\n store: PreviewGatewayStore,\n config: PreviewGatewayRuntimeConfig,\n session: PreviewGatewaySession,\n) {\n const now = Date.now();\n session.lastHeartbeatAt = now;\n if (session.slidingExpiration) {\n session.expiresAt = now + config.sessionTtlMs;\n }\n await store.touchSession(session, remainingSessionTtl(session, config));\n reportSession(config, session, \"connected\");\n}\n\nfunction remainingSessionTtl(session: PreviewGatewaySession, config: PreviewGatewayRuntimeConfig) {\n return Math.max(1, Math.min(config.sessionTtlMs, session.expiresAt - Date.now()));\n}\n\nfunction isPreviewClientOnline(\n session: PreviewGatewaySession,\n config: PreviewGatewayRuntimeConfig,\n) {\n const lastHeartbeatAt = session.lastHeartbeatAt || session.createdAt;\n return (\n session.expiresAt > Date.now() &&\n Date.now() - lastHeartbeatAt <= config.clientHeartbeatTimeoutMs\n );\n}\n\nasync function serializePreviewRequest(\n request: Request,\n url: URL,\n routePath: string,\n maxBodyBytes: number,\n): Promise<PreviewGatewayRequest> {\n const method = request.method.toUpperCase();\n const headers: Record<string, string> = {};\n const requestHopByHopHeaders = getHopByHopHeaderNames(request.headers);\n request.headers.forEach((value, key) => {\n const normalized = key.toLowerCase();\n if (\n !requestHopByHopHeaders.has(normalized) &&\n normalized !== \"forwarded\" &&\n !normalized.startsWith(\"x-forwarded-\")\n ) {\n headers[key] = value;\n }\n });\n\n headers[\"x-forwarded-host\"] = request.headers.get(\"host\") || url.host;\n headers[\"x-forwarded-proto\"] = url.protocol.replace(\":\", \"\");\n\n let body: string | undefined;\n if (method !== \"GET\" && method !== \"HEAD\") {\n const contentLength = Number(request.headers.get(\"content-length\") || 0);\n if (contentLength > maxBodyBytes) {\n throw new GatewayHttpError(413, \"Preview request body is too large.\");\n }\n\n const buffer = await readBodyWithLimit(\n request,\n maxBodyBytes,\n \"Preview request body is too large.\",\n );\n body = buffer.toString(\"base64\");\n }\n\n return {\n id: randomId(\"req\"),\n method,\n path: `${routePath}${url.search}`,\n headers,\n body,\n encoding: body ? \"base64\" : undefined,\n createdAt: Date.now(),\n };\n}\n\nasync function readBodyWithLimit(request: Request, maxBytes: number, message: string) {\n if (!request.body) return Buffer.alloc(0);\n const reader = request.body.getReader();\n const chunks: Uint8Array[] = [];\n let size = 0;\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n size += value.byteLength;\n if (size > maxBytes) {\n await reader.cancel();\n throw new GatewayHttpError(413, message);\n }\n chunks.push(value);\n }\n } catch (error) {\n await reader.cancel().catch(() => undefined);\n throw error;\n }\n return Buffer.concat(chunks, size);\n}\n\nasync function requireSession(store: PreviewGatewayStore, sessionId: string, token: string | null) {\n const session = await store.getSessionById(sessionId);\n if (!session || !token || token !== session.token) {\n throw new GatewayHttpError(401, \"Invalid preview gateway session.\");\n }\n return session;\n}\n\nfunction resolvePublicPreviewRoute(request: Request, url: URL, domain: string) {\n const host = (request.headers.get(\"host\") || url.host)\n .split(\":\")[0]\n .trim()\n .toLowerCase()\n .replace(/\\.$/, \"\");\n\n // Every preview subdomain is its own origin. Honoring the path form there\n // would let `alice.<domain>/__preview/bob` serve bob's app, cookies, and\n // scripts on alice's origin, so tenant hosts route only by subdomain.\n if (host.endsWith(`.${domain}`)) {\n return {\n name: host.slice(0, -(domain.length + 1)),\n path: url.pathname || \"/\",\n };\n }\n\n const pathMatch = url.pathname.match(/^\\/__preview\\/([^/]+)(\\/.*)?$/);\n if (!pathMatch) return undefined;\n return {\n name: pathMatch[1],\n path: pathMatch[2] || \"/\",\n };\n}\n\nfunction matchSessionRoute(pathname: string) {\n const match = pathname.match(\n /^\\/api\\/sessions\\/([^/]+)(?:\\/(requests|heartbeat|responses)(?:\\/([^/]+))?)?$/,\n );\n if (!match) return undefined;\n return {\n sessionId: match[1],\n action: match[2],\n requestId: match[3],\n };\n}\n\nfunction nodeRequestToWebRequest(req: IncomingMessage, signal?: AbortSignal) {\n const host = headerValue(req.headers, \"host\") || \"localhost\";\n // This adapter is the public trust boundary. A visitor-controlled forwarded\n // protocol must not influence the authority serialized to the local app.\n const protocol = isLocalHost(host.split(\":\")[0]) ? \"http\" : \"https\";\n const url = `${protocol}://${host}${req.url || \"/\"}`;\n const headers = nodeHeadersToWebHeaders(req.headers);\n const method = req.method || \"GET\";\n const init: RequestInit & { duplex?: \"half\" } = {\n method,\n headers,\n signal,\n };\n\n if (method !== \"GET\" && method !== \"HEAD\") {\n init.body = req as unknown as BodyInit;\n init.duplex = \"half\";\n }\n\n return new Request(url, init);\n}\n\nfunction nodeHeadersToWebHeaders(headers: IncomingHttpHeaders) {\n const next = new Headers();\n for (const [key, value] of Object.entries(headers)) {\n if (value === undefined) continue;\n if (Array.isArray(value)) {\n for (const item of value) next.append(key, item);\n } else {\n next.set(key, value);\n }\n }\n return next;\n}\n\nfunction headerValue(headers: IncomingHttpHeaders, key: string) {\n const value = headers[key];\n return Array.isArray(value) ? value[0] : value;\n}\n\nfunction createPublicUrl(request: Request, config: PreviewGatewayRuntimeConfig, name: string) {\n // Each browser preview needs its own origin: a path prefix cannot relocate\n // root-relative scripts, imports, fetches, or client-side navigation.\n if (config.domain === \"localhost\" || config.domain.endsWith(\".localhost\")) {\n const base = new URL(config.baseUrl || request.url);\n base.hostname = `${name}.${config.domain}`;\n return base.origin;\n }\n if (config.baseUrl) {\n const base = new URL(config.baseUrl);\n if (isLocalHost(base.hostname)) {\n return `${config.baseUrl.replace(/\\/+$/, \"\")}/__preview/${name}`;\n }\n }\n\n const host = request.headers.get(\"host\") || new URL(request.url).host;\n if (isLocalHost(host.split(\":\")[0])) {\n return `${new URL(request.url).origin}/__preview/${name}`;\n }\n\n return `https://${name}.${config.domain}`;\n}\n\nfunction json(value: unknown, status = 200) {\n return new Response(JSON.stringify(value), {\n status,\n headers: {\n \"content-type\": \"application/json\",\n \"cache-control\": \"no-store\",\n },\n });\n}\n\nfunction text(value: string, status = 200) {\n return new Response(value, {\n status,\n headers: {\n \"content-type\": \"text/plain; charset=utf-8\",\n \"cache-control\": \"no-store\",\n },\n });\n}\n\nfunction inactivePreviewResponse(request: Request, name: string) {\n if (!request.headers.get(\"accept\")?.includes(\"text/html\")) {\n return text(`No active Farm preview is running for \"${name}\".`, 404);\n }\n const safeName = name.replace(/[&<>\"']/g, (character) => {\n if (character === \"&\") return \"&\";\n if (character === \"<\") return \"<\";\n if (character === \">\") return \">\";\n if (character === '\"') return \""\";\n return \"'\";\n });\n return new Response(\n `<!doctype html><html lang=\"en\"><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"><title>Farm preview expired</title><style>body{font:16px/1.5 system-ui,sans-serif;display:grid;min-height:100vh;margin:0;place-items:center;background:#f7f7f5;color:#171714}main{max-width:34rem;padding:2rem;text-align:center}h1{font-size:1.75rem}code{background:#e9e9e4;border-radius:.35rem;padding:.15rem .35rem}footer{margin-top:2.5rem;padding-top:1.5rem;border-top:1px solid #e3e3de;color:#6b6b63;font-size:.875rem}footer p{margin:0 0 1rem}footer p a{color:#171714;text-underline-offset:.2em}.start{display:inline-block;border-radius:.5rem;padding:.5rem .9rem;background:#171714;color:#f7f7f5;font-weight:500;text-decoration:none}</style><main><h1>This Farm preview has expired</h1><p>The temporary preview <code>${safeName}</code> is no longer connected.</p><p>Ask its owner to run <code>farm preview</code> again.</p><footer><p>Previews like this come from <a href=\"https://farmjs.dev\">Farm.js</a>. One command shares your local app.</p><a class=\"start\" href=\"https://farmjs.dev/docs/preview\">Try farm preview</a></footer></main></html>`,\n {\n status: 410,\n headers: {\n \"content-type\": \"text/html; charset=utf-8\",\n \"cache-control\": \"no-store\",\n },\n },\n );\n}\n\nfunction sanitizePreviewName(value: string | undefined) {\n if (!value) return undefined;\n return value\n .toLowerCase()\n .replace(/[^a-z0-9-]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .slice(0, 48);\n}\n\nfunction randomPreviewName() {\n return `farm-${Math.random().toString(36).slice(2, 8)}`;\n}\n\nfunction randomId(prefix: string) {\n return `${prefix}_${crypto.randomUUID().replace(/-/g, \"\")}`;\n}\n\nfunction normalizeDomain(value: string) {\n return value\n .trim()\n .toLowerCase()\n .replace(/^https?:\\/\\//, \"\")\n .replace(/^\\.*/, \"\")\n .replace(/\\/*$/, \"\");\n}\n\nfunction responseKey(sessionId: string, requestId: string) {\n return `${sessionId}:${requestId}`;\n}\n\nfunction clamp(value: number, min: number, max: number) {\n if (!Number.isFinite(value)) return min;\n return Math.max(min, Math.min(max, value));\n}\n\nfunction isLocalHost(hostname: string) {\n return (\n hostname === \"localhost\" ||\n hostname.endsWith(\".localhost\") ||\n hostname === \"127.0.0.1\" ||\n hostname === \"::1\"\n );\n}\n\nclass GatewayHttpError extends Error {\n constructor(\n public status: number,\n message: string,\n ) {\n super(message);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["DEFAULT_SESSION_TTL_MS","delay"],"sources":["../src/auth.ts","../src/index.ts"],"sourcesContent":["import { createHmac, randomUUID, timingSafeEqual } from \"node:crypto\";\n\nexport interface PreviewManagedAuthOptions {\n /** Secret used to sign account tokens and one-preview tunnel grants. */\n signingSecret: string;\n /** Public client id for the Farming Labs GitHub OAuth app. */\n githubClientId?: string;\n /** First-party device login. Omit to preserve GitHub account authentication. */\n deviceAuth?: {\n issuer: string;\n clientId: string;\n /** Resolve an API key or approved device credential on every new grant. */\n authorizeAccount(token: string): Promise<{\n subject: string;\n login: string;\n expiresAt?: number;\n keyId?: string;\n project?: string;\n } | null>;\n };\n /** Public console URL. Must not contain credentials or query parameters. */\n dashboardUrl?: string;\n accountTokenTtlMs?: number;\n defaultSessionTtlMs?: number;\n maxSessionTtlMs?: number;\n githubApiUrl?: string;\n fetch?: typeof fetch;\n /**\n * Shared abuse-control hook for provider-token exchange and, in device mode,\n * account credential verification on the tunnel-grant endpoint.\n * Implementations must not consume the request body. Failures reject the\n * exchange with a retryable 503 response instead of bypassing the limiter.\n */\n rateLimitExchange?: PreviewAuthExchangeRateLimiter;\n}\n\nexport interface PreviewAuthExchangeRateLimitResult {\n allowed: boolean;\n /** How long the caller should wait before retrying a rejected exchange. */\n retryAfterMs?: number;\n}\n\nexport type PreviewAuthExchangeRateLimiter = (\n request: Request,\n) => PreviewAuthExchangeRateLimitResult | Promise<PreviewAuthExchangeRateLimitResult>;\n\nexport interface PreviewAccountIdentity {\n /** Optional credential restriction; never taken from the grant request. */\n project?: string;\n /** Non-secret identifier supplied by the credential verifier, never the CLI. */\n keyId?: string;\n provider: \"github\" | \"device\";\n subject: string;\n login: string;\n name?: string;\n avatarUrl?: string;\n}\n\nexport interface PreviewAccountClaims extends PreviewAccountIdentity {\n kind: \"account\";\n issuedAt: number;\n expiresAt: number;\n}\n\nexport interface PreviewTunnelGrantClaims {\n /** Unique signed grant identity, used to revoke reconnects across transports. */\n nonce?: string;\n project?: string;\n keyId?: string;\n kind: \"tunnel\";\n /** Absent on older GitHub grants. */\n provider?: \"github\" | \"device\";\n subject: string;\n login: string;\n name: string;\n issuedAt: number;\n expiresAt: number;\n}\n\nexport interface PreviewTunnelGrant {\n token: string;\n name: string;\n expiresAt: number;\n}\n\nconst TOKEN_PREFIX = \"farm_preview_v1\";\nconst DEFAULT_ACCOUNT_TOKEN_TTL_MS = 1000 * 60 * 60 * 24 * 7;\nconst DEFAULT_SESSION_TTL_MS = 1000 * 60 * 60;\nconst DEFAULT_MAX_SESSION_TTL_MS = 1000 * 60 * 60 * 24;\nconst MIN_SESSION_TTL_MS = 1000 * 60;\nconst MAX_CLOCK_SKEW_MS = 1000 * 60 * 5;\n\nexport function getPreviewAuthPublicConfig(options: PreviewManagedAuthOptions) {\n validatePreviewAuthOptions(options);\n const durations = resolvePreviewDurations(options);\n return {\n enabled: true as const,\n controlAuth: \"bearer\" as const,\n ...(options.deviceAuth\n ? {\n provider: \"device\" as const,\n clientId: options.deviceAuth.clientId,\n issuer: options.deviceAuth.issuer,\n }\n : { provider: \"github\" as const, clientId: options.githubClientId, scope: \"read:user\" }),\n ...(options.dashboardUrl ? { dashboardUrl: options.dashboardUrl } : {}),\n defaultSessionTtlMs: durations.defaultSessionTtlMs,\n maxSessionTtlMs: durations.maxSessionTtlMs,\n };\n}\n\nexport async function exchangeGitHubPreviewToken(\n providerToken: string,\n options: PreviewManagedAuthOptions,\n): Promise<{ token: string; expiresAt: number; user: PreviewAccountIdentity }> {\n validatePreviewAuthOptions(options);\n if (options.deviceAuth)\n throw new PreviewAuthError(400, \"Use the configured device sign-in flow.\");\n if (!providerToken || providerToken.length > 4096) {\n throw new PreviewAuthError(400, \"A GitHub access token is required.\");\n }\n\n const request = options.fetch || fetch;\n const response = await request(`${options.githubApiUrl || \"https://api.github.com\"}/user`, {\n headers: {\n accept: \"application/vnd.github+json\",\n authorization: `Bearer ${providerToken}`,\n \"user-agent\": \"farm-preview-gateway\",\n \"x-github-api-version\": \"2026-03-10\",\n },\n signal: AbortSignal.timeout(10_000),\n });\n if (!response.ok) {\n await response.body?.cancel();\n throw new PreviewAuthError(401, \"GitHub could not verify this Farm Preview login.\");\n }\n\n const profile = (await response.json()) as {\n id?: number;\n login?: string;\n name?: string | null;\n avatar_url?: string;\n };\n if (!Number.isSafeInteger(profile.id) || !profile.login) {\n throw new PreviewAuthError(502, \"GitHub returned an invalid user profile.\");\n }\n\n const now = Date.now();\n const expiresAt = now + (options.accountTokenTtlMs ?? DEFAULT_ACCOUNT_TOKEN_TTL_MS);\n const user: PreviewAccountIdentity = {\n provider: \"github\",\n subject: String(profile.id),\n login: profile.login,\n ...(profile.name ? { name: profile.name } : {}),\n ...(profile.avatar_url ? { avatarUrl: profile.avatar_url } : {}),\n };\n const claims: PreviewAccountClaims = {\n kind: \"account\",\n ...user,\n issuedAt: now,\n expiresAt,\n };\n\n return {\n token: signPreviewClaims(claims, options.signingSecret),\n expiresAt,\n user,\n };\n}\n\nexport function verifyPreviewAccountToken(\n token: string,\n options: Pick<PreviewManagedAuthOptions, \"signingSecret\">,\n): PreviewAccountClaims {\n const claims = verifyPreviewClaims(token, options.signingSecret);\n if (\n claims.kind !== \"account\" ||\n claims.provider !== \"github\" ||\n typeof claims.subject !== \"string\" ||\n typeof claims.login !== \"string\"\n ) {\n throw new PreviewAuthError(401, \"The Farm Preview account token is invalid.\");\n }\n return claims as unknown as PreviewAccountClaims;\n}\n\nexport function issuePreviewTunnelGrant(\n account: PreviewAccountClaims,\n input: { name: string; project?: string; expiresInMs?: number },\n options: PreviewManagedAuthOptions,\n): PreviewTunnelGrant {\n validatePreviewAuthOptions(options);\n const durations = resolvePreviewDurations(options);\n if (\n input.project !== undefined &&\n (typeof input.project !== \"string\" || !/^[a-z0-9][a-z0-9-]{0,62}$/.test(input.project))\n )\n throw new PreviewAuthError(400, \"Preview project must be a lowercase slug of 1–63 characters.\");\n if (\n input.expiresInMs !== undefined &&\n (!Number.isSafeInteger(input.expiresInMs) || input.expiresInMs <= 0)\n ) {\n throw new PreviewAuthError(400, \"Preview expiry must be a positive integer.\");\n }\n const requestedTtlMs = input.expiresInMs ?? durations.defaultSessionTtlMs;\n if (account.project !== undefined && account.project !== (input.project ?? input.name))\n throw new PreviewAuthError(403, \"This API key is restricted to another preview project.\");\n const ttlMs = Math.max(MIN_SESSION_TTL_MS, Math.min(durations.maxSessionTtlMs, requestedTtlMs));\n const issuedAt = Date.now();\n const expiresAt = Math.min(issuedAt + ttlMs, account.expiresAt);\n if (expiresAt <= issuedAt) throw new PreviewAuthError(401, \"The account credential has expired.\");\n const claims: PreviewTunnelGrantClaims & { nonce: string } = {\n kind: \"tunnel\",\n provider: account.provider,\n subject: account.subject,\n login: account.login,\n name: input.name,\n project: input.project ?? input.name,\n ...(account.keyId ? { keyId: account.keyId } : {}),\n issuedAt,\n expiresAt,\n nonce: randomUUID(),\n };\n return {\n token: signPreviewClaims(claims, options.signingSecret),\n name: input.name,\n expiresAt,\n };\n}\n\nexport function verifyPreviewTunnelGrant(\n token: string,\n options: Pick<PreviewManagedAuthOptions, \"signingSecret\"> & { name?: string },\n): PreviewTunnelGrantClaims {\n const claims = verifyPreviewClaims(token, options.signingSecret);\n if (\n claims.kind !== \"tunnel\" ||\n typeof claims.subject !== \"string\" ||\n typeof claims.login !== \"string\" ||\n typeof claims.name !== \"string\" ||\n (claims.nonce !== undefined &&\n (typeof claims.nonce !== \"string\" || !/^[\\w-]{1,128}$/.test(claims.nonce))) ||\n (claims.project !== undefined &&\n (typeof claims.project !== \"string\" || !/^[a-z0-9][a-z0-9-]{0,62}$/.test(claims.project))) ||\n (claims.keyId !== undefined &&\n (typeof claims.keyId !== \"string\" || !/^[\\w-]{1,128}$/.test(claims.keyId))) ||\n (options.name !== undefined && claims.name !== options.name)\n ) {\n throw new PreviewAuthError(401, \"The Farm Preview tunnel grant is invalid.\");\n }\n return claims as unknown as PreviewTunnelGrantClaims;\n}\n\nexport function readPreviewBearerToken(request: Request): string | undefined {\n const authorization = request.headers.get(\"authorization\");\n if (!authorization || !/^Bearer /i.test(authorization)) return undefined;\n const token = authorization.slice(7).trim();\n return token || undefined;\n}\n\nexport class PreviewAuthError extends Error {\n constructor(\n public status: number,\n message: string,\n ) {\n super(message);\n }\n}\n\nfunction resolvePreviewDurations(options: PreviewManagedAuthOptions) {\n const maxSessionTtlMs = Math.max(\n MIN_SESSION_TTL_MS,\n options.maxSessionTtlMs ?? DEFAULT_MAX_SESSION_TTL_MS,\n );\n return {\n maxSessionTtlMs,\n defaultSessionTtlMs: Math.min(\n maxSessionTtlMs,\n Math.max(MIN_SESSION_TTL_MS, options.defaultSessionTtlMs ?? DEFAULT_SESSION_TTL_MS),\n ),\n };\n}\n\nfunction validatePreviewAuthOptions(options: PreviewManagedAuthOptions) {\n if (Buffer.byteLength(options.signingSecret) < 32) {\n throw new Error(\"Farm Preview auth signingSecret must contain at least 32 bytes.\");\n }\n if (!options.githubClientId && !options.deviceAuth) {\n throw new Error(\"Farm Preview auth requires a GitHub OAuth client id or deviceAuth.\");\n }\n if (options.deviceAuth) {\n validatePublicAuthUrl(options.deviceAuth.issuer);\n if (!options.deviceAuth.clientId)\n throw new Error(\"Farm Preview deviceAuth requires a clientId.\");\n }\n if (options.dashboardUrl) validatePublicAuthUrl(options.dashboardUrl);\n for (const [name, value] of [\n [\"accountTokenTtlMs\", options.accountTokenTtlMs],\n [\"defaultSessionTtlMs\", options.defaultSessionTtlMs],\n [\"maxSessionTtlMs\", options.maxSessionTtlMs],\n ] as const) {\n if (value !== undefined && (!Number.isSafeInteger(value) || value <= 0)) {\n throw new Error(`Farm Preview auth ${name} must be a positive integer.`);\n }\n }\n}\n\nfunction validatePublicAuthUrl(value: string) {\n const url = new URL(value);\n const local = [\"localhost\", \"127.0.0.1\", \"[::1]\"].includes(url.hostname);\n if (\n (url.protocol !== \"https:\" && !(local && url.protocol === \"http:\")) ||\n url.username ||\n url.password ||\n url.search ||\n url.hash\n ) {\n throw new Error(\n \"Farm Preview auth URLs must use HTTPS (HTTP only for loopback development), without credentials, queries or fragments.\",\n );\n }\n}\n\nexport async function authorizePreviewAccount(\n token: string,\n options: PreviewManagedAuthOptions,\n): Promise<PreviewAccountClaims> {\n if (!options.deviceAuth) return verifyPreviewAccountToken(token, options);\n if (!token || token.length > 4096) throw new PreviewAuthError(401, \"Invalid preview credential.\");\n let account;\n try {\n account = await options.deviceAuth.authorizeAccount(token);\n } catch {\n throw new PreviewAuthError(503, \"Farm Preview authentication is temporarily unavailable.\");\n }\n const now = Date.now();\n if (\n !account ||\n typeof account.subject !== \"string\" ||\n !account.subject ||\n typeof account.login !== \"string\" ||\n !account.login ||\n (account.project !== undefined &&\n (typeof account.project !== \"string\" ||\n !/^[a-z0-9][a-z0-9-]{0,62}$/.test(account.project))) ||\n (account.keyId !== undefined &&\n (typeof account.keyId !== \"string\" || !/^[\\w-]{1,128}$/.test(account.keyId))) ||\n (account.expiresAt !== undefined &&\n (!Number.isSafeInteger(account.expiresAt) || account.expiresAt <= now))\n ) {\n throw new PreviewAuthError(401, \"The Farm.js Infrastructure credential is invalid or expired.\");\n }\n return {\n kind: \"account\",\n provider: \"device\",\n subject: account.subject,\n login: account.login,\n ...(account.keyId ? { keyId: account.keyId } : {}),\n ...(account.project ? { project: account.project } : {}),\n issuedAt: now,\n expiresAt: account.expiresAt ?? now + DEFAULT_ACCOUNT_TOKEN_TTL_MS,\n };\n}\n\nfunction signPreviewClaims(claims: object, secret: string) {\n const payload = Buffer.from(JSON.stringify(claims)).toString(\"base64url\");\n const signature = createHmac(\"sha256\", secret)\n .update(`${TOKEN_PREFIX}.${payload}`)\n .digest(\"base64url\");\n return `${TOKEN_PREFIX}.${payload}.${signature}`;\n}\n\nfunction verifyPreviewClaims(token: string, secret: string): Record<string, unknown> {\n if (!token || token.length > 16_384) {\n throw new PreviewAuthError(401, \"The Farm Preview credential is invalid.\");\n }\n const [prefix, payload, presentedSignature, ...rest] = token.split(\".\");\n if (prefix !== TOKEN_PREFIX || !payload || !presentedSignature || rest.length) {\n throw new PreviewAuthError(401, \"The Farm Preview credential is invalid.\");\n }\n\n const expectedSignature = createHmac(\"sha256\", secret)\n .update(`${TOKEN_PREFIX}.${payload}`)\n .digest();\n let presented: Buffer;\n try {\n presented = Buffer.from(presentedSignature, \"base64url\");\n } catch {\n throw new PreviewAuthError(401, \"The Farm Preview credential is invalid.\");\n }\n if (\n presented.length !== expectedSignature.length ||\n !timingSafeEqual(presented, expectedSignature)\n ) {\n throw new PreviewAuthError(401, \"The Farm Preview credential is invalid.\");\n }\n\n let claims: unknown;\n try {\n claims = JSON.parse(Buffer.from(payload, \"base64url\").toString(\"utf8\"));\n } catch {\n throw new PreviewAuthError(401, \"The Farm Preview credential is invalid.\");\n }\n if (\n typeof claims !== \"object\" ||\n claims === null ||\n Array.isArray(claims) ||\n !(\"issuedAt\" in claims) ||\n !(\"expiresAt\" in claims) ||\n !Number.isSafeInteger(claims.issuedAt) ||\n !Number.isSafeInteger(claims.expiresAt) ||\n Number(claims.issuedAt) > Date.now() + MAX_CLOCK_SKEW_MS ||\n Number(claims.expiresAt) <= Date.now()\n ) {\n throw new PreviewAuthError(401, \"The Farm Preview credential has expired or is invalid.\");\n }\n return claims as Record<string, unknown>;\n}\n","import type { IncomingHttpHeaders, IncomingMessage, ServerResponse } from \"node:http\";\nimport { setTimeout as delay } from \"node:timers/promises\";\nimport {\n authorizePreviewAccount,\n exchangeGitHubPreviewToken,\n getPreviewAuthPublicConfig,\n issuePreviewTunnelGrant,\n PreviewAuthError,\n readPreviewBearerToken,\n verifyPreviewAccountToken,\n verifyPreviewTunnelGrant,\n type PreviewAuthExchangeRateLimitResult,\n type PreviewManagedAuthOptions,\n} from \"./auth.js\";\n\nexport {\n exchangeGitHubPreviewToken,\n getPreviewAuthPublicConfig,\n issuePreviewTunnelGrant,\n PreviewAuthError,\n readPreviewBearerToken,\n verifyPreviewAccountToken,\n verifyPreviewTunnelGrant,\n type PreviewAccountClaims,\n type PreviewAuthExchangeRateLimiter,\n type PreviewAuthExchangeRateLimitResult,\n type PreviewAccountIdentity,\n type PreviewManagedAuthOptions,\n type PreviewTunnelGrant,\n type PreviewTunnelGrantClaims,\n} from \"./auth.js\";\n\nexport interface PreviewSessionAccess {\n id: string;\n name: string;\n ownerId?: string;\n project?: string;\n keyId?: string;\n grantId?: string;\n expiresAt?: number;\n}\n\nexport interface PreviewGatewayOptions {\n /** Required access control when supplied. Denials and errors fail closed, unlike telemetry. */\n authorizeSession?: (session: PreviewSessionAccess) => Promise<boolean>;\n /** Optional metadata-only telemetry. Failures never interrupt a preview. */\n observer?: PreviewGatewayObserver;\n domain?: string;\n baseUrl?: string;\n store?: PreviewGatewayStore;\n sessionTtlMs?: number;\n clientHeartbeatTimeoutMs?: number;\n requestTimeoutMs?: number;\n pollTimeoutMs?: number;\n pollIntervalMs?: number;\n maxBodyBytes?: number;\n maxResponseBodyBytes?: number;\n /** Managed account login and one-preview grant configuration. */\n auth?: PreviewManagedAuthOptions;\n}\n\nexport interface PreviewGatewaySession {\n grantId?: string;\n project?: string;\n keyId?: string;\n ownerId?: string;\n id: string;\n name: string;\n hostname: string;\n publicUrl: string;\n token: string;\n localUrl?: string;\n createdAt: number;\n expiresAt: number;\n /** Internal idle expiry marker. Missing means expiresAt is an absolute cap. */\n slidingExpiration?: true;\n lastHeartbeatAt?: number;\n}\n\nexport interface PreviewGatewayRequest {\n id: string;\n method: string;\n path: string;\n headers: Record<string, string>;\n body?: string | null;\n encoding?: \"base64\";\n createdAt: number;\n cancelled?: true;\n}\n\nexport interface PreviewGatewayResponse {\n status: number;\n // A header may carry multiple values (notably Set-Cookie: a login response\n // commonly sets a session cookie and a CSRF cookie). Those must survive as\n // an array through the store and back onto the public response.\n headers?: Record<string, string | string[]>;\n body?: string | null;\n encoding?: \"base64\";\n}\n\nexport interface PreviewGatewayStore {\n /**\n * Stores a session and claims its name atomically. The claim succeeds when\n * the name is free, already belongs to this session, or still belongs to\n * `options.replaceSessionId` (a stale session the gateway decided to take\n * over). Return `false` when another session holds the name; nothing may\n * be stored in that case. Stores that resolve `undefined` are treated as\n * having claimed the name.\n */\n createSession(\n session: PreviewGatewaySession,\n ttlMs: number,\n options?: PreviewGatewayCreateSessionOptions,\n ): Promise<boolean | void>;\n getSessionByName(name: string): Promise<PreviewGatewaySession | undefined>;\n getSessionById(id: string): Promise<PreviewGatewaySession | undefined>;\n touchSession(session: PreviewGatewaySession, ttlMs: number): Promise<void>;\n enqueueRequest(sessionId: string, request: PreviewGatewayRequest, ttlMs: number): Promise<void>;\n takeRequests(sessionId: string, limit: number): Promise<PreviewGatewayRequest[]>;\n /**\n * Puts requests taken by a poll that could not deliver them back at the\n * head of the queue, in their original order. Stores without it fall back\n * to `enqueueRequest`, which keeps the requests but not their position.\n */\n requeueRequests?(\n sessionId: string,\n requests: PreviewGatewayRequest[],\n ttlMs: number,\n ): Promise<void>;\n saveResponse(\n sessionId: string,\n requestId: string,\n response: PreviewGatewayResponse,\n ttlMs: number,\n ): Promise<void>;\n getResponse(sessionId: string, requestId: string): Promise<PreviewGatewayResponse | undefined>;\n deleteResponse(sessionId: string, requestId: string): Promise<void>;\n deleteSession(session: PreviewGatewaySession): Promise<void>;\n}\n\nexport interface PreviewGatewayCreateSessionOptions {\n /** A stale session whose name the new session may take over. */\n replaceSessionId?: string;\n}\n\ninterface PreviewGatewayRuntimeConfig {\n authorizeSession?: PreviewGatewayOptions[\"authorizeSession\"];\n observer?: PreviewGatewayObserver;\n domain: string;\n baseUrl?: string;\n sessionTtlMs: number;\n clientHeartbeatTimeoutMs: number;\n requestTimeoutMs: number;\n pollTimeoutMs: number;\n pollIntervalMs: number;\n maxBodyBytes: number;\n maxResponseBodyBytes: number;\n auth?: PreviewManagedAuthOptions;\n}\n\nexport interface PreviewGatewayObserver {\n session?(event: {\n grantId?: string;\n project?: string;\n keyId?: string;\n id: string;\n name: string;\n ownerId?: string;\n publicUrl: string;\n expiresAt?: number;\n state: \"connected\" | \"disconnected\";\n at: number;\n }): void | Promise<void>;\n request?(event: {\n sessionId: string;\n method: string;\n path: string;\n status: number;\n durationMs: number;\n at: number;\n }): void | Promise<void>;\n}\n\nfunction observe(callback: (() => void | Promise<void>) | undefined) {\n if (!callback) return;\n try {\n void Promise.resolve(callback()).catch(() => undefined);\n } catch {\n /* Optional telemetry. */\n }\n}\n\nfunction reportSession(\n config: PreviewGatewayRuntimeConfig,\n session: PreviewGatewaySession,\n state: \"connected\" | \"disconnected\",\n) {\n if (!config.observer?.session) return;\n observe(() =>\n config.observer!.session!({\n id: session.id,\n grantId: session.grantId,\n name: session.name,\n project: session.project,\n keyId: session.keyId,\n ownerId: session.ownerId,\n publicUrl: session.publicUrl,\n expiresAt: session.expiresAt,\n state,\n at: Date.now(),\n }),\n );\n}\n\nconst DEFAULT_DOMAIN = \"preview.farmjs.dev\";\nconst DEFAULT_SESSION_TTL_MS = 1000 * 60 * 30;\nconst DEFAULT_CLIENT_HEARTBEAT_TIMEOUT_MS = 1000 * 20;\nconst DEFAULT_REQUEST_TIMEOUT_MS = 25000;\nconst DEFAULT_POLL_TIMEOUT_MS = 15000;\nconst DEFAULT_POLL_INTERVAL_MS = 100;\nconst DEFAULT_MAX_BODY_BYTES = 1024 * 1024 * 5;\nconst DEFAULT_MAX_RESPONSE_BODY_BYTES = 1024 * 1024 * 5;\nconst DEFAULT_POLL_REQUEST_LIMIT = 50;\nconst HOP_BY_HOP_HEADERS = new Set([\n \"connection\",\n \"content-length\",\n \"keep-alive\",\n \"proxy-authenticate\",\n \"proxy-authorization\",\n \"te\",\n \"trailer\",\n \"transfer-encoding\",\n \"upgrade\",\n]);\n\nfunction getHopByHopHeaderNames(headers: Headers | Record<string, string | string[]>): Set<string> {\n const names = new Set(HOP_BY_HOP_HEADERS);\n const connection =\n headers instanceof Headers\n ? headers.get(\"connection\")\n : Object.entries(headers).find(([name]) => name.toLowerCase() === \"connection\")?.[1];\n const values = Array.isArray(connection) ? connection : connection ? [connection] : [];\n for (const value of values) {\n for (const name of value.split(\",\")) {\n const normalized = name.trim().toLowerCase();\n if (normalized) names.add(normalized);\n }\n }\n return names;\n}\n\n/**\n * A gateway request handler, plus the name-ownership probe another preview\n * transport needs before it claims the same public hostname.\n */\nexport type PreviewGatewayHandler = ((request: Request) => Promise<Response>) & {\n isPreviewNameClaimed(name: string): Promise<boolean>;\n};\n\nexport type NodePreviewGatewayHandler = ((\n req: IncomingMessage,\n res: ServerResponse,\n) => Promise<void>) & {\n isPreviewNameClaimed(name: string): Promise<boolean>;\n};\n\nexport function createPreviewGatewayHandler(\n options: PreviewGatewayOptions = {},\n): PreviewGatewayHandler {\n if (options.auth) getPreviewAuthPublicConfig(options.auth);\n const store = options.store || createPreviewGatewayStoreFromEnv();\n const config = {\n authorizeSession: options.authorizeSession,\n observer: options.observer,\n domain: normalizeDomain(options.domain || process.env.FARM_PREVIEW_DOMAIN || DEFAULT_DOMAIN),\n baseUrl: options.baseUrl || process.env.FARM_PREVIEW_GATEWAY_URL,\n sessionTtlMs: options.sessionTtlMs ?? DEFAULT_SESSION_TTL_MS,\n clientHeartbeatTimeoutMs:\n options.clientHeartbeatTimeoutMs ?? DEFAULT_CLIENT_HEARTBEAT_TIMEOUT_MS,\n requestTimeoutMs: options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS,\n pollTimeoutMs: options.pollTimeoutMs ?? DEFAULT_POLL_TIMEOUT_MS,\n pollIntervalMs: options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS,\n maxBodyBytes: options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES,\n maxResponseBodyBytes: options.maxResponseBodyBytes ?? DEFAULT_MAX_RESPONSE_BODY_BYTES,\n auth: options.auth,\n };\n\n const handlePreviewGatewayRequest = async function handlePreviewGatewayRequest(\n request: Request,\n ): Promise<Response> {\n try {\n const url = new URL(request.url);\n\n if (request.method === \"GET\" && url.pathname === \"/api/health\") {\n return json({\n ok: true,\n domain: config.domain,\n store: store.constructor.name || \"PreviewGatewayStore\",\n });\n }\n\n if (request.method === \"GET\" && url.pathname === \"/api/auth/config\") {\n return json(\n config.auth\n ? getPreviewAuthPublicConfig(config.auth)\n : {\n enabled: false,\n controlAuth: \"bearer\",\n defaultSessionTtlMs: config.sessionTtlMs,\n maxSessionTtlMs: config.sessionTtlMs,\n },\n );\n }\n\n if (request.method === \"POST\" && url.pathname === \"/api/auth/exchange\") {\n if (!config.auth) return text(\"Managed preview authentication is not configured.\", 404);\n const rateLimitResponse = await limitPreviewAccountExchange(request, config.auth);\n if (rateLimitResponse) return rateLimitResponse;\n return await exchangePreviewAccount(request, config.auth);\n }\n\n if (request.method === \"POST\" && url.pathname === \"/api/tunnel/grants\") {\n if (!config.auth) return text(\"Managed preview authentication is not configured.\", 404);\n if (config.auth.deviceAuth) {\n const limited = await limitPreviewAccountExchange(request, config.auth);\n if (limited) return limited;\n }\n return await createTunnelGrant(request, config.auth);\n }\n\n if (request.method === \"POST\" && url.pathname === \"/api/sessions\") {\n return await createSession(request, store, config);\n }\n\n const sessionRoute = matchSessionRoute(url.pathname);\n if (sessionRoute) {\n return await handleSessionRoute(request, url, store, config, sessionRoute);\n }\n\n const publicRoute = resolvePublicPreviewRoute(request, url, config.domain);\n if (publicRoute) {\n return await proxyPublicRequest(request, url, store, config, publicRoute);\n }\n\n return json(\n {\n ok: true,\n message: \"Farm Preview Gateway\",\n createSession: \"/api/sessions\",\n health: \"/api/health\",\n },\n 200,\n );\n } catch (error) {\n if (error instanceof GatewayHttpError || error instanceof PreviewAuthError) {\n return text(error.message, error.status);\n }\n // Unexpected errors can carry store or provider details (for example a\n // Redis REST error body), so visitors only see a fixed message.\n console.error(\"[farm-preview-gateway] Unexpected error:\", error);\n return text(\"Unexpected preview gateway error.\", 500);\n }\n };\n\n // The persistent relay serves the same public hostnames from a separate name\n // namespace, and its route wins over this handler, so it has to be able to\n // ask whether a live polling session already owns a name before accepting a\n // claim for it.\n return Object.assign(handlePreviewGatewayRequest, {\n async isPreviewNameClaimed(name: string) {\n const claimed = sanitizePreviewName(name);\n if (!claimed) return false;\n const session = await store.getSessionByName(claimed);\n return Boolean(session && isPreviewClientOnline(session, config));\n },\n });\n}\n\nasync function limitPreviewAccountExchange(request: Request, auth: PreviewManagedAuthOptions) {\n if (!auth.rateLimitExchange) return undefined;\n let result: PreviewAuthExchangeRateLimitResult;\n try {\n result = await auth.rateLimitExchange(request);\n } catch {\n return new Response(\"Farm Preview login is temporarily unavailable. Try again shortly.\", {\n status: 503,\n headers: {\n \"cache-control\": \"no-store\",\n \"content-type\": \"text/plain; charset=utf-8\",\n \"retry-after\": \"1\",\n },\n });\n }\n if (result.allowed) return undefined;\n\n const retryAfterMs =\n Number.isFinite(result.retryAfterMs) && (result.retryAfterMs ?? 0) > 0\n ? result.retryAfterMs!\n : 1000;\n return new Response(\"Too many Farm Preview login attempts. Try again shortly.\", {\n status: 429,\n headers: {\n \"cache-control\": \"no-store\",\n \"content-type\": \"text/plain; charset=utf-8\",\n \"retry-after\": String(Math.max(1, Math.ceil(retryAfterMs / 1000))),\n },\n });\n}\n\nasync function exchangePreviewAccount(request: Request, auth: PreviewManagedAuthOptions) {\n const input = (await request.json().catch(() => ({}))) as {\n provider?: string;\n accessToken?: string;\n };\n if (input.provider !== \"github\") {\n throw new PreviewAuthError(400, \"Farm Preview currently supports GitHub sign-in.\");\n }\n return json(await exchangeGitHubPreviewToken(input.accessToken || \"\", auth));\n}\n\nasync function createTunnelGrant(request: Request, auth: PreviewManagedAuthOptions) {\n const accountToken = readPreviewBearerToken(request);\n if (!accountToken) {\n throw new PreviewAuthError(401, \"Sign in with Farm Preview before creating a tunnel grant.\");\n }\n const account = await authorizePreviewAccount(accountToken, auth);\n const input = (await request.json().catch(() => ({}))) as {\n name?: string;\n project?: string;\n expiresInMs?: number;\n };\n const name = sanitizePreviewName(input.name);\n if (!name) throw new PreviewAuthError(400, \"A valid preview name is required.\");\n\n return json(\n issuePreviewTunnelGrant(\n account,\n {\n name,\n project: input.project,\n ...(input.expiresInMs !== undefined ? { expiresInMs: input.expiresInMs } : {}),\n },\n auth,\n ),\n );\n}\n\nexport function createNodePreviewGatewayHandler(\n options: PreviewGatewayOptions = {},\n): NodePreviewGatewayHandler {\n const handler = createPreviewGatewayHandler(options);\n\n const nodePreviewGatewayHandler = async function nodePreviewGatewayHandler(\n req: IncomingMessage,\n res: ServerResponse,\n ) {\n const controller = new AbortController();\n const abort = () => controller.abort();\n const abortOnClose = () => {\n if (!res.writableEnded) abort();\n };\n req.once(\"aborted\", abort);\n res.once(\"close\", abortOnClose);\n try {\n const response = await handler(nodeRequestToWebRequest(req, controller.signal));\n if (controller.signal.aborted || res.destroyed) {\n await response.body?.cancel();\n return;\n }\n res.statusCode = response.status;\n // Headers.forEach folds repeated headers into one comma-joined value, which\n // corrupts multiple Set-Cookie. Emit those separately as an array so each\n // cookie becomes its own header line.\n const setCookies = response.headers.getSetCookie?.() ?? [];\n response.headers.forEach((value, key) => {\n if (key.toLowerCase() === \"set-cookie\") return;\n res.setHeader(key, value);\n });\n if (setCookies.length > 0) {\n res.setHeader(\"set-cookie\", setCookies);\n }\n res.end(Buffer.from(await response.arrayBuffer()));\n } finally {\n req.off(\"aborted\", abort);\n res.off(\"close\", abortOnClose);\n }\n };\n\n return Object.assign(nodePreviewGatewayHandler, {\n isPreviewNameClaimed: handler.isPreviewNameClaimed,\n });\n}\n\nexport function createPreviewGatewayStoreFromEnv(): PreviewGatewayStore {\n const redisUrl = process.env.UPSTASH_REDIS_REST_URL || process.env.KV_REST_API_URL;\n const redisToken = process.env.UPSTASH_REDIS_REST_TOKEN || process.env.KV_REST_API_TOKEN;\n\n if (redisUrl && redisToken) {\n return new RedisRestPreviewGatewayStore({\n url: redisUrl,\n token: redisToken,\n });\n }\n\n return new MemoryPreviewGatewayStore();\n}\n\nexport class MemoryPreviewGatewayStore implements PreviewGatewayStore {\n private sessions = new Map<string, PreviewGatewaySession>();\n private names = new Map<string, string>();\n private queues = new Map<string, PreviewGatewayRequest[]>();\n private responses = new Map<string, PreviewGatewayResponse>();\n\n async createSession(\n session: PreviewGatewaySession,\n _ttlMs: number,\n options: PreviewGatewayCreateSessionOptions = {},\n ): Promise<boolean> {\n // No await between the ownership check and the write, so concurrent\n // claims for one name cannot both succeed.\n const owner = this.names.get(session.name);\n if (\n owner &&\n owner !== session.id &&\n owner !== options.replaceSessionId &&\n this.sessions.has(owner)\n ) {\n return false;\n }\n this.sessions.set(session.id, session);\n this.names.set(session.name, session.id);\n this.queues.set(session.id, []);\n return true;\n }\n\n async getSessionByName(name: string): Promise<PreviewGatewaySession | undefined> {\n const id = this.names.get(name);\n return id ? this.getSessionById(id) : undefined;\n }\n\n async getSessionById(id: string): Promise<PreviewGatewaySession | undefined> {\n const session = this.sessions.get(id);\n if (!session) return undefined;\n if (session.expiresAt <= Date.now()) {\n await this.deleteSession(session);\n return undefined;\n }\n return session;\n }\n\n async touchSession(session: PreviewGatewaySession, _ttlMs: number): Promise<void> {\n // A concurrent delete (the preview was stopped) must win: a poll handler\n // that outlives the DELETE must never resurrect a session that is no\n // longer stored, or its name would look \"online\" and block a restart.\n if (!this.sessions.has(session.id)) return;\n this.sessions.set(session.id, session);\n const nameOwner = this.names.get(session.name);\n if (!nameOwner || nameOwner === session.id) {\n this.names.set(session.name, session.id);\n }\n }\n\n async enqueueRequest(sessionId: string, request: PreviewGatewayRequest): Promise<void> {\n const queue = this.queues.get(sessionId) || [];\n queue.push(request);\n this.queues.set(sessionId, queue);\n }\n\n async takeRequests(sessionId: string, limit: number): Promise<PreviewGatewayRequest[]> {\n const queue = this.queues.get(sessionId) || [];\n return queue.splice(0, limit);\n }\n\n async requeueRequests(sessionId: string, requests: PreviewGatewayRequest[]): Promise<void> {\n if (!this.sessions.has(sessionId)) return;\n const queue = this.queues.get(sessionId) || [];\n queue.unshift(...requests);\n this.queues.set(sessionId, queue);\n }\n\n async saveResponse(\n sessionId: string,\n requestId: string,\n response: PreviewGatewayResponse,\n ): Promise<void> {\n this.responses.set(responseKey(sessionId, requestId), response);\n }\n\n async getResponse(\n sessionId: string,\n requestId: string,\n ): Promise<PreviewGatewayResponse | undefined> {\n return this.responses.get(responseKey(sessionId, requestId));\n }\n\n async deleteResponse(sessionId: string, requestId: string): Promise<void> {\n this.responses.delete(responseKey(sessionId, requestId));\n }\n\n async deleteSession(session: PreviewGatewaySession): Promise<void> {\n this.sessions.delete(session.id);\n // The name may have been taken over by a newer session; only remove the\n // mapping if it still points at the session being deleted.\n if (this.names.get(session.name) === session.id) {\n this.names.delete(session.name);\n }\n this.queues.delete(session.id);\n }\n}\n\n// Claims KEYS[1] for ARGV[2] when the name is free, already ours, or still\n// held by the replaceable owner ARGV[1] (empty when there is none).\nconst CLAIM_NAME_SCRIPT = `\n local owner = redis.call(\"GET\", KEYS[1])\n if not owner or owner == ARGV[2] or (ARGV[1] ~= \"\" and owner == ARGV[1]) then\n redis.call(\"SET\", KEYS[1], ARGV[2], \"PX\", ARGV[3])\n return 1\n end\n return 0\n`;\n\nexport class RedisRestPreviewGatewayStore implements PreviewGatewayStore {\n constructor(private options: { url: string; token: string; prefix?: string }) {}\n\n async createSession(\n session: PreviewGatewaySession,\n ttlMs: number,\n options: PreviewGatewayCreateSessionOptions = {},\n ): Promise<boolean> {\n // The session record is written before the name is claimed, so a name\n // that points at a missing session always belongs to a dead one.\n await this.command(\"SET\", this.sessionKey(session.id), JSON.stringify(session), \"PX\", ttlMs);\n let claimed = await this.claimName(session, options.replaceSessionId, ttlMs);\n if (!claimed) {\n const owner = await this.command<string | null>(\"GET\", this.nameKey(session.name));\n if (owner && !(await this.command<number>(\"EXISTS\", this.sessionKey(owner)))) {\n claimed = await this.claimName(session, owner, ttlMs);\n }\n }\n if (!claimed) {\n await this.command(\"DEL\", this.sessionKey(session.id));\n return false;\n }\n await this.command(\"DEL\", this.queueKey(session.id));\n return true;\n }\n\n async getSessionByName(name: string): Promise<PreviewGatewaySession | undefined> {\n const id = await this.command<string | null>(\"GET\", this.nameKey(name));\n return id ? this.getSessionById(id) : undefined;\n }\n\n async getSessionById(id: string): Promise<PreviewGatewaySession | undefined> {\n const value = await this.command<string | null>(\"GET\", this.sessionKey(id));\n return value ? JSON.parse(value) : undefined;\n }\n\n async touchSession(session: PreviewGatewaySession, ttlMs: number): Promise<void> {\n const nextSession = { ...session };\n const remainingTtlMs = Math.max(1, Math.min(ttlMs, session.expiresAt - Date.now()));\n // XX only refreshes a session key that still exists. A poll handler that\n // outlives a DELETE must not recreate the session (nor its name mapping),\n // which would otherwise block reclaiming the name on restart.\n const result = await this.command<string | null>(\n \"SET\",\n this.sessionKey(session.id),\n JSON.stringify(nextSession),\n \"PX\",\n remainingTtlMs,\n \"XX\",\n );\n if (result === null) return;\n await this.claimName(session, undefined, remainingTtlMs);\n await this.command(\"EXPIRE\", this.queueKey(session.id), Math.ceil(remainingTtlMs / 1000));\n }\n\n async enqueueRequest(\n sessionId: string,\n request: PreviewGatewayRequest,\n ttlMs: number,\n ): Promise<void> {\n await this.command(\"RPUSH\", this.queueKey(sessionId), JSON.stringify(request));\n await this.command(\"EXPIRE\", this.queueKey(sessionId), Math.ceil(ttlMs / 1000));\n }\n\n async takeRequests(sessionId: string, limit: number): Promise<PreviewGatewayRequest[]> {\n const values = await this.command<string[] | string | null>(\n \"LPOP\",\n this.queueKey(sessionId),\n limit,\n );\n if (!values) return [];\n const list = Array.isArray(values) ? values : [values];\n return list.map((value) => JSON.parse(value));\n }\n\n async requeueRequests(\n sessionId: string,\n requests: PreviewGatewayRequest[],\n ttlMs: number,\n ): Promise<void> {\n if (!requests.length) return;\n // LPUSH prepends one value at a time, so push in reverse to keep order.\n const values = requests.map((request) => JSON.stringify(request)).reverse();\n await this.command(\"LPUSH\", this.queueKey(sessionId), ...values);\n await this.command(\"EXPIRE\", this.queueKey(sessionId), Math.ceil(ttlMs / 1000));\n }\n\n async saveResponse(\n sessionId: string,\n requestId: string,\n response: PreviewGatewayResponse,\n ttlMs: number,\n ): Promise<void> {\n await this.command(\n \"SET\",\n this.responseKey(sessionId, requestId),\n JSON.stringify(response),\n \"PX\",\n ttlMs,\n );\n }\n\n async getResponse(\n sessionId: string,\n requestId: string,\n ): Promise<PreviewGatewayResponse | undefined> {\n const value = await this.command<string | null>(\"GET\", this.responseKey(sessionId, requestId));\n return value ? JSON.parse(value) : undefined;\n }\n\n async deleteResponse(sessionId: string, requestId: string): Promise<void> {\n await this.command(\"DEL\", this.responseKey(sessionId, requestId));\n }\n\n async deleteSession(session: PreviewGatewaySession): Promise<void> {\n const keys = [this.sessionKey(session.id), this.queueKey(session.id)];\n // The name may have been taken over by a newer session; only remove the\n // mapping if it still points at the session being deleted.\n const nameOwner = await this.command<string | null>(\"GET\", this.nameKey(session.name));\n if (nameOwner === session.id) {\n keys.push(this.nameKey(session.name));\n }\n await this.command(\"DEL\", ...keys);\n }\n\n private async claimName(\n session: PreviewGatewaySession,\n replaceSessionId: string | undefined,\n ttlMs: number,\n ) {\n const result = await this.command<number>(\n \"EVAL\",\n CLAIM_NAME_SCRIPT,\n 1,\n this.nameKey(session.name),\n replaceSessionId || \"\",\n session.id,\n Math.max(1, Math.ceil(ttlMs)),\n );\n return Number(result) === 1;\n }\n\n private async command<T = unknown>(...args: Array<string | number>) {\n const response = await fetch(this.options.url, {\n method: \"POST\",\n headers: {\n authorization: `Bearer ${this.options.token}`,\n \"content-type\": \"application/json\",\n },\n body: JSON.stringify(args),\n });\n\n if (!response.ok) {\n throw new Error(\n `Preview gateway Redis command failed (${response.status}): ${await response.text()}`,\n );\n }\n\n const data = (await response.json()) as { result?: T; error?: string };\n if (data.error) {\n throw new Error(`Preview gateway Redis command failed: ${data.error}`);\n }\n return data.result as T;\n }\n\n private key(value: string) {\n return `${this.options.prefix || \"farm-preview\"}:${value}`;\n }\n\n private sessionKey(id: string) {\n return this.key(`session:${id}`);\n }\n\n private nameKey(name: string) {\n return this.key(`name:${name}`);\n }\n\n private queueKey(id: string) {\n return this.key(`queue:${id}`);\n }\n\n private responseKey(sessionId: string, requestId: string) {\n return this.key(`response:${sessionId}:${requestId}`);\n }\n}\n\nasync function createSession(\n request: Request,\n store: PreviewGatewayStore,\n config: PreviewGatewayRuntimeConfig,\n) {\n const input = (await request.json().catch(() => ({}))) as {\n name?: string;\n localUrl?: string;\n expiresInMs?: number;\n };\n const requestedName = sanitizePreviewName(input.name);\n let name = requestedName || randomPreviewName();\n let expiresAt = Date.now() + config.sessionTtlMs;\n let slidingExpiration = true;\n let ownerId: string | undefined;\n let project: string | undefined;\n let keyId: string | undefined;\n let grantId: string | undefined;\n\n if (config.auth) {\n const grantToken = readPreviewBearerToken(request);\n if (!grantToken) {\n throw new GatewayHttpError(401, \"Sign in with Farm Preview before creating a session.\");\n }\n try {\n const grant = verifyPreviewTunnelGrant(grantToken, {\n signingSecret: config.auth.signingSecret,\n ...(requestedName ? { name: requestedName } : {}),\n });\n name = grant.name;\n ownerId = `${grant.provider || \"github\"}:${grant.subject}`;\n project = grant.project ?? grant.name;\n keyId = grant.keyId;\n grantId = grant.nonce;\n expiresAt = grant.expiresAt;\n slidingExpiration = false;\n } catch (error) {\n if (error instanceof PreviewAuthError)\n throw new GatewayHttpError(error.status, error.message);\n throw error;\n }\n } else if (Number.isFinite(input.expiresInMs)) {\n expiresAt = Date.now() + clamp(Number(input.expiresInMs), 60_000, config.sessionTtlMs);\n slidingExpiration = false;\n }\n\n // A name that is still owned by a live session cannot be claimed by a new\n // one, otherwise any caller could repoint an active preview's public URL\n // to their own session. Stale sessions (missed heartbeats) may be taken\n // over, matching how public routing treats them. The store claims the name\n // atomically, so two concurrent creates cannot both win the same name.\n let session: PreviewGatewaySession;\n for (let attempt = 0; ; attempt++) {\n const existing = await store.getSessionByName(name);\n if (!existing || !isPreviewClientOnline(existing, config)) {\n session = {\n ...(grantId ? { grantId } : {}),\n ...(project ? { project } : {}),\n ...(keyId ? { keyId } : {}),\n ...(ownerId ? { ownerId } : {}),\n id: randomId(\"sess\"),\n name,\n hostname: `${name}.${config.domain}`,\n publicUrl: createPublicUrl(request, config, name),\n token: randomId(\"token\"),\n localUrl: input.localUrl,\n createdAt: Date.now(),\n expiresAt,\n ...(slidingExpiration ? { slidingExpiration: true as const } : {}),\n lastHeartbeatAt: Date.now(),\n };\n await requireSessionAccess(config, session);\n const claimed = await store.createSession(\n session,\n Math.max(1, expiresAt - Date.now()),\n existing ? { replaceSessionId: existing.id } : undefined,\n );\n if (claimed !== false) break;\n }\n if (requestedName || config.auth) {\n return text(\n `A Farm preview named \"${name}\" is already active. Choose another name or stop the running preview.`,\n 409,\n );\n }\n if (attempt >= 4) {\n return text(\"Could not allocate a unique preview name. Try again.\", 503);\n }\n name = randomPreviewName();\n }\n\n reportSession(config, session, \"connected\");\n\n return json({\n id: session.id,\n name: session.name,\n token: session.token,\n publicUrl: session.publicUrl,\n ...(session.slidingExpiration ? {} : { expiresAt: session.expiresAt }),\n });\n}\n\nasync function handleSessionRoute(\n request: Request,\n url: URL,\n store: PreviewGatewayStore,\n config: PreviewGatewayRuntimeConfig,\n route: { sessionId: string; action?: string; requestId?: string },\n) {\n const session = await requireSession(\n store,\n route.sessionId,\n readPreviewBearerToken(request) || url.searchParams.get(\"token\"),\n );\n\n // Cleanup is still possible when the authority is unavailable or the grant is revoked.\n if (request.method !== \"DELETE\" || route.action) await requireSessionAccess(config, session);\n\n if (request.method === \"GET\" && route.action === \"requests\") {\n return await pollSessionRequests(url, store, config, session, request.signal);\n }\n\n if (request.method === \"POST\" && route.action === \"responses\" && route.requestId) {\n let response: PreviewGatewayResponse;\n try {\n response = await parsePreviewResponse(request, config.maxResponseBodyBytes);\n } catch (error) {\n if (error instanceof GatewayHttpError && error.status === 413) {\n await store.saveResponse(\n session.id,\n route.requestId,\n createOversizedPreviewResponse(config.maxResponseBodyBytes),\n remainingSessionTtl(session, config),\n );\n }\n throw error;\n }\n await store.saveResponse(\n session.id,\n route.requestId,\n response,\n remainingSessionTtl(session, config),\n );\n await markSessionOnline(store, config, session);\n return json({ ok: true });\n }\n\n if (request.method === \"POST\" && route.action === \"heartbeat\") {\n await markSessionOnline(store, config, session);\n return json({\n ok: true,\n ...(session.slidingExpiration ? {} : { expiresAt: session.expiresAt }),\n });\n }\n\n if (request.method === \"DELETE\" && !route.action) {\n await store.deleteSession(session);\n reportSession(config, session, \"disconnected\");\n return json({ ok: true });\n }\n\n return text(\"Preview gateway route not found.\", 404);\n}\n\nasync function parsePreviewResponse(request: Request, maxBodyBytes: number) {\n const maxEncodedBytes = Math.ceil(maxBodyBytes / 3) * 4 + 64 * 1024;\n const contentLength = Number(request.headers.get(\"content-length\") || 0);\n if (Number.isFinite(contentLength) && contentLength > maxEncodedBytes) {\n await request.body?.cancel();\n throw new GatewayHttpError(413, \"Preview response body is too large.\");\n }\n\n const bytes = await readBodyWithLimit(\n request,\n maxEncodedBytes,\n \"Preview response body is too large.\",\n );\n let response: PreviewGatewayResponse;\n try {\n response = JSON.parse(bytes.toString(\"utf8\")) as PreviewGatewayResponse;\n } catch {\n throw new GatewayHttpError(400, \"Preview response body must be valid JSON.\");\n }\n\n if (getPreviewResponseBodySize(response) > maxBodyBytes) {\n throw new GatewayHttpError(413, \"Preview response body is too large.\");\n }\n return response;\n}\n\nfunction getPreviewResponseBodySize(response: PreviewGatewayResponse) {\n if (!response.body) return 0;\n return Buffer.byteLength(response.body, response.encoding === \"base64\" ? \"base64\" : \"utf8\");\n}\n\nfunction createOversizedPreviewResponse(maxBodyBytes: number): PreviewGatewayResponse {\n return {\n status: 502,\n headers: { \"content-type\": \"text/plain; charset=utf-8\" },\n body: Buffer.from(\n `The local preview response exceeded the ${maxBodyBytes} byte limit.`,\n ).toString(\"base64\"),\n encoding: \"base64\",\n };\n}\n\nasync function pollSessionRequests(\n url: URL,\n store: PreviewGatewayStore,\n config: PreviewGatewayRuntimeConfig,\n session: PreviewGatewaySession,\n signal: AbortSignal,\n) {\n const waitMs = clamp(Number(url.searchParams.get(\"wait\") || config.pollTimeoutMs), 1000, 25000);\n const deadline = Date.now() + waitMs;\n let nextAccessCheckAt = Date.now() + 1000;\n // A poll whose client went away must neither take requests nor keep the\n // session looking online: nobody receives its response.\n const abandoned = () => new Response(null, { status: 499 });\n\n if (signal.aborted) return abandoned();\n await markSessionOnline(store, config, session);\n\n while (Date.now() < deadline) {\n if (signal.aborted) return abandoned();\n if (Date.now() >= nextAccessCheckAt) {\n await requireSessionAccess(config, session);\n nextAccessCheckAt = Date.now() + 1000;\n }\n const requests = await store.takeRequests(session.id, DEFAULT_POLL_REQUEST_LIMIT);\n if (requests.length) {\n try {\n await requireSessionAccess(config, session);\n if (!signal.aborted) await markSessionOnline(store, config, session);\n } catch (error) {\n await requeuePreviewRequests(store, config, session, requests);\n throw error;\n }\n if (signal.aborted) {\n await requeuePreviewRequests(store, config, session, requests);\n return abandoned();\n }\n return json({ requests });\n }\n try {\n await delay(config.pollIntervalMs, undefined, { signal });\n } catch {\n if (signal.aborted) return abandoned();\n throw new Error(\"Preview request polling was interrupted.\");\n }\n }\n\n if (signal.aborted) return abandoned();\n await markSessionOnline(store, config, session);\n return new Response(null, { status: 204 });\n}\n\nasync function requeuePreviewRequests(\n store: PreviewGatewayStore,\n config: PreviewGatewayRuntimeConfig,\n session: PreviewGatewaySession,\n requests: PreviewGatewayRequest[],\n) {\n const ttlMs = remainingSessionTtl(session, config);\n if (store.requeueRequests) {\n await store.requeueRequests(session.id, requests, ttlMs);\n return;\n }\n for (const request of requests) {\n await store.enqueueRequest(session.id, request, ttlMs);\n }\n}\n\nasync function proxyPublicRequest(\n request: Request,\n url: URL,\n store: PreviewGatewayStore,\n config: PreviewGatewayRuntimeConfig,\n route: { name: string; path: string },\n) {\n const session = await store.getSessionByName(route.name);\n if (!session) {\n return inactivePreviewResponse(request, route.name);\n }\n if (!isPreviewClientOnline(session, config)) {\n await store.deleteSession(session);\n reportSession(config, session, \"disconnected\");\n return inactivePreviewResponse(request, route.name);\n }\n\n await requireSessionAccess(config, session);\n\n const startedAt = Date.now();\n let status = 500;\n try {\n const previewRequest = await serializePreviewRequest(\n request,\n url,\n route.path,\n config.maxBodyBytes,\n );\n await store.enqueueRequest(session.id, previewRequest, remainingSessionTtl(session, config));\n await store.touchSession(session, remainingSessionTtl(session, config));\n\n const response = await waitForPreviewResponse(\n store,\n config,\n session,\n previewRequest.id,\n request.signal,\n );\n if (response === \"cancelled\") {\n status = 499;\n await store.enqueueRequest(\n session.id,\n {\n ...previewRequest,\n body: undefined,\n encoding: undefined,\n cancelled: true,\n createdAt: Date.now(),\n },\n remainingSessionTtl(session, config),\n );\n return new Response(null, { status: 499 });\n }\n if (response === \"stale\") {\n status = 503;\n return inactivePreviewResponse(request, route.name);\n }\n if (!response) {\n status = 504;\n return text(\"The local Farm preview did not respond before the gateway timed out.\", 504);\n }\n\n const headers = new Headers();\n const responseHopByHopHeaders = getHopByHopHeaderNames(response.headers || {});\n for (const [key, value] of Object.entries(response.headers || {})) {\n const normalized = key.toLowerCase();\n if (responseHopByHopHeaders.has(normalized) || normalized === \"content-encoding\") {\n continue;\n }\n // Append multi-valued headers (Set-Cookie) so every value reaches the\n // visitor; Headers.set would keep only the last.\n for (const single of Array.isArray(value) ? value : [value]) {\n headers.append(key, single);\n }\n }\n headers.set(\"cache-control\", \"no-store\");\n headers.set(\"x-farm-preview\", session.name);\n\n status = response.status || 200;\n return new Response(\n response.body\n ? Buffer.from(response.body, response.encoding === \"base64\" ? \"base64\" : \"utf8\")\n : null,\n {\n status,\n headers,\n },\n );\n } catch (error) {\n if (error instanceof GatewayHttpError) status = error.status;\n throw error;\n } finally {\n if (config.observer?.request)\n observe(() =>\n config.observer!.request!({\n sessionId: session.id,\n method: request.method,\n path: route.path.split(/[?#]/, 1)[0].slice(0, 2048),\n status,\n durationMs: Date.now() - startedAt,\n at: Date.now(),\n }),\n );\n }\n}\n\nasync function waitForPreviewResponse(\n store: PreviewGatewayStore,\n config: PreviewGatewayRuntimeConfig,\n session: PreviewGatewaySession,\n requestId: string,\n signal: AbortSignal,\n) {\n const deadline = Date.now() + config.requestTimeoutMs;\n let nextLivenessCheckAt = 0;\n\n while (Date.now() < deadline) {\n if (signal.aborted) return \"cancelled\";\n if (Date.now() >= nextLivenessCheckAt) {\n await requireSessionAccess(config, session);\n const latestSession = await store.getSessionById(session.id);\n if (!latestSession || !isPreviewClientOnline(latestSession, config)) {\n if (latestSession) await store.deleteSession(latestSession);\n return \"stale\";\n }\n nextLivenessCheckAt = Date.now() + 1000;\n }\n\n const response = await store.getResponse(session.id, requestId);\n if (response) {\n await requireSessionAccess(config, session);\n await store.deleteResponse(session.id, requestId);\n return response;\n }\n try {\n await delay(config.pollIntervalMs, undefined, { signal });\n } catch {\n if (signal.aborted) return \"cancelled\";\n throw new Error(\"Preview response polling was interrupted.\");\n }\n }\n\n return undefined;\n}\n\nasync function requireSessionAccess(\n config: PreviewGatewayRuntimeConfig,\n session: PreviewGatewaySession,\n) {\n if (!config.authorizeSession) return;\n const { id, name, ownerId, project, keyId, grantId, expiresAt } = session;\n let allowed;\n try {\n allowed = await config.authorizeSession({\n id,\n name,\n ownerId,\n project,\n keyId,\n grantId,\n expiresAt,\n });\n } catch {\n throw new GatewayHttpError(503, \"Preview access verification is temporarily unavailable.\");\n }\n if (allowed !== true) throw new GatewayHttpError(403, \"This preview is no longer authorized.\");\n}\n\nasync function markSessionOnline(\n store: PreviewGatewayStore,\n config: PreviewGatewayRuntimeConfig,\n session: PreviewGatewaySession,\n) {\n const now = Date.now();\n session.lastHeartbeatAt = now;\n if (session.slidingExpiration) {\n session.expiresAt = now + config.sessionTtlMs;\n }\n await store.touchSession(session, remainingSessionTtl(session, config));\n reportSession(config, session, \"connected\");\n}\n\nfunction remainingSessionTtl(session: PreviewGatewaySession, config: PreviewGatewayRuntimeConfig) {\n return Math.max(1, Math.min(config.sessionTtlMs, session.expiresAt - Date.now()));\n}\n\nfunction isPreviewClientOnline(\n session: PreviewGatewaySession,\n config: PreviewGatewayRuntimeConfig,\n) {\n const lastHeartbeatAt = session.lastHeartbeatAt || session.createdAt;\n return (\n session.expiresAt > Date.now() &&\n Date.now() - lastHeartbeatAt <= config.clientHeartbeatTimeoutMs\n );\n}\n\nasync function serializePreviewRequest(\n request: Request,\n url: URL,\n routePath: string,\n maxBodyBytes: number,\n): Promise<PreviewGatewayRequest> {\n const method = request.method.toUpperCase();\n const headers: Record<string, string> = {};\n const requestHopByHopHeaders = getHopByHopHeaderNames(request.headers);\n request.headers.forEach((value, key) => {\n const normalized = key.toLowerCase();\n if (\n !requestHopByHopHeaders.has(normalized) &&\n normalized !== \"forwarded\" &&\n !normalized.startsWith(\"x-forwarded-\")\n ) {\n headers[key] = value;\n }\n });\n\n headers[\"x-forwarded-host\"] = request.headers.get(\"host\") || url.host;\n headers[\"x-forwarded-proto\"] = url.protocol.replace(\":\", \"\");\n\n let body: string | undefined;\n if (method !== \"GET\" && method !== \"HEAD\") {\n const contentLength = Number(request.headers.get(\"content-length\") || 0);\n if (contentLength > maxBodyBytes) {\n throw new GatewayHttpError(413, \"Preview request body is too large.\");\n }\n\n const buffer = await readBodyWithLimit(\n request,\n maxBodyBytes,\n \"Preview request body is too large.\",\n );\n body = buffer.toString(\"base64\");\n }\n\n return {\n id: randomId(\"req\"),\n method,\n path: `${routePath}${url.search}`,\n headers,\n body,\n encoding: body ? \"base64\" : undefined,\n createdAt: Date.now(),\n };\n}\n\nasync function readBodyWithLimit(request: Request, maxBytes: number, message: string) {\n if (!request.body) return Buffer.alloc(0);\n const reader = request.body.getReader();\n const chunks: Uint8Array[] = [];\n let size = 0;\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n size += value.byteLength;\n if (size > maxBytes) {\n await reader.cancel();\n throw new GatewayHttpError(413, message);\n }\n chunks.push(value);\n }\n } catch (error) {\n await reader.cancel().catch(() => undefined);\n throw error;\n }\n return Buffer.concat(chunks, size);\n}\n\nasync function requireSession(store: PreviewGatewayStore, sessionId: string, token: string | null) {\n const session = await store.getSessionById(sessionId);\n if (!session || !token || token !== session.token) {\n throw new GatewayHttpError(401, \"Invalid preview gateway session.\");\n }\n return session;\n}\n\nfunction resolvePublicPreviewRoute(request: Request, url: URL, domain: string) {\n const host = (request.headers.get(\"host\") || url.host)\n .split(\":\")[0]\n .trim()\n .toLowerCase()\n .replace(/\\.$/, \"\");\n\n // Every preview subdomain is its own origin. Honoring the path form there\n // would let `alice.<domain>/__preview/bob` serve bob's app, cookies, and\n // scripts on alice's origin, so tenant hosts route only by subdomain.\n if (host.endsWith(`.${domain}`)) {\n return {\n name: host.slice(0, -(domain.length + 1)),\n path: url.pathname || \"/\",\n };\n }\n\n const pathMatch = url.pathname.match(/^\\/__preview\\/([^/]+)(\\/.*)?$/);\n if (!pathMatch) return undefined;\n return {\n name: pathMatch[1],\n path: pathMatch[2] || \"/\",\n };\n}\n\nfunction matchSessionRoute(pathname: string) {\n const match = pathname.match(\n /^\\/api\\/sessions\\/([^/]+)(?:\\/(requests|heartbeat|responses)(?:\\/([^/]+))?)?$/,\n );\n if (!match) return undefined;\n return {\n sessionId: match[1],\n action: match[2],\n requestId: match[3],\n };\n}\n\nfunction nodeRequestToWebRequest(req: IncomingMessage, signal?: AbortSignal) {\n const host = headerValue(req.headers, \"host\") || \"localhost\";\n // This adapter is the public trust boundary. A visitor-controlled forwarded\n // protocol must not influence the authority serialized to the local app.\n const protocol = isLocalHost(host.split(\":\")[0]) ? \"http\" : \"https\";\n const url = `${protocol}://${host}${req.url || \"/\"}`;\n const headers = nodeHeadersToWebHeaders(req.headers);\n const method = req.method || \"GET\";\n const init: RequestInit & { duplex?: \"half\" } = {\n method,\n headers,\n signal,\n };\n\n if (method !== \"GET\" && method !== \"HEAD\") {\n init.body = req as unknown as BodyInit;\n init.duplex = \"half\";\n }\n\n return new Request(url, init);\n}\n\nfunction nodeHeadersToWebHeaders(headers: IncomingHttpHeaders) {\n const next = new Headers();\n for (const [key, value] of Object.entries(headers)) {\n if (value === undefined) continue;\n if (Array.isArray(value)) {\n for (const item of value) next.append(key, item);\n } else {\n next.set(key, value);\n }\n }\n return next;\n}\n\nfunction headerValue(headers: IncomingHttpHeaders, key: string) {\n const value = headers[key];\n return Array.isArray(value) ? value[0] : value;\n}\n\nfunction createPublicUrl(request: Request, config: PreviewGatewayRuntimeConfig, name: string) {\n // Each browser preview needs its own origin: a path prefix cannot relocate\n // root-relative scripts, imports, fetches, or client-side navigation.\n if (config.domain === \"localhost\" || config.domain.endsWith(\".localhost\")) {\n const base = new URL(config.baseUrl || request.url);\n base.hostname = `${name}.${config.domain}`;\n return base.origin;\n }\n if (config.baseUrl) {\n const base = new URL(config.baseUrl);\n if (isLocalHost(base.hostname)) {\n return `${config.baseUrl.replace(/\\/+$/, \"\")}/__preview/${name}`;\n }\n }\n\n const host = request.headers.get(\"host\") || new URL(request.url).host;\n if (isLocalHost(host.split(\":\")[0])) {\n return `${new URL(request.url).origin}/__preview/${name}`;\n }\n\n return `https://${name}.${config.domain}`;\n}\n\nfunction json(value: unknown, status = 200) {\n return new Response(JSON.stringify(value), {\n status,\n headers: {\n \"content-type\": \"application/json\",\n \"cache-control\": \"no-store\",\n },\n });\n}\n\nfunction text(value: string, status = 200) {\n return new Response(value, {\n status,\n headers: {\n \"content-type\": \"text/plain; charset=utf-8\",\n \"cache-control\": \"no-store\",\n },\n });\n}\n\nfunction inactivePreviewResponse(request: Request, name: string) {\n if (!request.headers.get(\"accept\")?.includes(\"text/html\")) {\n return text(`No active Farm preview is running for \"${name}\".`, 404);\n }\n const safeName = name.replace(/[&<>\"']/g, (character) => {\n if (character === \"&\") return \"&\";\n if (character === \"<\") return \"<\";\n if (character === \">\") return \">\";\n if (character === '\"') return \""\";\n return \"'\";\n });\n return new Response(\n `<!doctype html><html lang=\"en\"><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"><title>Farm preview expired</title><style>body{font:16px/1.5 system-ui,sans-serif;display:grid;min-height:100vh;margin:0;place-items:center;background:#f7f7f5;color:#171714}main{max-width:34rem;padding:2rem;text-align:center}h1{font-size:1.75rem}code{background:#e9e9e4;border-radius:.35rem;padding:.15rem .35rem}footer{margin-top:2.5rem;padding-top:1.5rem;border-top:1px solid #e3e3de;color:#6b6b63;font-size:.875rem}footer p{margin:0 0 1rem}footer p a{color:#171714;text-underline-offset:.2em}.start{display:inline-block;border-radius:.5rem;padding:.5rem .9rem;background:#171714;color:#f7f7f5;font-weight:500;text-decoration:none}</style><main><h1>This Farm preview has expired</h1><p>The temporary preview <code>${safeName}</code> is no longer connected.</p><p>Ask its owner to run <code>farm preview</code> again.</p><footer><p>Previews like this come from <a href=\"https://farmjs.dev\">Farm.js</a>. One command shares your local app.</p><a class=\"start\" href=\"https://farmjs.dev/docs/preview\">Try farm preview</a></footer></main></html>`,\n {\n status: 410,\n headers: {\n \"content-type\": \"text/html; charset=utf-8\",\n \"cache-control\": \"no-store\",\n },\n },\n );\n}\n\nfunction sanitizePreviewName(value: string | undefined) {\n if (!value) return undefined;\n return value\n .toLowerCase()\n .replace(/[^a-z0-9-]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .slice(0, 48);\n}\n\nfunction randomPreviewName() {\n return `farm-${Math.random().toString(36).slice(2, 8)}`;\n}\n\nfunction randomId(prefix: string) {\n return `${prefix}_${crypto.randomUUID().replace(/-/g, \"\")}`;\n}\n\nfunction normalizeDomain(value: string) {\n return value\n .trim()\n .toLowerCase()\n .replace(/^https?:\\/\\//, \"\")\n .replace(/^\\.*/, \"\")\n .replace(/\\/*$/, \"\");\n}\n\nfunction responseKey(sessionId: string, requestId: string) {\n return `${sessionId}:${requestId}`;\n}\n\nfunction clamp(value: number, min: number, max: number) {\n if (!Number.isFinite(value)) return min;\n return Math.max(min, Math.min(max, value));\n}\n\nfunction isLocalHost(hostname: string) {\n return (\n hostname === \"localhost\" ||\n hostname.endsWith(\".localhost\") ||\n hostname === \"127.0.0.1\" ||\n hostname === \"::1\"\n );\n}\n\nclass GatewayHttpError extends Error {\n constructor(\n public status: number,\n message: string,\n ) {\n super(message);\n }\n}\n"],"mappings":"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"}
|