@farm.js/preview-gateway 0.1.1-beta.2 → 0.1.1

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/README.md CHANGED
@@ -14,6 +14,15 @@ The gateway limits public request bodies and local preview response bodies to 5
14
14
 
15
15
  Public disconnects are propagated through the request queue. The polling agent aborts the matching localhost fetch instead of leaving abandoned app work running until its timeout.
16
16
 
17
+ `authorizeSession` is an optional, required-when-configured access hook. It receives only
18
+ session ID, name, verified owner/project/key/grant IDs, and expiry—not transport tokens.
19
+ Return `true` to allow access; denial returns 403 and exceptions return a generic 503.
20
+ The gateway checks registration, public/control requests, queued dispatch and waiting responses.
21
+ Unlike `observer`, errors never fail open. Bound your authority request with a timeout;
22
+ the Infra example uses five seconds. Session DELETE remains available for cleanup.
23
+ Omitting the hook preserves self-hosted behavior. The persistent relay offers the same
24
+ hook, also checking idle sockets every five seconds and dispatch from its coordinator.
25
+
17
26
  Managed deployments can configure `auth` with a GitHub OAuth client id and a signing secret. The gateway then exposes the CLI login exchange, issues account-bound tunnel grants scoped to one preview name and absolute expiry, and requires a grant before creating a session:
18
27
 
19
28
  ```ts
@@ -35,6 +44,8 @@ Keep the signing secret server-only and at least 32 bytes long. GitHub access to
35
44
  Managed public gateways should also provide `rateLimitExchange`. The hook runs before the request body
36
45
  is read or the provider API is called, and rejected requests receive `429` with `Retry-After`. Use a
37
46
  shared store for multi-instance deployments; the hosted Vercel example uses an atomic Redis window.
47
+ If that shared limiter is unavailable, the exchange fails closed with a retryable `503` response and
48
+ does not contact the provider API.
38
49
 
39
50
  Managed grants and explicitly requested durations are absolute expiries. An unauthenticated session
40
51
  created without `expiresInMs` instead uses `sessionTtlMs` as an idle lease and extends it only when the
@@ -42,3 +53,8 @@ local polling agent proves it is still online.
42
53
 
43
54
  Current gateways advertise bearer-header authentication for polling session controls. The Farm CLI
44
55
  uses query-string session tokens only after it positively identifies a pre-auth compatibility gateway.
56
+ Those legacy requests place the session credential in the URL, where reverse proxies, CDNs, platform
57
+ access logs, and URL instrumentation can record it. The Farm CLI and preview gateway package do not
58
+ log control URLs, but compatibility-gateway operators must redact the `token` query parameter from
59
+ request logs and restrict access to existing logs. Upgrade the gateway to bearer authentication to
60
+ keep session credentials out of control URLs.
@@ -5,15 +5,32 @@ interface PreviewManagedAuthOptions {
5
5
  /** Secret used to sign account tokens and one-preview tunnel grants. */
6
6
  signingSecret: string;
7
7
  /** Public client id for the Farming Labs GitHub OAuth app. */
8
- githubClientId: string;
8
+ githubClientId?: string;
9
+ /** First-party device login. Omit to preserve GitHub account authentication. */
10
+ deviceAuth?: {
11
+ issuer: string;
12
+ clientId: string;
13
+ /** Resolve an API key or approved device credential on every new grant. */
14
+ authorizeAccount(token: string): Promise<{
15
+ subject: string;
16
+ login: string;
17
+ expiresAt?: number;
18
+ keyId?: string;
19
+ project?: string;
20
+ } | null>;
21
+ };
22
+ /** Public console URL. Must not contain credentials or query parameters. */
23
+ dashboardUrl?: string;
9
24
  accountTokenTtlMs?: number;
10
25
  defaultSessionTtlMs?: number;
11
26
  maxSessionTtlMs?: number;
12
27
  githubApiUrl?: string;
13
28
  fetch?: typeof fetch;
14
29
  /**
15
- * Shared abuse-control hook for the provider-token exchange endpoint.
16
- * Implementations must not consume the request body.
30
+ * Shared abuse-control hook for provider-token exchange and, in device mode,
31
+ * account credential verification on the tunnel-grant endpoint.
32
+ * Implementations must not consume the request body. Failures reject the
33
+ * exchange with a retryable 503 response instead of bypassing the limiter.
17
34
  */
18
35
  rateLimitExchange?: PreviewAuthExchangeRateLimiter;
19
36
  }
@@ -24,7 +41,11 @@ interface PreviewAuthExchangeRateLimitResult {
24
41
  }
25
42
  type PreviewAuthExchangeRateLimiter = (request: Request) => PreviewAuthExchangeRateLimitResult | Promise<PreviewAuthExchangeRateLimitResult>;
26
43
  interface PreviewAccountIdentity {
27
- provider: "github";
44
+ /** Optional credential restriction; never taken from the grant request. */
45
+ project?: string;
46
+ /** Non-secret identifier supplied by the credential verifier, never the CLI. */
47
+ keyId?: string;
48
+ provider: "github" | "device";
28
49
  subject: string;
29
50
  login: string;
30
51
  name?: string;
@@ -36,7 +57,13 @@ interface PreviewAccountClaims extends PreviewAccountIdentity {
36
57
  expiresAt: number;
37
58
  }
38
59
  interface PreviewTunnelGrantClaims {
60
+ /** Unique signed grant identity, used to revoke reconnects across transports. */
61
+ nonce?: string;
62
+ project?: string;
63
+ keyId?: string;
39
64
  kind: "tunnel";
65
+ /** Absent on older GitHub grants. */
66
+ provider?: "github" | "device";
40
67
  subject: string;
41
68
  login: string;
42
69
  name: string;
@@ -49,13 +76,23 @@ interface PreviewTunnelGrant {
49
76
  expiresAt: number;
50
77
  }
51
78
  declare function getPreviewAuthPublicConfig(options: PreviewManagedAuthOptions): {
79
+ defaultSessionTtlMs: number;
80
+ maxSessionTtlMs: number;
81
+ dashboardUrl?: string | undefined;
82
+ provider: "device";
83
+ clientId: string;
84
+ issuer: string;
52
85
  enabled: true;
53
86
  controlAuth: "bearer";
54
- provider: "github";
55
- clientId: string;
56
- scope: string;
87
+ } | {
57
88
  defaultSessionTtlMs: number;
58
89
  maxSessionTtlMs: number;
90
+ dashboardUrl?: string | undefined;
91
+ provider: "github";
92
+ clientId: string | undefined;
93
+ scope: string;
94
+ enabled: true;
95
+ controlAuth: "bearer";
59
96
  };
60
97
  declare function exchangeGitHubPreviewToken(providerToken: string, options: PreviewManagedAuthOptions): Promise<{
61
98
  token: string;
@@ -65,6 +102,7 @@ declare function exchangeGitHubPreviewToken(providerToken: string, options: Prev
65
102
  declare function verifyPreviewAccountToken(token: string, options: Pick<PreviewManagedAuthOptions, "signingSecret">): PreviewAccountClaims;
66
103
  declare function issuePreviewTunnelGrant(account: PreviewAccountClaims, input: {
67
104
  name: string;
105
+ project?: string;
68
106
  expiresInMs?: number;
69
107
  }, options: PreviewManagedAuthOptions): PreviewTunnelGrant;
70
108
  declare function verifyPreviewTunnelGrant(token: string, options: Pick<PreviewManagedAuthOptions, "signingSecret"> & {
@@ -77,7 +115,20 @@ declare class PreviewAuthError extends Error {
77
115
  }
78
116
  //#endregion
79
117
  //#region src/index.d.ts
118
+ interface PreviewSessionAccess {
119
+ id: string;
120
+ name: string;
121
+ ownerId?: string;
122
+ project?: string;
123
+ keyId?: string;
124
+ grantId?: string;
125
+ expiresAt?: number;
126
+ }
80
127
  interface PreviewGatewayOptions {
128
+ /** Required access control when supplied. Denials and errors fail closed, unlike telemetry. */
129
+ authorizeSession?: (session: PreviewSessionAccess) => Promise<boolean>;
130
+ /** Optional metadata-only telemetry. Failures never interrupt a preview. */
131
+ observer?: PreviewGatewayObserver;
81
132
  domain?: string;
82
133
  baseUrl?: string;
83
134
  store?: PreviewGatewayStore;
@@ -92,6 +143,10 @@ interface PreviewGatewayOptions {
92
143
  auth?: PreviewManagedAuthOptions;
93
144
  }
94
145
  interface PreviewGatewaySession {
146
+ grantId?: string;
147
+ project?: string;
148
+ keyId?: string;
149
+ ownerId?: string;
95
150
  id: string;
96
151
  name: string;
97
152
  hostname: string;
@@ -132,6 +187,28 @@ interface PreviewGatewayStore {
132
187
  deleteResponse(sessionId: string, requestId: string): Promise<void>;
133
188
  deleteSession(session: PreviewGatewaySession): Promise<void>;
134
189
  }
190
+ interface PreviewGatewayObserver {
191
+ session?(event: {
192
+ grantId?: string;
193
+ project?: string;
194
+ keyId?: string;
195
+ id: string;
196
+ name: string;
197
+ ownerId?: string;
198
+ publicUrl: string;
199
+ expiresAt?: number;
200
+ state: "connected" | "disconnected";
201
+ at: number;
202
+ }): void | Promise<void>;
203
+ request?(event: {
204
+ sessionId: string;
205
+ method: string;
206
+ path: string;
207
+ status: number;
208
+ durationMs: number;
209
+ at: number;
210
+ }): void | Promise<void>;
211
+ }
135
212
  /**
136
213
  * A gateway request handler, plus the name-ownership probe another preview
137
214
  * transport needs before it claims the same public hostname.
@@ -186,5 +263,5 @@ declare class RedisRestPreviewGatewayStore implements PreviewGatewayStore {
186
263
  private responseKey;
187
264
  }
188
265
  //#endregion
189
- export { MemoryPreviewGatewayStore, NodePreviewGatewayHandler, type PreviewAccountClaims, type PreviewAccountIdentity, PreviewAuthError, type PreviewAuthExchangeRateLimitResult, type PreviewAuthExchangeRateLimiter, PreviewGatewayHandler, PreviewGatewayOptions, PreviewGatewayRequest, PreviewGatewayResponse, PreviewGatewaySession, PreviewGatewayStore, type PreviewManagedAuthOptions, type PreviewTunnelGrant, type PreviewTunnelGrantClaims, RedisRestPreviewGatewayStore, createNodePreviewGatewayHandler, createPreviewGatewayHandler, createPreviewGatewayStoreFromEnv, exchangeGitHubPreviewToken, getPreviewAuthPublicConfig, issuePreviewTunnelGrant, readPreviewBearerToken, verifyPreviewAccountToken, verifyPreviewTunnelGrant };
190
- //# sourceMappingURL=index-zloSmShT.d.ts.map
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-DeizJwbc.d.ts.map
package/dist/index.d.ts CHANGED
@@ -5,15 +5,32 @@ interface PreviewManagedAuthOptions {
5
5
  /** Secret used to sign account tokens and one-preview tunnel grants. */
6
6
  signingSecret: string;
7
7
  /** Public client id for the Farming Labs GitHub OAuth app. */
8
- githubClientId: string;
8
+ githubClientId?: string;
9
+ /** First-party device login. Omit to preserve GitHub account authentication. */
10
+ deviceAuth?: {
11
+ issuer: string;
12
+ clientId: string;
13
+ /** Resolve an API key or approved device credential on every new grant. */
14
+ authorizeAccount(token: string): Promise<{
15
+ subject: string;
16
+ login: string;
17
+ expiresAt?: number;
18
+ keyId?: string;
19
+ project?: string;
20
+ } | null>;
21
+ };
22
+ /** Public console URL. Must not contain credentials or query parameters. */
23
+ dashboardUrl?: string;
9
24
  accountTokenTtlMs?: number;
10
25
  defaultSessionTtlMs?: number;
11
26
  maxSessionTtlMs?: number;
12
27
  githubApiUrl?: string;
13
28
  fetch?: typeof fetch;
14
29
  /**
15
- * Shared abuse-control hook for the provider-token exchange endpoint.
16
- * Implementations must not consume the request body.
30
+ * Shared abuse-control hook for provider-token exchange and, in device mode,
31
+ * account credential verification on the tunnel-grant endpoint.
32
+ * Implementations must not consume the request body. Failures reject the
33
+ * exchange with a retryable 503 response instead of bypassing the limiter.
17
34
  */
18
35
  rateLimitExchange?: PreviewAuthExchangeRateLimiter;
19
36
  }
@@ -24,7 +41,11 @@ interface PreviewAuthExchangeRateLimitResult {
24
41
  }
25
42
  type PreviewAuthExchangeRateLimiter = (request: Request) => PreviewAuthExchangeRateLimitResult | Promise<PreviewAuthExchangeRateLimitResult>;
26
43
  interface PreviewAccountIdentity {
27
- provider: "github";
44
+ /** Optional credential restriction; never taken from the grant request. */
45
+ project?: string;
46
+ /** Non-secret identifier supplied by the credential verifier, never the CLI. */
47
+ keyId?: string;
48
+ provider: "github" | "device";
28
49
  subject: string;
29
50
  login: string;
30
51
  name?: string;
@@ -36,7 +57,13 @@ interface PreviewAccountClaims extends PreviewAccountIdentity {
36
57
  expiresAt: number;
37
58
  }
38
59
  interface PreviewTunnelGrantClaims {
60
+ /** Unique signed grant identity, used to revoke reconnects across transports. */
61
+ nonce?: string;
62
+ project?: string;
63
+ keyId?: string;
39
64
  kind: "tunnel";
65
+ /** Absent on older GitHub grants. */
66
+ provider?: "github" | "device";
40
67
  subject: string;
41
68
  login: string;
42
69
  name: string;
@@ -49,13 +76,23 @@ interface PreviewTunnelGrant {
49
76
  expiresAt: number;
50
77
  }
51
78
  declare function getPreviewAuthPublicConfig(options: PreviewManagedAuthOptions): {
79
+ defaultSessionTtlMs: number;
80
+ maxSessionTtlMs: number;
81
+ dashboardUrl?: string | undefined;
82
+ provider: "device";
83
+ clientId: string;
84
+ issuer: string;
52
85
  enabled: true;
53
86
  controlAuth: "bearer";
54
- provider: "github";
55
- clientId: string;
56
- scope: string;
87
+ } | {
57
88
  defaultSessionTtlMs: number;
58
89
  maxSessionTtlMs: number;
90
+ dashboardUrl?: string | undefined;
91
+ provider: "github";
92
+ clientId: string | undefined;
93
+ scope: string;
94
+ enabled: true;
95
+ controlAuth: "bearer";
59
96
  };
60
97
  declare function exchangeGitHubPreviewToken(providerToken: string, options: PreviewManagedAuthOptions): Promise<{
61
98
  token: string;
@@ -65,6 +102,7 @@ declare function exchangeGitHubPreviewToken(providerToken: string, options: Prev
65
102
  declare function verifyPreviewAccountToken(token: string, options: Pick<PreviewManagedAuthOptions, "signingSecret">): PreviewAccountClaims;
66
103
  declare function issuePreviewTunnelGrant(account: PreviewAccountClaims, input: {
67
104
  name: string;
105
+ project?: string;
68
106
  expiresInMs?: number;
69
107
  }, options: PreviewManagedAuthOptions): PreviewTunnelGrant;
70
108
  declare function verifyPreviewTunnelGrant(token: string, options: Pick<PreviewManagedAuthOptions, "signingSecret"> & {
@@ -77,7 +115,20 @@ declare class PreviewAuthError extends Error {
77
115
  }
78
116
  //#endregion
79
117
  //#region src/index.d.ts
118
+ interface PreviewSessionAccess {
119
+ id: string;
120
+ name: string;
121
+ ownerId?: string;
122
+ project?: string;
123
+ keyId?: string;
124
+ grantId?: string;
125
+ expiresAt?: number;
126
+ }
80
127
  interface PreviewGatewayOptions {
128
+ /** Required access control when supplied. Denials and errors fail closed, unlike telemetry. */
129
+ authorizeSession?: (session: PreviewSessionAccess) => Promise<boolean>;
130
+ /** Optional metadata-only telemetry. Failures never interrupt a preview. */
131
+ observer?: PreviewGatewayObserver;
81
132
  domain?: string;
82
133
  baseUrl?: string;
83
134
  store?: PreviewGatewayStore;
@@ -92,6 +143,10 @@ interface PreviewGatewayOptions {
92
143
  auth?: PreviewManagedAuthOptions;
93
144
  }
94
145
  interface PreviewGatewaySession {
146
+ grantId?: string;
147
+ project?: string;
148
+ keyId?: string;
149
+ ownerId?: string;
95
150
  id: string;
96
151
  name: string;
97
152
  hostname: string;
@@ -132,6 +187,28 @@ interface PreviewGatewayStore {
132
187
  deleteResponse(sessionId: string, requestId: string): Promise<void>;
133
188
  deleteSession(session: PreviewGatewaySession): Promise<void>;
134
189
  }
190
+ interface PreviewGatewayObserver {
191
+ session?(event: {
192
+ grantId?: string;
193
+ project?: string;
194
+ keyId?: string;
195
+ id: string;
196
+ name: string;
197
+ ownerId?: string;
198
+ publicUrl: string;
199
+ expiresAt?: number;
200
+ state: "connected" | "disconnected";
201
+ at: number;
202
+ }): void | Promise<void>;
203
+ request?(event: {
204
+ sessionId: string;
205
+ method: string;
206
+ path: string;
207
+ status: number;
208
+ durationMs: number;
209
+ at: number;
210
+ }): void | Promise<void>;
211
+ }
135
212
  /**
136
213
  * A gateway request handler, plus the name-ownership probe another preview
137
214
  * transport needs before it claims the same public hostname.
@@ -186,5 +263,5 @@ declare class RedisRestPreviewGatewayStore implements PreviewGatewayStore {
186
263
  private responseKey;
187
264
  }
188
265
  //#endregion
189
- export { MemoryPreviewGatewayStore, NodePreviewGatewayHandler, type PreviewAccountClaims, type PreviewAccountIdentity, PreviewAuthError, type PreviewAuthExchangeRateLimitResult, type PreviewAuthExchangeRateLimiter, PreviewGatewayHandler, PreviewGatewayOptions, PreviewGatewayRequest, PreviewGatewayResponse, PreviewGatewaySession, PreviewGatewayStore, type PreviewManagedAuthOptions, type PreviewTunnelGrant, type PreviewTunnelGrantClaims, RedisRestPreviewGatewayStore, createNodePreviewGatewayHandler, createPreviewGatewayHandler, createPreviewGatewayStoreFromEnv, exchangeGitHubPreviewToken, getPreviewAuthPublicConfig, issuePreviewTunnelGrant, readPreviewBearerToken, verifyPreviewAccountToken, verifyPreviewTunnelGrant };
190
- //# sourceMappingURL=index-zloSmShT.d.ts.map
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-DeizJwbc.d.ts.map
package/dist/index.js CHANGED
@@ -13,15 +13,23 @@ function getPreviewAuthPublicConfig(options) {
13
13
  return {
14
14
  enabled: true,
15
15
  controlAuth: "bearer",
16
- provider: "github",
17
- clientId: options.githubClientId,
18
- scope: "read:user",
16
+ ...options.deviceAuth ? {
17
+ provider: "device",
18
+ clientId: options.deviceAuth.clientId,
19
+ issuer: options.deviceAuth.issuer
20
+ } : {
21
+ provider: "github",
22
+ clientId: options.githubClientId,
23
+ scope: "read:user"
24
+ },
25
+ ...options.dashboardUrl ? { dashboardUrl: options.dashboardUrl } : {},
19
26
  defaultSessionTtlMs: durations.defaultSessionTtlMs,
20
27
  maxSessionTtlMs: durations.maxSessionTtlMs
21
28
  };
22
29
  }
23
30
  async function exchangeGitHubPreviewToken(providerToken, options) {
24
31
  validatePreviewAuthOptions(options);
32
+ if (options.deviceAuth) throw new PreviewAuthError(400, "Use the configured device sign-in flow.");
25
33
  if (!providerToken || providerToken.length > 4096) throw new PreviewAuthError(400, "A GitHub access token is required.");
26
34
  const response = await (options.fetch || fetch)(`${options.githubApiUrl || "https://api.github.com"}/user`, {
27
35
  headers: {
@@ -66,17 +74,23 @@ function verifyPreviewAccountToken(token, options) {
66
74
  function issuePreviewTunnelGrant(account, input, options) {
67
75
  validatePreviewAuthOptions(options);
68
76
  const durations = resolvePreviewDurations(options);
77
+ if (input.project !== void 0 && (typeof input.project !== "string" || !/^[a-z0-9][a-z0-9-]{0,62}$/.test(input.project))) throw new PreviewAuthError(400, "Preview project must be a lowercase slug of 1–63 characters.");
69
78
  if (input.expiresInMs !== void 0 && (!Number.isSafeInteger(input.expiresInMs) || input.expiresInMs <= 0)) throw new PreviewAuthError(400, "Preview expiry must be a positive integer.");
70
79
  const requestedTtlMs = input.expiresInMs ?? durations.defaultSessionTtlMs;
80
+ if (account.project !== void 0 && account.project !== (input.project ?? input.name)) throw new PreviewAuthError(403, "This API key is restricted to another preview project.");
71
81
  const ttlMs = Math.max(MIN_SESSION_TTL_MS, Math.min(durations.maxSessionTtlMs, requestedTtlMs));
72
82
  const issuedAt = Date.now();
73
- const expiresAt = issuedAt + ttlMs;
83
+ const expiresAt = Math.min(issuedAt + ttlMs, account.expiresAt);
84
+ if (expiresAt <= issuedAt) throw new PreviewAuthError(401, "The account credential has expired.");
74
85
  return {
75
86
  token: signPreviewClaims({
76
87
  kind: "tunnel",
88
+ provider: account.provider,
77
89
  subject: account.subject,
78
90
  login: account.login,
79
91
  name: input.name,
92
+ project: input.project ?? input.name,
93
+ ...account.keyId ? { keyId: account.keyId } : {},
80
94
  issuedAt,
81
95
  expiresAt,
82
96
  nonce: randomUUID()
@@ -87,7 +101,7 @@ function issuePreviewTunnelGrant(account, input, options) {
87
101
  }
88
102
  function verifyPreviewTunnelGrant(token, options) {
89
103
  const claims = verifyPreviewClaims(token, options.signingSecret);
90
- if (claims.kind !== "tunnel" || typeof claims.subject !== "string" || typeof claims.login !== "string" || typeof claims.name !== "string" || options.name !== void 0 && claims.name !== options.name) throw new PreviewAuthError(401, "The Farm Preview tunnel grant is invalid.");
104
+ if (claims.kind !== "tunnel" || typeof claims.subject !== "string" || typeof claims.login !== "string" || typeof claims.name !== "string" || claims.nonce !== void 0 && (typeof claims.nonce !== "string" || !/^[\w-]{1,128}$/.test(claims.nonce)) || claims.project !== void 0 && (typeof claims.project !== "string" || !/^[a-z0-9][a-z0-9-]{0,62}$/.test(claims.project)) || claims.keyId !== void 0 && (typeof claims.keyId !== "string" || !/^[\w-]{1,128}$/.test(claims.keyId)) || options.name !== void 0 && claims.name !== options.name) throw new PreviewAuthError(401, "The Farm Preview tunnel grant is invalid.");
91
105
  return claims;
92
106
  }
93
107
  function readPreviewBearerToken(request) {
@@ -110,13 +124,49 @@ function resolvePreviewDurations(options) {
110
124
  }
111
125
  function validatePreviewAuthOptions(options) {
112
126
  if (Buffer.byteLength(options.signingSecret) < 32) throw new Error("Farm Preview auth signingSecret must contain at least 32 bytes.");
113
- if (!options.githubClientId) throw new Error("Farm Preview auth requires a GitHub OAuth client id.");
127
+ if (!options.githubClientId && !options.deviceAuth) throw new Error("Farm Preview auth requires a GitHub OAuth client id or deviceAuth.");
128
+ if (options.deviceAuth) {
129
+ validatePublicAuthUrl(options.deviceAuth.issuer);
130
+ if (!options.deviceAuth.clientId) throw new Error("Farm Preview deviceAuth requires a clientId.");
131
+ }
132
+ if (options.dashboardUrl) validatePublicAuthUrl(options.dashboardUrl);
114
133
  for (const [name, value] of [
115
134
  ["accountTokenTtlMs", options.accountTokenTtlMs],
116
135
  ["defaultSessionTtlMs", options.defaultSessionTtlMs],
117
136
  ["maxSessionTtlMs", options.maxSessionTtlMs]
118
137
  ]) if (value !== void 0 && (!Number.isSafeInteger(value) || value <= 0)) throw new Error(`Farm Preview auth ${name} must be a positive integer.`);
119
138
  }
139
+ function validatePublicAuthUrl(value) {
140
+ const url = new URL(value);
141
+ const local = [
142
+ "localhost",
143
+ "127.0.0.1",
144
+ "[::1]"
145
+ ].includes(url.hostname);
146
+ if (url.protocol !== "https:" && !(local && url.protocol === "http:") || url.username || url.password || url.search || url.hash) throw new Error("Farm Preview auth URLs must use HTTPS (HTTP only for loopback development), without credentials, queries or fragments.");
147
+ }
148
+ async function authorizePreviewAccount(token, options) {
149
+ if (!options.deviceAuth) return verifyPreviewAccountToken(token, options);
150
+ if (!token || token.length > 4096) throw new PreviewAuthError(401, "Invalid preview credential.");
151
+ let account;
152
+ try {
153
+ account = await options.deviceAuth.authorizeAccount(token);
154
+ } catch {
155
+ throw new PreviewAuthError(503, "Farm Preview authentication is temporarily unavailable.");
156
+ }
157
+ const now = Date.now();
158
+ if (!account || typeof account.subject !== "string" || !account.subject || typeof account.login !== "string" || !account.login || account.project !== void 0 && (typeof account.project !== "string" || !/^[a-z0-9][a-z0-9-]{0,62}$/.test(account.project)) || account.keyId !== void 0 && (typeof account.keyId !== "string" || !/^[\w-]{1,128}$/.test(account.keyId)) || account.expiresAt !== void 0 && (!Number.isSafeInteger(account.expiresAt) || account.expiresAt <= now)) throw new PreviewAuthError(401, "The Farm Infra credential is invalid or expired.");
159
+ return {
160
+ kind: "account",
161
+ provider: "device",
162
+ subject: account.subject,
163
+ login: account.login,
164
+ ...account.keyId ? { keyId: account.keyId } : {},
165
+ ...account.project ? { project: account.project } : {},
166
+ issuedAt: now,
167
+ expiresAt: account.expiresAt ?? now + DEFAULT_ACCOUNT_TOKEN_TTL_MS
168
+ };
169
+ }
120
170
  function signPreviewClaims(claims, secret) {
121
171
  const payload = Buffer.from(JSON.stringify(claims)).toString("base64url");
122
172
  const signature = createHmac("sha256", secret).update(`${TOKEN_PREFIX}.${payload}`).digest("base64url");
@@ -145,6 +195,27 @@ function verifyPreviewClaims(token, secret) {
145
195
  }
146
196
  //#endregion
147
197
  //#region src/index.ts
198
+ function observe(callback) {
199
+ if (!callback) return;
200
+ try {
201
+ Promise.resolve(callback()).catch(() => void 0);
202
+ } catch {}
203
+ }
204
+ function reportSession(config, session, state) {
205
+ if (!config.observer?.session) return;
206
+ observe(() => config.observer.session({
207
+ id: session.id,
208
+ grantId: session.grantId,
209
+ name: session.name,
210
+ project: session.project,
211
+ keyId: session.keyId,
212
+ ownerId: session.ownerId,
213
+ publicUrl: session.publicUrl,
214
+ expiresAt: session.expiresAt,
215
+ state,
216
+ at: Date.now()
217
+ }));
218
+ }
148
219
  const DEFAULT_DOMAIN = "preview.farmjs.dev";
149
220
  const DEFAULT_SESSION_TTL_MS = 18e5;
150
221
  const DEFAULT_CLIENT_HEARTBEAT_TIMEOUT_MS = 2e4;
@@ -179,6 +250,8 @@ function createPreviewGatewayHandler(options = {}) {
179
250
  if (options.auth) getPreviewAuthPublicConfig(options.auth);
180
251
  const store = options.store || createPreviewGatewayStoreFromEnv();
181
252
  const config = {
253
+ authorizeSession: options.authorizeSession,
254
+ observer: options.observer,
182
255
  domain: normalizeDomain(options.domain || process.env.FARM_PREVIEW_DOMAIN || DEFAULT_DOMAIN),
183
256
  baseUrl: options.baseUrl || process.env.FARM_PREVIEW_GATEWAY_URL,
184
257
  sessionTtlMs: options.sessionTtlMs ?? DEFAULT_SESSION_TTL_MS,
@@ -212,6 +285,10 @@ function createPreviewGatewayHandler(options = {}) {
212
285
  }
213
286
  if (request.method === "POST" && url.pathname === "/api/tunnel/grants") {
214
287
  if (!config.auth) return text("Managed preview authentication is not configured.", 404);
288
+ if (config.auth.deviceAuth) {
289
+ const limited = await limitPreviewAccountExchange(request, config.auth);
290
+ if (limited) return limited;
291
+ }
215
292
  return await createTunnelGrant(request, config.auth);
216
293
  }
217
294
  if (request.method === "POST" && url.pathname === "/api/sessions") return await createSession(request, store, config);
@@ -238,7 +315,19 @@ function createPreviewGatewayHandler(options = {}) {
238
315
  }
239
316
  async function limitPreviewAccountExchange(request, auth) {
240
317
  if (!auth.rateLimitExchange) return void 0;
241
- const result = await auth.rateLimitExchange(request);
318
+ let result;
319
+ try {
320
+ result = await auth.rateLimitExchange(request);
321
+ } catch {
322
+ return new Response("Farm Preview login is temporarily unavailable. Try again shortly.", {
323
+ status: 503,
324
+ headers: {
325
+ "cache-control": "no-store",
326
+ "content-type": "text/plain; charset=utf-8",
327
+ "retry-after": "1"
328
+ }
329
+ });
330
+ }
242
331
  if (result.allowed) return void 0;
243
332
  const retryAfterMs = Number.isFinite(result.retryAfterMs) && (result.retryAfterMs ?? 0) > 0 ? result.retryAfterMs : 1e3;
244
333
  return new Response("Too many Farm Preview login attempts. Try again shortly.", {
@@ -258,12 +347,13 @@ async function exchangePreviewAccount(request, auth) {
258
347
  async function createTunnelGrant(request, auth) {
259
348
  const accountToken = readPreviewBearerToken(request);
260
349
  if (!accountToken) throw new PreviewAuthError(401, "Sign in with Farm Preview before creating a tunnel grant.");
261
- const account = verifyPreviewAccountToken(accountToken, auth);
350
+ const account = await authorizePreviewAccount(accountToken, auth);
262
351
  const input = await request.json().catch(() => ({}));
263
352
  const name = sanitizePreviewName(input.name);
264
353
  if (!name) throw new PreviewAuthError(400, "A valid preview name is required.");
265
354
  return json(issuePreviewTunnelGrant(account, {
266
355
  name,
356
+ project: input.project,
267
357
  ...input.expiresInMs !== void 0 ? { expiresInMs: input.expiresInMs } : {}
268
358
  }, auth));
269
359
  }
@@ -445,6 +535,10 @@ async function createSession(request, store, config) {
445
535
  let name = requestedName || randomPreviewName();
446
536
  let expiresAt = Date.now() + config.sessionTtlMs;
447
537
  let slidingExpiration = true;
538
+ let ownerId;
539
+ let project;
540
+ let keyId;
541
+ let grantId;
448
542
  if (config.auth) {
449
543
  const grantToken = readPreviewBearerToken(request);
450
544
  if (!grantToken) throw new GatewayHttpError(401, "Sign in with Farm Preview before creating a session.");
@@ -454,6 +548,10 @@ async function createSession(request, store, config) {
454
548
  ...requestedName ? { name: requestedName } : {}
455
549
  });
456
550
  name = grant.name;
551
+ ownerId = `${grant.provider || "github"}:${grant.subject}`;
552
+ project = grant.project ?? grant.name;
553
+ keyId = grant.keyId;
554
+ grantId = grant.nonce;
457
555
  expiresAt = grant.expiresAt;
458
556
  slidingExpiration = false;
459
557
  } catch (error) {
@@ -473,6 +571,10 @@ async function createSession(request, store, config) {
473
571
  }
474
572
  const hostname = `${name}.${config.domain}`;
475
573
  const session = {
574
+ ...grantId ? { grantId } : {},
575
+ ...project ? { project } : {},
576
+ ...keyId ? { keyId } : {},
577
+ ...ownerId ? { ownerId } : {},
476
578
  id: randomId("sess"),
477
579
  name,
478
580
  hostname,
@@ -484,7 +586,9 @@ async function createSession(request, store, config) {
484
586
  ...slidingExpiration ? { slidingExpiration: true } : {},
485
587
  lastHeartbeatAt: Date.now()
486
588
  };
589
+ await requireSessionAccess(config, session);
487
590
  await store.createSession(session, Math.max(1, expiresAt - Date.now()));
591
+ reportSession(config, session, "connected");
488
592
  return json({
489
593
  id: session.id,
490
594
  name: session.name,
@@ -495,6 +599,7 @@ async function createSession(request, store, config) {
495
599
  }
496
600
  async function handleSessionRoute(request, url, store, config, route) {
497
601
  const session = await requireSession(store, route.sessionId, readPreviewBearerToken(request) || url.searchParams.get("token"));
602
+ if (request.method !== "DELETE" || route.action) await requireSessionAccess(config, session);
498
603
  if (request.method === "GET" && route.action === "requests") return await pollSessionRequests(url, store, config, session);
499
604
  if (request.method === "POST" && route.action === "responses" && route.requestId) {
500
605
  let response;
@@ -517,6 +622,7 @@ async function handleSessionRoute(request, url, store, config, route) {
517
622
  }
518
623
  if (request.method === "DELETE" && !route.action) {
519
624
  await store.deleteSession(session);
625
+ reportSession(config, session, "disconnected");
520
626
  return json({ ok: true });
521
627
  }
522
628
  return text("Preview gateway route not found.", 404);
@@ -553,10 +659,16 @@ function createOversizedPreviewResponse(maxBodyBytes) {
553
659
  async function pollSessionRequests(url, store, config, session) {
554
660
  const waitMs = clamp(Number(url.searchParams.get("wait") || config.pollTimeoutMs), 1e3, 25e3);
555
661
  const deadline = Date.now() + waitMs;
662
+ let nextAccessCheckAt = Date.now() + 1e3;
556
663
  await markSessionOnline(store, config, session);
557
664
  while (Date.now() < deadline) {
665
+ if (Date.now() >= nextAccessCheckAt) {
666
+ await requireSessionAccess(config, session);
667
+ nextAccessCheckAt = Date.now() + 1e3;
668
+ }
558
669
  const requests = await store.takeRequests(session.id, DEFAULT_POLL_REQUEST_LIMIT);
559
670
  if (requests.length) {
671
+ await requireSessionAccess(config, session);
560
672
  await markSessionOnline(store, config, session);
561
673
  return json({ requests });
562
674
  }
@@ -570,37 +682,63 @@ async function proxyPublicRequest(request, url, store, config, route) {
570
682
  if (!session) return inactivePreviewResponse(request, route.name);
571
683
  if (!isPreviewClientOnline(session, config)) {
572
684
  await store.deleteSession(session);
685
+ reportSession(config, session, "disconnected");
573
686
  return inactivePreviewResponse(request, route.name);
574
687
  }
575
- const previewRequest = await serializePreviewRequest(request, url, route.path, config.maxBodyBytes);
576
- await store.enqueueRequest(session.id, previewRequest, remainingSessionTtl(session, config));
577
- await store.touchSession(session, remainingSessionTtl(session, config));
578
- const response = await waitForPreviewResponse(store, config, session, previewRequest.id, request.signal);
579
- if (response === "cancelled") {
580
- await store.enqueueRequest(session.id, {
581
- ...previewRequest,
582
- body: void 0,
583
- encoding: void 0,
584
- cancelled: true,
585
- createdAt: Date.now()
586
- }, remainingSessionTtl(session, config));
587
- return new Response(null, { status: 499 });
588
- }
589
- if (response === "stale") return inactivePreviewResponse(request, route.name);
590
- if (!response) return text("The local Farm preview did not respond before the gateway timed out.", 504);
591
- const headers = new Headers();
592
- const responseHopByHopHeaders = getHopByHopHeaderNames(response.headers || {});
593
- for (const [key, value] of Object.entries(response.headers || {})) {
594
- const normalized = key.toLowerCase();
595
- if (responseHopByHopHeaders.has(normalized) || normalized === "content-encoding") continue;
596
- for (const single of Array.isArray(value) ? value : [value]) headers.append(key, single);
597
- }
598
- headers.set("cache-control", "no-store");
599
- headers.set("x-farm-preview", session.name);
600
- return new Response(response.body ? Buffer.from(response.body, response.encoding === "base64" ? "base64" : "utf8") : null, {
601
- status: response.status || 200,
602
- headers
603
- });
688
+ await requireSessionAccess(config, session);
689
+ const startedAt = Date.now();
690
+ let status = 500;
691
+ try {
692
+ const previewRequest = await serializePreviewRequest(request, url, route.path, config.maxBodyBytes);
693
+ await store.enqueueRequest(session.id, previewRequest, remainingSessionTtl(session, config));
694
+ await store.touchSession(session, remainingSessionTtl(session, config));
695
+ const response = await waitForPreviewResponse(store, config, session, previewRequest.id, request.signal);
696
+ if (response === "cancelled") {
697
+ status = 499;
698
+ await store.enqueueRequest(session.id, {
699
+ ...previewRequest,
700
+ body: void 0,
701
+ encoding: void 0,
702
+ cancelled: true,
703
+ createdAt: Date.now()
704
+ }, remainingSessionTtl(session, config));
705
+ return new Response(null, { status: 499 });
706
+ }
707
+ if (response === "stale") {
708
+ status = 503;
709
+ return inactivePreviewResponse(request, route.name);
710
+ }
711
+ if (!response) {
712
+ status = 504;
713
+ return text("The local Farm preview did not respond before the gateway timed out.", 504);
714
+ }
715
+ const headers = new Headers();
716
+ const responseHopByHopHeaders = getHopByHopHeaderNames(response.headers || {});
717
+ for (const [key, value] of Object.entries(response.headers || {})) {
718
+ const normalized = key.toLowerCase();
719
+ if (responseHopByHopHeaders.has(normalized) || normalized === "content-encoding") continue;
720
+ for (const single of Array.isArray(value) ? value : [value]) headers.append(key, single);
721
+ }
722
+ headers.set("cache-control", "no-store");
723
+ headers.set("x-farm-preview", session.name);
724
+ status = response.status || 200;
725
+ return new Response(response.body ? Buffer.from(response.body, response.encoding === "base64" ? "base64" : "utf8") : null, {
726
+ status,
727
+ headers
728
+ });
729
+ } catch (error) {
730
+ if (error instanceof GatewayHttpError) status = error.status;
731
+ throw error;
732
+ } finally {
733
+ if (config.observer?.request) observe(() => config.observer.request({
734
+ sessionId: session.id,
735
+ method: request.method,
736
+ path: route.path.split(/[?#]/, 1)[0].slice(0, 2048),
737
+ status,
738
+ durationMs: Date.now() - startedAt,
739
+ at: Date.now()
740
+ }));
741
+ }
604
742
  }
605
743
  async function waitForPreviewResponse(store, config, session, requestId, signal) {
606
744
  const deadline = Date.now() + config.requestTimeoutMs;
@@ -608,6 +746,7 @@ async function waitForPreviewResponse(store, config, session, requestId, signal)
608
746
  while (Date.now() < deadline) {
609
747
  if (signal.aborted) return "cancelled";
610
748
  if (Date.now() >= nextLivenessCheckAt) {
749
+ await requireSessionAccess(config, session);
611
750
  const latestSession = await store.getSessionById(session.id);
612
751
  if (!latestSession || !isPreviewClientOnline(latestSession, config)) {
613
752
  if (latestSession) await store.deleteSession(latestSession);
@@ -617,6 +756,7 @@ async function waitForPreviewResponse(store, config, session, requestId, signal)
617
756
  }
618
757
  const response = await store.getResponse(session.id, requestId);
619
758
  if (response) {
759
+ await requireSessionAccess(config, session);
620
760
  await store.deleteResponse(session.id, requestId);
621
761
  return response;
622
762
  }
@@ -628,11 +768,31 @@ async function waitForPreviewResponse(store, config, session, requestId, signal)
628
768
  }
629
769
  }
630
770
  }
771
+ async function requireSessionAccess(config, session) {
772
+ if (!config.authorizeSession) return;
773
+ const { id, name, ownerId, project, keyId, grantId, expiresAt } = session;
774
+ let allowed;
775
+ try {
776
+ allowed = await config.authorizeSession({
777
+ id,
778
+ name,
779
+ ownerId,
780
+ project,
781
+ keyId,
782
+ grantId,
783
+ expiresAt
784
+ });
785
+ } catch {
786
+ throw new GatewayHttpError(503, "Preview access verification is temporarily unavailable.");
787
+ }
788
+ if (allowed !== true) throw new GatewayHttpError(403, "This preview is no longer authorized.");
789
+ }
631
790
  async function markSessionOnline(store, config, session) {
632
791
  const now = Date.now();
633
792
  session.lastHeartbeatAt = now;
634
793
  if (session.slidingExpiration) session.expiresAt = now + config.sessionTtlMs;
635
794
  await store.touchSession(session, remainingSessionTtl(session, config));
795
+ reportSession(config, session, "connected");
636
796
  }
637
797
  function remainingSessionTtl(session, config) {
638
798
  return Math.max(1, Math.min(config.sessionTtlMs, session.expiresAt - Date.now()));
@@ -745,6 +905,11 @@ function headerValue(headers, key) {
745
905
  return Array.isArray(value) ? value[0] : value;
746
906
  }
747
907
  function createPublicUrl(request, config, name) {
908
+ if (config.domain === "localhost" || config.domain.endsWith(".localhost")) {
909
+ const base = new URL(config.baseUrl || request.url);
910
+ base.hostname = `${name}.${config.domain}`;
911
+ return base.origin;
912
+ }
748
913
  if (config.baseUrl) {
749
914
  if (isLocalHost(new URL(config.baseUrl).hostname)) return `${config.baseUrl.replace(/\/+$/, "")}/__preview/${name}`;
750
915
  }
@@ -807,7 +972,7 @@ function clamp(value, min, max) {
807
972
  return Math.max(min, Math.min(max, value));
808
973
  }
809
974
  function isLocalHost(hostname) {
810
- return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1";
975
+ return hostname === "localhost" || hostname.endsWith(".localhost") || hostname === "127.0.0.1" || hostname === "::1";
811
976
  }
812
977
  var GatewayHttpError = class extends Error {
813
978
  constructor(status, message) {
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 accountTokenTtlMs?: number;\n defaultSessionTtlMs?: number;\n maxSessionTtlMs?: number;\n githubApiUrl?: string;\n fetch?: typeof fetch;\n /**\n * Shared abuse-control hook for the provider-token exchange endpoint.\n * Implementations must not consume the request body.\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 provider: \"github\";\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 kind: \"tunnel\";\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 provider: \"github\" as const,\n clientId: options.githubClientId,\n scope: \"read:user\",\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 (!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; expiresInMs?: number },\n options: PreviewManagedAuthOptions,\n): PreviewTunnelGrant {\n validatePreviewAuthOptions(options);\n const durations = resolvePreviewDurations(options);\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 const ttlMs = Math.max(MIN_SESSION_TTL_MS, Math.min(durations.maxSessionTtlMs, requestedTtlMs));\n const issuedAt = Date.now();\n const expiresAt = issuedAt + ttlMs;\n const claims: PreviewTunnelGrantClaims & { nonce: string } = {\n kind: \"tunnel\",\n subject: account.subject,\n login: account.login,\n name: input.name,\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 (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) {\n throw new Error(\"Farm Preview auth requires a GitHub OAuth client id.\");\n }\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 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 exchangeGitHubPreviewToken,\n getPreviewAuthPublicConfig,\n issuePreviewTunnelGrant,\n PreviewAuthError,\n readPreviewBearerToken,\n verifyPreviewAccountToken,\n verifyPreviewTunnelGrant,\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 PreviewGatewayOptions {\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 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 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\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 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 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 const result = await auth.rateLimitExchange(request);\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 = verifyPreviewAccountToken(accountToken, auth);\n const input = (await request.json().catch(() => ({}))) as {\n name?: 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 ...(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\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 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 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 store.createSession(session, Math.max(1, expiresAt - Date.now()));\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 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 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\n await markSessionOnline(store, config, session);\n\n while (Date.now() < deadline) {\n const requests = await store.takeRequests(session.id, DEFAULT_POLL_REQUEST_LIMIT);\n if (requests.length) {\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 return inactivePreviewResponse(request, route.name);\n }\n\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 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 return inactivePreviewResponse(request, route.name);\n }\n if (!response) {\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 return new Response(\n response.body\n ? Buffer.from(response.body, response.encoding === \"base64\" ? \"base64\" : \"utf8\")\n : null,\n {\n status: response.status || 200,\n headers,\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 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 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 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}\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 pathMatch = url.pathname.match(/^\\/__preview\\/([^/]+)(\\/.*)?$/);\n if (pathMatch) {\n return {\n name: pathMatch[1],\n path: pathMatch[2] || \"/\",\n };\n }\n\n const host = (request.headers.get(\"host\") || url.host).split(\":\")[0];\n if (!host || host === domain || !host.endsWith(`.${domain}`)) {\n return undefined;\n }\n\n return {\n name: host.slice(0, -(domain.length + 1)),\n path: url.pathname || \"/\",\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 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 \"&amp;\";\n if (character === \"<\") return \"&lt;\";\n if (character === \">\") return \"&gt;\";\n if (character === '\"') return \"&quot;\";\n return \"&#39;\";\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}</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></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 .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 hostname === \"localhost\" || hostname === \"127.0.0.1\" || hostname === \"::1\";\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 Infra 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 pathMatch = url.pathname.match(/^\\/__preview\\/([^/]+)(\\/.*)?$/);\n if (pathMatch) {\n return {\n name: pathMatch[1],\n path: pathMatch[2] || \"/\",\n };\n }\n\n const host = (request.headers.get(\"host\") || url.host).split(\":\")[0];\n if (!host || host === domain || !host.endsWith(`.${domain}`)) {\n return undefined;\n }\n\n return {\n name: host.slice(0, -(domain.length + 1)),\n path: url.pathname || \"/\",\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 \"&amp;\";\n if (character === \"<\") return \"&lt;\";\n if (character === \">\") return \"&gt;\";\n if (character === '\"') return \"&quot;\";\n return \"&#39;\";\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}</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></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 .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":";;;AAqFA,MAAM,eAAe;AACrB,MAAM,+BAA+B;AACrC,MAAMA,2BAAyB;AAC/B,MAAM,6BAA6B;AACnC,MAAM,qBAAqB;AAC3B,MAAM,oBAAoB;AAE1B,SAAgB,2BAA2B,SAAoC;CAC7E,2BAA2B,OAAO;CAClC,MAAM,YAAY,wBAAwB,OAAO;CACjD,OAAO;EACL,SAAS;EACT,aAAa;EACb,GAAI,QAAQ,aACR;GACE,UAAU;GACV,UAAU,QAAQ,WAAW;GAC7B,QAAQ,QAAQ,WAAW;EAC7B,IACA;GAAE,UAAU;GAAmB,UAAU,QAAQ;GAAgB,OAAO;EAAY;EACxF,GAAI,QAAQ,eAAe,EAAE,cAAc,QAAQ,aAAa,IAAI,CAAC;EACrE,qBAAqB,UAAU;EAC/B,iBAAiB,UAAU;CAC7B;AACF;AAEA,eAAsB,2BACpB,eACA,SAC6E;CAC7E,2BAA2B,OAAO;CAClC,IAAI,QAAQ,YACV,MAAM,IAAI,iBAAiB,KAAK,yCAAyC;CAC3E,IAAI,CAAC,iBAAiB,cAAc,SAAS,MAC3C,MAAM,IAAI,iBAAiB,KAAK,oCAAoC;CAItE,MAAM,WAAW,OADD,QAAQ,SAAS,MAAA,CACF,GAAG,QAAQ,gBAAgB,yBAAyB,QAAQ;EACzF,SAAS;GACP,QAAQ;GACR,eAAe,UAAU;GACzB,cAAc;GACd,wBAAwB;EAC1B;EACA,QAAQ,YAAY,QAAQ,GAAM;CACpC,CAAC;CACD,IAAI,CAAC,SAAS,IAAI;EAChB,MAAM,SAAS,MAAM,OAAO;EAC5B,MAAM,IAAI,iBAAiB,KAAK,kDAAkD;CACpF;CAEA,MAAM,UAAW,MAAM,SAAS,KAAK;CAMrC,IAAI,CAAC,OAAO,cAAc,QAAQ,EAAE,KAAK,CAAC,QAAQ,OAChD,MAAM,IAAI,iBAAiB,KAAK,0CAA0C;CAG5E,MAAM,MAAM,KAAK,IAAI;CACrB,MAAM,YAAY,OAAO,QAAQ,qBAAqB;CACtD,MAAM,OAA+B;EACnC,UAAU;EACV,SAAS,OAAO,QAAQ,EAAE;EAC1B,OAAO,QAAQ;EACf,GAAI,QAAQ,OAAO,EAAE,MAAM,QAAQ,KAAK,IAAI,CAAC;EAC7C,GAAI,QAAQ,aAAa,EAAE,WAAW,QAAQ,WAAW,IAAI,CAAC;CAChE;CAQA,OAAO;EACL,OAAO,kBAAkB;GAPzB,MAAM;GACN,GAAG;GACH,UAAU;GACV;EAI8B,GAAG,QAAQ,aAAa;EACtD;EACA;CACF;AACF;AAEA,SAAgB,0BACd,OACA,SACsB;CACtB,MAAM,SAAS,oBAAoB,OAAO,QAAQ,aAAa;CAC/D,IACE,OAAO,SAAS,aAChB,OAAO,aAAa,YACpB,OAAO,OAAO,YAAY,YAC1B,OAAO,OAAO,UAAU,UAExB,MAAM,IAAI,iBAAiB,KAAK,4CAA4C;CAE9E,OAAO;AACT;AAEA,SAAgB,wBACd,SACA,OACA,SACoB;CACpB,2BAA2B,OAAO;CAClC,MAAM,YAAY,wBAAwB,OAAO;CACjD,IACE,MAAM,YAAY,KAAA,MACjB,OAAO,MAAM,YAAY,YAAY,CAAC,4BAA4B,KAAK,MAAM,OAAO,IAErF,MAAM,IAAI,iBAAiB,KAAK,8DAA8D;CAChG,IACE,MAAM,gBAAgB,KAAA,MACrB,CAAC,OAAO,cAAc,MAAM,WAAW,KAAK,MAAM,eAAe,IAElE,MAAM,IAAI,iBAAiB,KAAK,4CAA4C;CAE9E,MAAM,iBAAiB,MAAM,eAAe,UAAU;CACtD,IAAI,QAAQ,YAAY,KAAA,KAAa,QAAQ,aAAa,MAAM,WAAW,MAAM,OAC/E,MAAM,IAAI,iBAAiB,KAAK,wDAAwD;CAC1F,MAAM,QAAQ,KAAK,IAAI,oBAAoB,KAAK,IAAI,UAAU,iBAAiB,cAAc,CAAC;CAC9F,MAAM,WAAW,KAAK,IAAI;CAC1B,MAAM,YAAY,KAAK,IAAI,WAAW,OAAO,QAAQ,SAAS;CAC9D,IAAI,aAAa,UAAU,MAAM,IAAI,iBAAiB,KAAK,qCAAqC;CAahG,OAAO;EACL,OAAO,kBAAkB;GAZzB,MAAM;GACN,UAAU,QAAQ;GAClB,SAAS,QAAQ;GACjB,OAAO,QAAQ;GACf,MAAM,MAAM;GACZ,SAAS,MAAM,WAAW,MAAM;GAChC,GAAI,QAAQ,QAAQ,EAAE,OAAO,QAAQ,MAAM,IAAI,CAAC;GAChD;GACA;GACA,OAAO,WAAW;EAGY,GAAG,QAAQ,aAAa;EACtD,MAAM,MAAM;EACZ;CACF;AACF;AAEA,SAAgB,yBACd,OACA,SAC0B;CAC1B,MAAM,SAAS,oBAAoB,OAAO,QAAQ,aAAa;CAC/D,IACE,OAAO,SAAS,YAChB,OAAO,OAAO,YAAY,YAC1B,OAAO,OAAO,UAAU,YACxB,OAAO,OAAO,SAAS,YACtB,OAAO,UAAU,KAAA,MACf,OAAO,OAAO,UAAU,YAAY,CAAC,iBAAiB,KAAK,OAAO,KAAK,MACzE,OAAO,YAAY,KAAA,MACjB,OAAO,OAAO,YAAY,YAAY,CAAC,4BAA4B,KAAK,OAAO,OAAO,MACxF,OAAO,UAAU,KAAA,MACf,OAAO,OAAO,UAAU,YAAY,CAAC,iBAAiB,KAAK,OAAO,KAAK,MACzE,QAAQ,SAAS,KAAA,KAAa,OAAO,SAAS,QAAQ,MAEvD,MAAM,IAAI,iBAAiB,KAAK,2CAA2C;CAE7E,OAAO;AACT;AAEA,SAAgB,uBAAuB,SAAsC;CAC3E,MAAM,gBAAgB,QAAQ,QAAQ,IAAI,eAAe;CACzD,IAAI,CAAC,iBAAiB,CAAC,YAAY,KAAK,aAAa,GAAG,OAAO,KAAA;CAE/D,OADc,cAAc,MAAM,CAAC,CAAC,CAAC,KAC1B,KAAK,KAAA;AAClB;AAEA,IAAa,mBAAb,cAAsC,MAAM;CAC1C,YACE,QACA,SACA;EACA,MAAM,OAAO;EAHN,KAAA,SAAA;CAIT;AACF;AAEA,SAAS,wBAAwB,SAAoC;CACnE,MAAM,kBAAkB,KAAK,IAC3B,oBACA,QAAQ,mBAAmB,0BAC7B;CACA,OAAO;EACL;EACA,qBAAqB,KAAK,IACxB,iBACA,KAAK,IAAI,oBAAoB,QAAQ,uBAAuBA,wBAAsB,CACpF;CACF;AACF;AAEA,SAAS,2BAA2B,SAAoC;CACtE,IAAI,OAAO,WAAW,QAAQ,aAAa,IAAI,IAC7C,MAAM,IAAI,MAAM,iEAAiE;CAEnF,IAAI,CAAC,QAAQ,kBAAkB,CAAC,QAAQ,YACtC,MAAM,IAAI,MAAM,oEAAoE;CAEtF,IAAI,QAAQ,YAAY;EACtB,sBAAsB,QAAQ,WAAW,MAAM;EAC/C,IAAI,CAAC,QAAQ,WAAW,UACtB,MAAM,IAAI,MAAM,8CAA8C;CAClE;CACA,IAAI,QAAQ,cAAc,sBAAsB,QAAQ,YAAY;CACpE,KAAK,MAAM,CAAC,MAAM,UAAU;EAC1B,CAAC,qBAAqB,QAAQ,iBAAiB;EAC/C,CAAC,uBAAuB,QAAQ,mBAAmB;EACnD,CAAC,mBAAmB,QAAQ,eAAe;CAC7C,GACE,IAAI,UAAU,KAAA,MAAc,CAAC,OAAO,cAAc,KAAK,KAAK,SAAS,IACnE,MAAM,IAAI,MAAM,qBAAqB,KAAK,6BAA6B;AAG7E;AAEA,SAAS,sBAAsB,OAAe;CAC5C,MAAM,MAAM,IAAI,IAAI,KAAK;CACzB,MAAM,QAAQ;EAAC;EAAa;EAAa;CAAO,CAAC,CAAC,SAAS,IAAI,QAAQ;CACvE,IACG,IAAI,aAAa,YAAY,EAAE,SAAS,IAAI,aAAa,YAC1D,IAAI,YACJ,IAAI,YACJ,IAAI,UACJ,IAAI,MAEJ,MAAM,IAAI,MACR,wHACF;AAEJ;AAEA,eAAsB,wBACpB,OACA,SAC+B;CAC/B,IAAI,CAAC,QAAQ,YAAY,OAAO,0BAA0B,OAAO,OAAO;CACxE,IAAI,CAAC,SAAS,MAAM,SAAS,MAAM,MAAM,IAAI,iBAAiB,KAAK,6BAA6B;CAChG,IAAI;CACJ,IAAI;EACF,UAAU,MAAM,QAAQ,WAAW,iBAAiB,KAAK;CAC3D,QAAQ;EACN,MAAM,IAAI,iBAAiB,KAAK,yDAAyD;CAC3F;CACA,MAAM,MAAM,KAAK,IAAI;CACrB,IACE,CAAC,WACD,OAAO,QAAQ,YAAY,YAC3B,CAAC,QAAQ,WACT,OAAO,QAAQ,UAAU,YACzB,CAAC,QAAQ,SACR,QAAQ,YAAY,KAAA,MAClB,OAAO,QAAQ,YAAY,YAC1B,CAAC,4BAA4B,KAAK,QAAQ,OAAO,MACpD,QAAQ,UAAU,KAAA,MAChB,OAAO,QAAQ,UAAU,YAAY,CAAC,iBAAiB,KAAK,QAAQ,KAAK,MAC3E,QAAQ,cAAc,KAAA,MACpB,CAAC,OAAO,cAAc,QAAQ,SAAS,KAAK,QAAQ,aAAa,MAEpE,MAAM,IAAI,iBAAiB,KAAK,kDAAkD;CAEpF,OAAO;EACL,MAAM;EACN,UAAU;EACV,SAAS,QAAQ;EACjB,OAAO,QAAQ;EACf,GAAI,QAAQ,QAAQ,EAAE,OAAO,QAAQ,MAAM,IAAI,CAAC;EAChD,GAAI,QAAQ,UAAU,EAAE,SAAS,QAAQ,QAAQ,IAAI,CAAC;EACtD,UAAU;EACV,WAAW,QAAQ,aAAa,MAAM;CACxC;AACF;AAEA,SAAS,kBAAkB,QAAgB,QAAgB;CACzD,MAAM,UAAU,OAAO,KAAK,KAAK,UAAU,MAAM,CAAC,CAAC,CAAC,SAAS,WAAW;CACxE,MAAM,YAAY,WAAW,UAAU,MAAM,CAAC,CAC3C,OAAO,GAAG,aAAa,GAAG,SAAS,CAAC,CACpC,OAAO,WAAW;CACrB,OAAO,GAAG,aAAa,GAAG,QAAQ,GAAG;AACvC;AAEA,SAAS,oBAAoB,OAAe,QAAyC;CACnF,IAAI,CAAC,SAAS,MAAM,SAAS,OAC3B,MAAM,IAAI,iBAAiB,KAAK,yCAAyC;CAE3E,MAAM,CAAC,QAAQ,SAAS,oBAAoB,GAAG,QAAQ,MAAM,MAAM,GAAG;CACtE,IAAI,WAAW,gBAAgB,CAAC,WAAW,CAAC,sBAAsB,KAAK,QACrE,MAAM,IAAI,iBAAiB,KAAK,yCAAyC;CAG3E,MAAM,oBAAoB,WAAW,UAAU,MAAM,CAAC,CACnD,OAAO,GAAG,aAAa,GAAG,SAAS,CAAC,CACpC,OAAO;CACV,IAAI;CACJ,IAAI;EACF,YAAY,OAAO,KAAK,oBAAoB,WAAW;CACzD,QAAQ;EACN,MAAM,IAAI,iBAAiB,KAAK,yCAAyC;CAC3E;CACA,IACE,UAAU,WAAW,kBAAkB,UACvC,CAAC,gBAAgB,WAAW,iBAAiB,GAE7C,MAAM,IAAI,iBAAiB,KAAK,yCAAyC;CAG3E,IAAI;CACJ,IAAI;EACF,SAAS,KAAK,MAAM,OAAO,KAAK,SAAS,WAAW,CAAC,CAAC,SAAS,MAAM,CAAC;CACxE,QAAQ;EACN,MAAM,IAAI,iBAAiB,KAAK,yCAAyC;CAC3E;CACA,IACE,OAAO,WAAW,YAClB,WAAW,QACX,MAAM,QAAQ,MAAM,KACpB,EAAE,cAAc,WAChB,EAAE,eAAe,WACjB,CAAC,OAAO,cAAc,OAAO,QAAQ,KACrC,CAAC,OAAO,cAAc,OAAO,SAAS,KACtC,OAAO,OAAO,QAAQ,IAAI,KAAK,IAAI,IAAI,qBACvC,OAAO,OAAO,SAAS,KAAK,KAAK,IAAI,GAErC,MAAM,IAAI,iBAAiB,KAAK,wDAAwD;CAE1F,OAAO;AACT;;;ACrQA,SAAS,QAAQ,UAAoD;CACnE,IAAI,CAAC,UAAU;CACf,IAAI;EACF,QAAa,QAAQ,SAAS,CAAC,CAAC,CAAC,YAAY,KAAA,CAAS;CACxD,QAAQ,CAER;AACF;AAEA,SAAS,cACP,QACA,SACA,OACA;CACA,IAAI,CAAC,OAAO,UAAU,SAAS;CAC/B,cACE,OAAO,SAAU,QAAS;EACxB,IAAI,QAAQ;EACZ,SAAS,QAAQ;EACjB,MAAM,QAAQ;EACd,SAAS,QAAQ;EACjB,OAAO,QAAQ;EACf,SAAS,QAAQ;EACjB,WAAW,QAAQ;EACnB,WAAW,QAAQ;EACnB;EACA,IAAI,KAAK,IAAI;CACf,CAAC,CACH;AACF;AAEA,MAAM,iBAAiB;AACvB,MAAM,yBAAyB;AAC/B,MAAM,sCAAsC;AAC5C,MAAM,6BAA6B;AACnC,MAAM,0BAA0B;AAChC,MAAM,2BAA2B;AACjC,MAAM,yBAAyB;AAC/B,MAAM,kCAAkC;AACxC,MAAM,6BAA6B;AACnC,MAAM,qCAAqB,IAAI,IAAI;CACjC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AAED,SAAS,uBAAuB,SAAmE;CACjG,MAAM,QAAQ,IAAI,IAAI,kBAAkB;CACxC,MAAM,aACJ,mBAAmB,UACf,QAAQ,IAAI,YAAY,IACxB,OAAO,QAAQ,OAAO,CAAC,CAAC,MAAM,CAAC,UAAU,KAAK,YAAY,MAAM,YAAY,CAAC,GAAG;CACtF,MAAM,SAAS,MAAM,QAAQ,UAAU,IAAI,aAAa,aAAa,CAAC,UAAU,IAAI,CAAC;CACrF,KAAK,MAAM,SAAS,QAClB,KAAK,MAAM,QAAQ,MAAM,MAAM,GAAG,GAAG;EACnC,MAAM,aAAa,KAAK,KAAK,CAAC,CAAC,YAAY;EAC3C,IAAI,YAAY,MAAM,IAAI,UAAU;CACtC;CAEF,OAAO;AACT;AAiBA,SAAgB,4BACd,UAAiC,CAAC,GACX;CACvB,IAAI,QAAQ,MAAM,2BAA2B,QAAQ,IAAI;CACzD,MAAM,QAAQ,QAAQ,SAAS,iCAAiC;CAChE,MAAM,SAAS;EACb,kBAAkB,QAAQ;EAC1B,UAAU,QAAQ;EAClB,QAAQ,gBAAgB,QAAQ,UAAU,QAAQ,IAAI,uBAAuB,cAAc;EAC3F,SAAS,QAAQ,WAAW,QAAQ,IAAI;EACxC,cAAc,QAAQ,gBAAgB;EACtC,0BACE,QAAQ,4BAA4B;EACtC,kBAAkB,QAAQ,oBAAoB;EAC9C,eAAe,QAAQ,iBAAiB;EACxC,gBAAgB,QAAQ,kBAAkB;EAC1C,cAAc,QAAQ,gBAAgB;EACtC,sBAAsB,QAAQ,wBAAwB;EACtD,MAAM,QAAQ;CAChB;CAmFA,OAAO,OAAO,OAAO,eAjF8B,4BACjD,SACmB;EACnB,IAAI;GACF,MAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;GAE/B,IAAI,QAAQ,WAAW,SAAS,IAAI,aAAa,eAC/C,OAAO,KAAK;IACV,IAAI;IACJ,QAAQ,OAAO;IACf,OAAO,MAAM,YAAY,QAAQ;GACnC,CAAC;GAGH,IAAI,QAAQ,WAAW,SAAS,IAAI,aAAa,oBAC/C,OAAO,KACL,OAAO,OACH,2BAA2B,OAAO,IAAI,IACtC;IACE,SAAS;IACT,aAAa;IACb,qBAAqB,OAAO;IAC5B,iBAAiB,OAAO;GAC1B,CACN;GAGF,IAAI,QAAQ,WAAW,UAAU,IAAI,aAAa,sBAAsB;IACtE,IAAI,CAAC,OAAO,MAAM,OAAO,KAAK,qDAAqD,GAAG;IACtF,MAAM,oBAAoB,MAAM,4BAA4B,SAAS,OAAO,IAAI;IAChF,IAAI,mBAAmB,OAAO;IAC9B,OAAO,MAAM,uBAAuB,SAAS,OAAO,IAAI;GAC1D;GAEA,IAAI,QAAQ,WAAW,UAAU,IAAI,aAAa,sBAAsB;IACtE,IAAI,CAAC,OAAO,MAAM,OAAO,KAAK,qDAAqD,GAAG;IACtF,IAAI,OAAO,KAAK,YAAY;KAC1B,MAAM,UAAU,MAAM,4BAA4B,SAAS,OAAO,IAAI;KACtE,IAAI,SAAS,OAAO;IACtB;IACA,OAAO,MAAM,kBAAkB,SAAS,OAAO,IAAI;GACrD;GAEA,IAAI,QAAQ,WAAW,UAAU,IAAI,aAAa,iBAChD,OAAO,MAAM,cAAc,SAAS,OAAO,MAAM;GAGnD,MAAM,eAAe,kBAAkB,IAAI,QAAQ;GACnD,IAAI,cACF,OAAO,MAAM,mBAAmB,SAAS,KAAK,OAAO,QAAQ,YAAY;GAG3E,MAAM,cAAc,0BAA0B,SAAS,KAAK,OAAO,MAAM;GACzE,IAAI,aACF,OAAO,MAAM,mBAAmB,SAAS,KAAK,OAAO,QAAQ,WAAW;GAG1E,OAAO,KACL;IACE,IAAI;IACJ,SAAS;IACT,eAAe;IACf,QAAQ;GACV,GACA,GACF;EACF,SAAS,OAAO;GACd,IAAI,iBAAiB,oBAAoB,iBAAiB,kBACxD,OAAO,KAAK,MAAM,SAAS,MAAM,MAAM;GAEzC,OAAO,KACL,iBAAiB,QAAQ,MAAM,UAAU,qCACzC,GACF;EACF;CACF,GAMkD,EAChD,MAAM,qBAAqB,MAAc;EACvC,MAAM,UAAU,oBAAoB,IAAI;EACxC,IAAI,CAAC,SAAS,OAAO;EACrB,MAAM,UAAU,MAAM,MAAM,iBAAiB,OAAO;EACpD,OAAO,QAAQ,WAAW,sBAAsB,SAAS,MAAM,CAAC;CAClE,EACF,CAAC;AACH;AAEA,eAAe,4BAA4B,SAAkB,MAAiC;CAC5F,IAAI,CAAC,KAAK,mBAAmB,OAAO,KAAA;CACpC,IAAI;CACJ,IAAI;EACF,SAAS,MAAM,KAAK,kBAAkB,OAAO;CAC/C,QAAQ;EACN,OAAO,IAAI,SAAS,qEAAqE;GACvF,QAAQ;GACR,SAAS;IACP,iBAAiB;IACjB,gBAAgB;IAChB,eAAe;GACjB;EACF,CAAC;CACH;CACA,IAAI,OAAO,SAAS,OAAO,KAAA;CAE3B,MAAM,eACJ,OAAO,SAAS,OAAO,YAAY,MAAM,OAAO,gBAAgB,KAAK,IACjE,OAAO,eACP;CACN,OAAO,IAAI,SAAS,4DAA4D;EAC9E,QAAQ;EACR,SAAS;GACP,iBAAiB;GACjB,gBAAgB;GAChB,eAAe,OAAO,KAAK,IAAI,GAAG,KAAK,KAAK,eAAe,GAAI,CAAC,CAAC;EACnE;CACF,CAAC;AACH;AAEA,eAAe,uBAAuB,SAAkB,MAAiC;CACvF,MAAM,QAAS,MAAM,QAAQ,KAAK,CAAC,CAAC,aAAa,CAAC,EAAE;CAIpD,IAAI,MAAM,aAAa,UACrB,MAAM,IAAI,iBAAiB,KAAK,iDAAiD;CAEnF,OAAO,KAAK,MAAM,2BAA2B,MAAM,eAAe,IAAI,IAAI,CAAC;AAC7E;AAEA,eAAe,kBAAkB,SAAkB,MAAiC;CAClF,MAAM,eAAe,uBAAuB,OAAO;CACnD,IAAI,CAAC,cACH,MAAM,IAAI,iBAAiB,KAAK,2DAA2D;CAE7F,MAAM,UAAU,MAAM,wBAAwB,cAAc,IAAI;CAChE,MAAM,QAAS,MAAM,QAAQ,KAAK,CAAC,CAAC,aAAa,CAAC,EAAE;CAKpD,MAAM,OAAO,oBAAoB,MAAM,IAAI;CAC3C,IAAI,CAAC,MAAM,MAAM,IAAI,iBAAiB,KAAK,mCAAmC;CAE9E,OAAO,KACL,wBACE,SACA;EACE;EACA,SAAS,MAAM;EACf,GAAI,MAAM,gBAAgB,KAAA,IAAY,EAAE,aAAa,MAAM,YAAY,IAAI,CAAC;CAC9E,GACA,IACF,CACF;AACF;AAEA,SAAgB,gCACd,UAAiC,CAAC,GACP;CAC3B,MAAM,UAAU,4BAA4B,OAAO;CAsCnD,OAAO,OAAO,OAAO,eApC4B,0BAC/C,KACA,KACA;EACA,MAAM,aAAa,IAAI,gBAAgB;EACvC,MAAM,cAAc,WAAW,MAAM;EACrC,MAAM,qBAAqB;GACzB,IAAI,CAAC,IAAI,eAAe,MAAM;EAChC;EACA,IAAI,KAAK,WAAW,KAAK;EACzB,IAAI,KAAK,SAAS,YAAY;EAC9B,IAAI;GACF,MAAM,WAAW,MAAM,QAAQ,wBAAwB,KAAK,WAAW,MAAM,CAAC;GAC9E,IAAI,WAAW,OAAO,WAAW,IAAI,WAAW;IAC9C,MAAM,SAAS,MAAM,OAAO;IAC5B;GACF;GACA,IAAI,aAAa,SAAS;GAI1B,MAAM,aAAa,SAAS,QAAQ,eAAe,KAAK,CAAC;GACzD,SAAS,QAAQ,SAAS,OAAO,QAAQ;IACvC,IAAI,IAAI,YAAY,MAAM,cAAc;IACxC,IAAI,UAAU,KAAK,KAAK;GAC1B,CAAC;GACD,IAAI,WAAW,SAAS,GACtB,IAAI,UAAU,cAAc,UAAU;GAExC,IAAI,IAAI,OAAO,KAAK,MAAM,SAAS,YAAY,CAAC,CAAC;EACnD,UAAU;GACR,IAAI,IAAI,WAAW,KAAK;GACxB,IAAI,IAAI,SAAS,YAAY;EAC/B;CACF,GAEgD,EAC9C,sBAAsB,QAAQ,qBAChC,CAAC;AACH;AAEA,SAAgB,mCAAwD;CACtE,MAAM,WAAW,QAAQ,IAAI,0BAA0B,QAAQ,IAAI;CACnE,MAAM,aAAa,QAAQ,IAAI,4BAA4B,QAAQ,IAAI;CAEvE,IAAI,YAAY,YACd,OAAO,IAAI,6BAA6B;EACtC,KAAK;EACL,OAAO;CACT,CAAC;CAGH,OAAO,IAAI,0BAA0B;AACvC;AAEA,IAAa,4BAAb,MAAsE;;EACjD,KAAA,2BAAA,IAAI,IAAmC;EAC1C,KAAA,wBAAA,IAAI,IAAoB;EACvB,KAAA,yBAAA,IAAI,IAAqC;EACtC,KAAA,4BAAA,IAAI,IAAoC;;CAE5D,MAAM,cAAc,SAA+C;EACjE,KAAK,SAAS,IAAI,QAAQ,IAAI,OAAO;EACrC,KAAK,MAAM,IAAI,QAAQ,MAAM,QAAQ,EAAE;EACvC,KAAK,OAAO,IAAI,QAAQ,IAAI,CAAC,CAAC;CAChC;CAEA,MAAM,iBAAiB,MAA0D;EAC/E,MAAM,KAAK,KAAK,MAAM,IAAI,IAAI;EAC9B,OAAO,KAAK,KAAK,eAAe,EAAE,IAAI,KAAA;CACxC;CAEA,MAAM,eAAe,IAAwD;EAC3E,MAAM,UAAU,KAAK,SAAS,IAAI,EAAE;EACpC,IAAI,CAAC,SAAS,OAAO,KAAA;EACrB,IAAI,QAAQ,aAAa,KAAK,IAAI,GAAG;GACnC,MAAM,KAAK,cAAc,OAAO;GAChC;EACF;EACA,OAAO;CACT;CAEA,MAAM,aAAa,SAAgC,QAA+B;EAIhF,IAAI,CAAC,KAAK,SAAS,IAAI,QAAQ,EAAE,GAAG;EACpC,KAAK,SAAS,IAAI,QAAQ,IAAI,OAAO;EACrC,MAAM,YAAY,KAAK,MAAM,IAAI,QAAQ,IAAI;EAC7C,IAAI,CAAC,aAAa,cAAc,QAAQ,IACtC,KAAK,MAAM,IAAI,QAAQ,MAAM,QAAQ,EAAE;CAE3C;CAEA,MAAM,eAAe,WAAmB,SAA+C;EACrF,MAAM,QAAQ,KAAK,OAAO,IAAI,SAAS,KAAK,CAAC;EAC7C,MAAM,KAAK,OAAO;EAClB,KAAK,OAAO,IAAI,WAAW,KAAK;CAClC;CAEA,MAAM,aAAa,WAAmB,OAAiD;EAErF,QADc,KAAK,OAAO,IAAI,SAAS,KAAK,CAAC,EAAA,CAChC,OAAO,GAAG,KAAK;CAC9B;CAEA,MAAM,aACJ,WACA,WACA,UACe;EACf,KAAK,UAAU,IAAI,YAAY,WAAW,SAAS,GAAG,QAAQ;CAChE;CAEA,MAAM,YACJ,WACA,WAC6C;EAC7C,OAAO,KAAK,UAAU,IAAI,YAAY,WAAW,SAAS,CAAC;CAC7D;CAEA,MAAM,eAAe,WAAmB,WAAkC;EACxE,KAAK,UAAU,OAAO,YAAY,WAAW,SAAS,CAAC;CACzD;CAEA,MAAM,cAAc,SAA+C;EACjE,KAAK,SAAS,OAAO,QAAQ,EAAE;EAG/B,IAAI,KAAK,MAAM,IAAI,QAAQ,IAAI,MAAM,QAAQ,IAC3C,KAAK,MAAM,OAAO,QAAQ,IAAI;EAEhC,KAAK,OAAO,OAAO,QAAQ,EAAE;CAC/B;AACF;AAEA,IAAa,+BAAb,MAAyE;CACvE,YAAY,SAAkE;EAA1D,KAAA,UAAA;CAA2D;CAE/E,MAAM,cAAc,SAAgC,OAA8B;EAChF,MAAM,KAAK,QAAQ,OAAO,KAAK,WAAW,QAAQ,EAAE,GAAG,KAAK,UAAU,OAAO,GAAG,MAAM,KAAK;EAC3F,MAAM,KAAK,QAAQ,OAAO,KAAK,QAAQ,QAAQ,IAAI,GAAG,QAAQ,IAAI,MAAM,KAAK;EAC7E,MAAM,KAAK,QAAQ,OAAO,KAAK,SAAS,QAAQ,EAAE,CAAC;CACrD;CAEA,MAAM,iBAAiB,MAA0D;EAC/E,MAAM,KAAK,MAAM,KAAK,QAAuB,OAAO,KAAK,QAAQ,IAAI,CAAC;EACtE,OAAO,KAAK,KAAK,eAAe,EAAE,IAAI,KAAA;CACxC;CAEA,MAAM,eAAe,IAAwD;EAC3E,MAAM,QAAQ,MAAM,KAAK,QAAuB,OAAO,KAAK,WAAW,EAAE,CAAC;EAC1E,OAAO,QAAQ,KAAK,MAAM,KAAK,IAAI,KAAA;CACrC;CAEA,MAAM,aAAa,SAAgC,OAA8B;EAC/E,MAAM,cAAc,EAAE,GAAG,QAAQ;EACjC,MAAM,iBAAiB,KAAK,IAAI,GAAG,KAAK,IAAI,OAAO,QAAQ,YAAY,KAAK,IAAI,CAAC,CAAC;EAYlF,IAAI,MARiB,KAAK,QACxB,OACA,KAAK,WAAW,QAAQ,EAAE,GAC1B,KAAK,UAAU,WAAW,GAC1B,MACA,gBACA,IACF,MACe,MAAM;EACrB,MAAM,YAAY,MAAM,KAAK,QAAuB,OAAO,KAAK,QAAQ,QAAQ,IAAI,CAAC;EACrF,IAAI,CAAC,aAAa,cAAc,QAAQ,IACtC,MAAM,KAAK,QAAQ,OAAO,KAAK,QAAQ,QAAQ,IAAI,GAAG,QAAQ,IAAI,MAAM,cAAc;EAExF,MAAM,KAAK,QAAQ,UAAU,KAAK,SAAS,QAAQ,EAAE,GAAG,KAAK,KAAK,iBAAiB,GAAI,CAAC;CAC1F;CAEA,MAAM,eACJ,WACA,SACA,OACe;EACf,MAAM,KAAK,QAAQ,SAAS,KAAK,SAAS,SAAS,GAAG,KAAK,UAAU,OAAO,CAAC;EAC7E,MAAM,KAAK,QAAQ,UAAU,KAAK,SAAS,SAAS,GAAG,KAAK,KAAK,QAAQ,GAAI,CAAC;CAChF;CAEA,MAAM,aAAa,WAAmB,OAAiD;EACrF,MAAM,SAAS,MAAM,KAAK,QACxB,QACA,KAAK,SAAS,SAAS,GACvB,KACF;EACA,IAAI,CAAC,QAAQ,OAAO,CAAC;EAErB,QADa,MAAM,QAAQ,MAAM,IAAI,SAAS,CAAC,MAAM,EAAA,CACzC,KAAK,UAAU,KAAK,MAAM,KAAK,CAAC;CAC9C;CAEA,MAAM,aACJ,WACA,WACA,UACA,OACe;EACf,MAAM,KAAK,QACT,OACA,KAAK,YAAY,WAAW,SAAS,GACrC,KAAK,UAAU,QAAQ,GACvB,MACA,KACF;CACF;CAEA,MAAM,YACJ,WACA,WAC6C;EAC7C,MAAM,QAAQ,MAAM,KAAK,QAAuB,OAAO,KAAK,YAAY,WAAW,SAAS,CAAC;EAC7F,OAAO,QAAQ,KAAK,MAAM,KAAK,IAAI,KAAA;CACrC;CAEA,MAAM,eAAe,WAAmB,WAAkC;EACxE,MAAM,KAAK,QAAQ,OAAO,KAAK,YAAY,WAAW,SAAS,CAAC;CAClE;CAEA,MAAM,cAAc,SAA+C;EACjE,MAAM,OAAO,CAAC,KAAK,WAAW,QAAQ,EAAE,GAAG,KAAK,SAAS,QAAQ,EAAE,CAAC;EAIpE,IAAI,MADoB,KAAK,QAAuB,OAAO,KAAK,QAAQ,QAAQ,IAAI,CAAC,MACnE,QAAQ,IACxB,KAAK,KAAK,KAAK,QAAQ,QAAQ,IAAI,CAAC;EAEtC,MAAM,KAAK,QAAQ,OAAO,GAAG,IAAI;CACnC;CAEA,MAAc,QAAqB,GAAG,MAA8B;EAClE,MAAM,WAAW,MAAM,MAAM,KAAK,QAAQ,KAAK;GAC7C,QAAQ;GACR,SAAS;IACP,eAAe,UAAU,KAAK,QAAQ;IACtC,gBAAgB;GAClB;GACA,MAAM,KAAK,UAAU,IAAI;EAC3B,CAAC;EAED,IAAI,CAAC,SAAS,IACZ,MAAM,IAAI,MACR,yCAAyC,SAAS,OAAO,KAAK,MAAM,SAAS,KAAK,GACpF;EAGF,MAAM,OAAQ,MAAM,SAAS,KAAK;EAClC,IAAI,KAAK,OACP,MAAM,IAAI,MAAM,yCAAyC,KAAK,OAAO;EAEvE,OAAO,KAAK;CACd;CAEA,IAAY,OAAe;EACzB,OAAO,GAAG,KAAK,QAAQ,UAAU,eAAe,GAAG;CACrD;CAEA,WAAmB,IAAY;EAC7B,OAAO,KAAK,IAAI,WAAW,IAAI;CACjC;CAEA,QAAgB,MAAc;EAC5B,OAAO,KAAK,IAAI,QAAQ,MAAM;CAChC;CAEA,SAAiB,IAAY;EAC3B,OAAO,KAAK,IAAI,SAAS,IAAI;CAC/B;CAEA,YAAoB,WAAmB,WAAmB;EACxD,OAAO,KAAK,IAAI,YAAY,UAAU,GAAG,WAAW;CACtD;AACF;AAEA,eAAe,cACb,SACA,OACA,QACA;CACA,MAAM,QAAS,MAAM,QAAQ,KAAK,CAAC,CAAC,aAAa,CAAC,EAAE;CAKpD,MAAM,gBAAgB,oBAAoB,MAAM,IAAI;CACpD,IAAI,OAAO,iBAAiB,kBAAkB;CAC9C,IAAI,YAAY,KAAK,IAAI,IAAI,OAAO;CACpC,IAAI,oBAAoB;CACxB,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CAEJ,IAAI,OAAO,MAAM;EACf,MAAM,aAAa,uBAAuB,OAAO;EACjD,IAAI,CAAC,YACH,MAAM,IAAI,iBAAiB,KAAK,sDAAsD;EAExF,IAAI;GACF,MAAM,QAAQ,yBAAyB,YAAY;IACjD,eAAe,OAAO,KAAK;IAC3B,GAAI,gBAAgB,EAAE,MAAM,cAAc,IAAI,CAAC;GACjD,CAAC;GACD,OAAO,MAAM;GACb,UAAU,GAAG,MAAM,YAAY,SAAS,GAAG,MAAM;GACjD,UAAU,MAAM,WAAW,MAAM;GACjC,QAAQ,MAAM;GACd,UAAU,MAAM;GAChB,YAAY,MAAM;GAClB,oBAAoB;EACtB,SAAS,OAAO;GACd,IAAI,iBAAiB,kBACnB,MAAM,IAAI,iBAAiB,MAAM,QAAQ,MAAM,OAAO;GACxD,MAAM;EACR;CACF,OAAO,IAAI,OAAO,SAAS,MAAM,WAAW,GAAG;EAC7C,YAAY,KAAK,IAAI,IAAI,MAAM,OAAO,MAAM,WAAW,GAAG,KAAQ,OAAO,YAAY;EACrF,oBAAoB;CACtB;CAMA,KAAK,IAAI,UAAU,IAAK,WAAW;EACjC,MAAM,WAAW,MAAM,MAAM,iBAAiB,IAAI;EAClD,IAAI,CAAC,YAAY,CAAC,sBAAsB,UAAU,MAAM,GACtD;EAEF,IAAI,iBAAiB,OAAO,MAC1B,OAAO,KACL,yBAAyB,KAAK,wEAC9B,GACF;EAEF,IAAI,WAAW,GACb,OAAO,KAAK,wDAAwD,GAAG;EAEzE,OAAO,kBAAkB;CAC3B;CAEA,MAAM,WAAW,GAAG,KAAK,GAAG,OAAO;CACnC,MAAM,UAAiC;EACrC,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC;EAC7B,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC;EAC7B,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;EACzB,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC;EAC7B,IAAI,SAAS,MAAM;EACnB;EACA;EACA,WAAW,gBAAgB,SAAS,QAAQ,IAAI;EAChD,OAAO,SAAS,OAAO;EACvB,UAAU,MAAM;EAChB,WAAW,KAAK,IAAI;EACpB;EACA,GAAI,oBAAoB,EAAE,mBAAmB,KAAc,IAAI,CAAC;EAChE,iBAAiB,KAAK,IAAI;CAC5B;CAEA,MAAM,qBAAqB,QAAQ,OAAO;CAC1C,MAAM,MAAM,cAAc,SAAS,KAAK,IAAI,GAAG,YAAY,KAAK,IAAI,CAAC,CAAC;CACtE,cAAc,QAAQ,SAAS,WAAW;CAE1C,OAAO,KAAK;EACV,IAAI,QAAQ;EACZ,MAAM,QAAQ;EACd,OAAO,QAAQ;EACf,WAAW,QAAQ;EACnB,GAAI,QAAQ,oBAAoB,CAAC,IAAI,EAAE,WAAW,QAAQ,UAAU;CACtE,CAAC;AACH;AAEA,eAAe,mBACb,SACA,KACA,OACA,QACA,OACA;CACA,MAAM,UAAU,MAAM,eACpB,OACA,MAAM,WACN,uBAAuB,OAAO,KAAK,IAAI,aAAa,IAAI,OAAO,CACjE;CAGA,IAAI,QAAQ,WAAW,YAAY,MAAM,QAAQ,MAAM,qBAAqB,QAAQ,OAAO;CAE3F,IAAI,QAAQ,WAAW,SAAS,MAAM,WAAW,YAC/C,OAAO,MAAM,oBAAoB,KAAK,OAAO,QAAQ,OAAO;CAG9D,IAAI,QAAQ,WAAW,UAAU,MAAM,WAAW,eAAe,MAAM,WAAW;EAChF,IAAI;EACJ,IAAI;GACF,WAAW,MAAM,qBAAqB,SAAS,OAAO,oBAAoB;EAC5E,SAAS,OAAO;GACd,IAAI,iBAAiB,oBAAoB,MAAM,WAAW,KACxD,MAAM,MAAM,aACV,QAAQ,IACR,MAAM,WACN,+BAA+B,OAAO,oBAAoB,GAC1D,oBAAoB,SAAS,MAAM,CACrC;GAEF,MAAM;EACR;EACA,MAAM,MAAM,aACV,QAAQ,IACR,MAAM,WACN,UACA,oBAAoB,SAAS,MAAM,CACrC;EACA,MAAM,kBAAkB,OAAO,QAAQ,OAAO;EAC9C,OAAO,KAAK,EAAE,IAAI,KAAK,CAAC;CAC1B;CAEA,IAAI,QAAQ,WAAW,UAAU,MAAM,WAAW,aAAa;EAC7D,MAAM,kBAAkB,OAAO,QAAQ,OAAO;EAC9C,OAAO,KAAK;GACV,IAAI;GACJ,GAAI,QAAQ,oBAAoB,CAAC,IAAI,EAAE,WAAW,QAAQ,UAAU;EACtE,CAAC;CACH;CAEA,IAAI,QAAQ,WAAW,YAAY,CAAC,MAAM,QAAQ;EAChD,MAAM,MAAM,cAAc,OAAO;EACjC,cAAc,QAAQ,SAAS,cAAc;EAC7C,OAAO,KAAK,EAAE,IAAI,KAAK,CAAC;CAC1B;CAEA,OAAO,KAAK,oCAAoC,GAAG;AACrD;AAEA,eAAe,qBAAqB,SAAkB,cAAsB;CAC1E,MAAM,kBAAkB,KAAK,KAAK,eAAe,CAAC,IAAI,IAAI;CAC1D,MAAM,gBAAgB,OAAO,QAAQ,QAAQ,IAAI,gBAAgB,KAAK,CAAC;CACvE,IAAI,OAAO,SAAS,aAAa,KAAK,gBAAgB,iBAAiB;EACrE,MAAM,QAAQ,MAAM,OAAO;EAC3B,MAAM,IAAI,iBAAiB,KAAK,qCAAqC;CACvE;CAEA,MAAM,QAAQ,MAAM,kBAClB,SACA,iBACA,qCACF;CACA,IAAI;CACJ,IAAI;EACF,WAAW,KAAK,MAAM,MAAM,SAAS,MAAM,CAAC;CAC9C,QAAQ;EACN,MAAM,IAAI,iBAAiB,KAAK,2CAA2C;CAC7E;CAEA,IAAI,2BAA2B,QAAQ,IAAI,cACzC,MAAM,IAAI,iBAAiB,KAAK,qCAAqC;CAEvE,OAAO;AACT;AAEA,SAAS,2BAA2B,UAAkC;CACpE,IAAI,CAAC,SAAS,MAAM,OAAO;CAC3B,OAAO,OAAO,WAAW,SAAS,MAAM,SAAS,aAAa,WAAW,WAAW,MAAM;AAC5F;AAEA,SAAS,+BAA+B,cAA8C;CACpF,OAAO;EACL,QAAQ;EACR,SAAS,EAAE,gBAAgB,4BAA4B;EACvD,MAAM,OAAO,KACX,2CAA2C,aAAa,aAC1D,CAAC,CAAC,SAAS,QAAQ;EACnB,UAAU;CACZ;AACF;AAEA,eAAe,oBACb,KACA,OACA,QACA,SACA;CACA,MAAM,SAAS,MAAM,OAAO,IAAI,aAAa,IAAI,MAAM,KAAK,OAAO,aAAa,GAAG,KAAM,IAAK;CAC9F,MAAM,WAAW,KAAK,IAAI,IAAI;CAC9B,IAAI,oBAAoB,KAAK,IAAI,IAAI;CAErC,MAAM,kBAAkB,OAAO,QAAQ,OAAO;CAE9C,OAAO,KAAK,IAAI,IAAI,UAAU;EAC5B,IAAI,KAAK,IAAI,KAAK,mBAAmB;GACnC,MAAM,qBAAqB,QAAQ,OAAO;GAC1C,oBAAoB,KAAK,IAAI,IAAI;EACnC;EACA,MAAM,WAAW,MAAM,MAAM,aAAa,QAAQ,IAAI,0BAA0B;EAChF,IAAI,SAAS,QAAQ;GACnB,MAAM,qBAAqB,QAAQ,OAAO;GAC1C,MAAM,kBAAkB,OAAO,QAAQ,OAAO;GAC9C,OAAO,KAAK,EAAE,SAAS,CAAC;EAC1B;EACA,MAAMC,WAAM,OAAO,cAAc;CACnC;CAEA,MAAM,kBAAkB,OAAO,QAAQ,OAAO;CAC9C,OAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,IAAI,CAAC;AAC3C;AAEA,eAAe,mBACb,SACA,KACA,OACA,QACA,OACA;CACA,MAAM,UAAU,MAAM,MAAM,iBAAiB,MAAM,IAAI;CACvD,IAAI,CAAC,SACH,OAAO,wBAAwB,SAAS,MAAM,IAAI;CAEpD,IAAI,CAAC,sBAAsB,SAAS,MAAM,GAAG;EAC3C,MAAM,MAAM,cAAc,OAAO;EACjC,cAAc,QAAQ,SAAS,cAAc;EAC7C,OAAO,wBAAwB,SAAS,MAAM,IAAI;CACpD;CAEA,MAAM,qBAAqB,QAAQ,OAAO;CAE1C,MAAM,YAAY,KAAK,IAAI;CAC3B,IAAI,SAAS;CACb,IAAI;EACF,MAAM,iBAAiB,MAAM,wBAC3B,SACA,KACA,MAAM,MACN,OAAO,YACT;EACA,MAAM,MAAM,eAAe,QAAQ,IAAI,gBAAgB,oBAAoB,SAAS,MAAM,CAAC;EAC3F,MAAM,MAAM,aAAa,SAAS,oBAAoB,SAAS,MAAM,CAAC;EAEtE,MAAM,WAAW,MAAM,uBACrB,OACA,QACA,SACA,eAAe,IACf,QAAQ,MACV;EACA,IAAI,aAAa,aAAa;GAC5B,SAAS;GACT,MAAM,MAAM,eACV,QAAQ,IACR;IACE,GAAG;IACH,MAAM,KAAA;IACN,UAAU,KAAA;IACV,WAAW;IACX,WAAW,KAAK,IAAI;GACtB,GACA,oBAAoB,SAAS,MAAM,CACrC;GACA,OAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,IAAI,CAAC;EAC3C;EACA,IAAI,aAAa,SAAS;GACxB,SAAS;GACT,OAAO,wBAAwB,SAAS,MAAM,IAAI;EACpD;EACA,IAAI,CAAC,UAAU;GACb,SAAS;GACT,OAAO,KAAK,wEAAwE,GAAG;EACzF;EAEA,MAAM,UAAU,IAAI,QAAQ;EAC5B,MAAM,0BAA0B,uBAAuB,SAAS,WAAW,CAAC,CAAC;EAC7E,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,SAAS,WAAW,CAAC,CAAC,GAAG;GACjE,MAAM,aAAa,IAAI,YAAY;GACnC,IAAI,wBAAwB,IAAI,UAAU,KAAK,eAAe,oBAC5D;GAIF,KAAK,MAAM,UAAU,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK,GACxD,QAAQ,OAAO,KAAK,MAAM;EAE9B;EACA,QAAQ,IAAI,iBAAiB,UAAU;EACvC,QAAQ,IAAI,kBAAkB,QAAQ,IAAI;EAE1C,SAAS,SAAS,UAAU;EAC5B,OAAO,IAAI,SACT,SAAS,OACL,OAAO,KAAK,SAAS,MAAM,SAAS,aAAa,WAAW,WAAW,MAAM,IAC7E,MACJ;GACE;GACA;EACF,CACF;CACF,SAAS,OAAO;EACd,IAAI,iBAAiB,kBAAkB,SAAS,MAAM;EACtD,MAAM;CACR,UAAU;EACR,IAAI,OAAO,UAAU,SACnB,cACE,OAAO,SAAU,QAAS;GACxB,WAAW,QAAQ;GACnB,QAAQ,QAAQ;GAChB,MAAM,MAAM,KAAK,MAAM,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,GAAG,IAAI;GAClD;GACA,YAAY,KAAK,IAAI,IAAI;GACzB,IAAI,KAAK,IAAI;EACf,CAAC,CACH;CACJ;AACF;AAEA,eAAe,uBACb,OACA,QACA,SACA,WACA,QACA;CACA,MAAM,WAAW,KAAK,IAAI,IAAI,OAAO;CACrC,IAAI,sBAAsB;CAE1B,OAAO,KAAK,IAAI,IAAI,UAAU;EAC5B,IAAI,OAAO,SAAS,OAAO;EAC3B,IAAI,KAAK,IAAI,KAAK,qBAAqB;GACrC,MAAM,qBAAqB,QAAQ,OAAO;GAC1C,MAAM,gBAAgB,MAAM,MAAM,eAAe,QAAQ,EAAE;GAC3D,IAAI,CAAC,iBAAiB,CAAC,sBAAsB,eAAe,MAAM,GAAG;IACnE,IAAI,eAAe,MAAM,MAAM,cAAc,aAAa;IAC1D,OAAO;GACT;GACA,sBAAsB,KAAK,IAAI,IAAI;EACrC;EAEA,MAAM,WAAW,MAAM,MAAM,YAAY,QAAQ,IAAI,SAAS;EAC9D,IAAI,UAAU;GACZ,MAAM,qBAAqB,QAAQ,OAAO;GAC1C,MAAM,MAAM,eAAe,QAAQ,IAAI,SAAS;GAChD,OAAO;EACT;EACA,IAAI;GACF,MAAMA,WAAM,OAAO,gBAAgB,KAAA,GAAW,EAAE,OAAO,CAAC;EAC1D,QAAQ;GACN,IAAI,OAAO,SAAS,OAAO;GAC3B,MAAM,IAAI,MAAM,2CAA2C;EAC7D;CACF;AAGF;AAEA,eAAe,qBACb,QACA,SACA;CACA,IAAI,CAAC,OAAO,kBAAkB;CAC9B,MAAM,EAAE,IAAI,MAAM,SAAS,SAAS,OAAO,SAAS,cAAc;CAClE,IAAI;CACJ,IAAI;EACF,UAAU,MAAM,OAAO,iBAAiB;GACtC;GACA;GACA;GACA;GACA;GACA;GACA;EACF,CAAC;CACH,QAAQ;EACN,MAAM,IAAI,iBAAiB,KAAK,yDAAyD;CAC3F;CACA,IAAI,YAAY,MAAM,MAAM,IAAI,iBAAiB,KAAK,uCAAuC;AAC/F;AAEA,eAAe,kBACb,OACA,QACA,SACA;CACA,MAAM,MAAM,KAAK,IAAI;CACrB,QAAQ,kBAAkB;CAC1B,IAAI,QAAQ,mBACV,QAAQ,YAAY,MAAM,OAAO;CAEnC,MAAM,MAAM,aAAa,SAAS,oBAAoB,SAAS,MAAM,CAAC;CACtE,cAAc,QAAQ,SAAS,WAAW;AAC5C;AAEA,SAAS,oBAAoB,SAAgC,QAAqC;CAChG,OAAO,KAAK,IAAI,GAAG,KAAK,IAAI,OAAO,cAAc,QAAQ,YAAY,KAAK,IAAI,CAAC,CAAC;AAClF;AAEA,SAAS,sBACP,SACA,QACA;CACA,MAAM,kBAAkB,QAAQ,mBAAmB,QAAQ;CAC3D,OACE,QAAQ,YAAY,KAAK,IAAI,KAC7B,KAAK,IAAI,IAAI,mBAAmB,OAAO;AAE3C;AAEA,eAAe,wBACb,SACA,KACA,WACA,cACgC;CAChC,MAAM,SAAS,QAAQ,OAAO,YAAY;CAC1C,MAAM,UAAkC,CAAC;CACzC,MAAM,yBAAyB,uBAAuB,QAAQ,OAAO;CACrE,QAAQ,QAAQ,SAAS,OAAO,QAAQ;EACtC,MAAM,aAAa,IAAI,YAAY;EACnC,IACE,CAAC,uBAAuB,IAAI,UAAU,KACtC,eAAe,eACf,CAAC,WAAW,WAAW,cAAc,GAErC,QAAQ,OAAO;CAEnB,CAAC;CAED,QAAQ,sBAAsB,QAAQ,QAAQ,IAAI,MAAM,KAAK,IAAI;CACjE,QAAQ,uBAAuB,IAAI,SAAS,QAAQ,KAAK,EAAE;CAE3D,IAAI;CACJ,IAAI,WAAW,SAAS,WAAW,QAAQ;EAEzC,IADsB,OAAO,QAAQ,QAAQ,IAAI,gBAAgB,KAAK,CACtD,IAAI,cAClB,MAAM,IAAI,iBAAiB,KAAK,oCAAoC;EAQtE,QAAO,MALc,kBACnB,SACA,cACA,oCACF,EAAA,CACc,SAAS,QAAQ;CACjC;CAEA,OAAO;EACL,IAAI,SAAS,KAAK;EAClB;EACA,MAAM,GAAG,YAAY,IAAI;EACzB;EACA;EACA,UAAU,OAAO,WAAW,KAAA;EAC5B,WAAW,KAAK,IAAI;CACtB;AACF;AAEA,eAAe,kBAAkB,SAAkB,UAAkB,SAAiB;CACpF,IAAI,CAAC,QAAQ,MAAM,OAAO,OAAO,MAAM,CAAC;CACxC,MAAM,SAAS,QAAQ,KAAK,UAAU;CACtC,MAAM,SAAuB,CAAC;CAC9B,IAAI,OAAO;CACX,IAAI;EACF,OAAO,MAAM;GACX,MAAM,EAAE,MAAM,UAAU,MAAM,OAAO,KAAK;GAC1C,IAAI,MAAM;GACV,QAAQ,MAAM;GACd,IAAI,OAAO,UAAU;IACnB,MAAM,OAAO,OAAO;IACpB,MAAM,IAAI,iBAAiB,KAAK,OAAO;GACzC;GACA,OAAO,KAAK,KAAK;EACnB;CACF,SAAS,OAAO;EACd,MAAM,OAAO,OAAO,CAAC,CAAC,YAAY,KAAA,CAAS;EAC3C,MAAM;CACR;CACA,OAAO,OAAO,OAAO,QAAQ,IAAI;AACnC;AAEA,eAAe,eAAe,OAA4B,WAAmB,OAAsB;CACjG,MAAM,UAAU,MAAM,MAAM,eAAe,SAAS;CACpD,IAAI,CAAC,WAAW,CAAC,SAAS,UAAU,QAAQ,OAC1C,MAAM,IAAI,iBAAiB,KAAK,kCAAkC;CAEpE,OAAO;AACT;AAEA,SAAS,0BAA0B,SAAkB,KAAU,QAAgB;CAC7E,MAAM,YAAY,IAAI,SAAS,MAAM,+BAA+B;CACpE,IAAI,WACF,OAAO;EACL,MAAM,UAAU;EAChB,MAAM,UAAU,MAAM;CACxB;CAGF,MAAM,QAAQ,QAAQ,QAAQ,IAAI,MAAM,KAAK,IAAI,KAAA,CAAM,MAAM,GAAG,CAAC,CAAC;CAClE,IAAI,CAAC,QAAQ,SAAS,UAAU,CAAC,KAAK,SAAS,IAAI,QAAQ,GACzD;CAGF,OAAO;EACL,MAAM,KAAK,MAAM,GAAG,EAAE,OAAO,SAAS,EAAE;EACxC,MAAM,IAAI,YAAY;CACxB;AACF;AAEA,SAAS,kBAAkB,UAAkB;CAC3C,MAAM,QAAQ,SAAS,MACrB,+EACF;CACA,IAAI,CAAC,OAAO,OAAO,KAAA;CACnB,OAAO;EACL,WAAW,MAAM;EACjB,QAAQ,MAAM;EACd,WAAW,MAAM;CACnB;AACF;AAEA,SAAS,wBAAwB,KAAsB,QAAsB;CAC3E,MAAM,OAAO,YAAY,IAAI,SAAS,MAAM,KAAK;CAIjD,MAAM,MAAM,GADK,YAAY,KAAK,MAAM,GAAG,CAAC,CAAC,EAAE,IAAI,SAAS,QACpC,KAAK,OAAO,IAAI,OAAO;CAC/C,MAAM,UAAU,wBAAwB,IAAI,OAAO;CACnD,MAAM,SAAS,IAAI,UAAU;CAC7B,MAAM,OAA0C;EAC9C;EACA;EACA;CACF;CAEA,IAAI,WAAW,SAAS,WAAW,QAAQ;EACzC,KAAK,OAAO;EACZ,KAAK,SAAS;CAChB;CAEA,OAAO,IAAI,QAAQ,KAAK,IAAI;AAC9B;AAEA,SAAS,wBAAwB,SAA8B;CAC7D,MAAM,OAAO,IAAI,QAAQ;CACzB,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,GAAG;EAClD,IAAI,UAAU,KAAA,GAAW;EACzB,IAAI,MAAM,QAAQ,KAAK,GACrB,KAAK,MAAM,QAAQ,OAAO,KAAK,OAAO,KAAK,IAAI;OAE/C,KAAK,IAAI,KAAK,KAAK;CAEvB;CACA,OAAO;AACT;AAEA,SAAS,YAAY,SAA8B,KAAa;CAC9D,MAAM,QAAQ,QAAQ;CACtB,OAAO,MAAM,QAAQ,KAAK,IAAI,MAAM,KAAK;AAC3C;AAEA,SAAS,gBAAgB,SAAkB,QAAqC,MAAc;CAG5F,IAAI,OAAO,WAAW,eAAe,OAAO,OAAO,SAAS,YAAY,GAAG;EACzE,MAAM,OAAO,IAAI,IAAI,OAAO,WAAW,QAAQ,GAAG;EAClD,KAAK,WAAW,GAAG,KAAK,GAAG,OAAO;EAClC,OAAO,KAAK;CACd;CACA,IAAI,OAAO,SAEL;MAAA,YAAY,IADC,IAAI,OAAO,OACT,CAAC,CAAC,QAAQ,GAC3B,OAAO,GAAG,OAAO,QAAQ,QAAQ,QAAQ,EAAE,EAAE,aAAa;CAAA;CAK9D,IAAI,aADS,QAAQ,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,QAAQ,GAAG,CAAC,CAAC,KAAA,CAC5C,MAAM,GAAG,CAAC,CAAC,EAAE,GAChC,OAAO,GAAG,IAAI,IAAI,QAAQ,GAAG,CAAC,CAAC,OAAO,aAAa;CAGrD,OAAO,WAAW,KAAK,GAAG,OAAO;AACnC;AAEA,SAAS,KAAK,OAAgB,SAAS,KAAK;CAC1C,OAAO,IAAI,SAAS,KAAK,UAAU,KAAK,GAAG;EACzC;EACA,SAAS;GACP,gBAAgB;GAChB,iBAAiB;EACnB;CACF,CAAC;AACH;AAEA,SAAS,KAAK,OAAe,SAAS,KAAK;CACzC,OAAO,IAAI,SAAS,OAAO;EACzB;EACA,SAAS;GACP,gBAAgB;GAChB,iBAAiB;EACnB;CACF,CAAC;AACH;AAEA,SAAS,wBAAwB,SAAkB,MAAc;CAC/D,IAAI,CAAC,QAAQ,QAAQ,IAAI,QAAQ,CAAC,EAAE,SAAS,WAAW,GACtD,OAAO,KAAK,0CAA0C,KAAK,KAAK,GAAG;CAErE,MAAM,WAAW,KAAK,QAAQ,aAAa,cAAc;EACvD,IAAI,cAAc,KAAK,OAAO;EAC9B,IAAI,cAAc,KAAK,OAAO;EAC9B,IAAI,cAAc,KAAK,OAAO;EAC9B,IAAI,cAAc,MAAK,OAAO;EAC9B,OAAO;CACT,CAAC;CACD,OAAO,IAAI,SACT,ugBAAugB,SAAS,gHAChhB;EACE,QAAQ;EACR,SAAS;GACP,gBAAgB;GAChB,iBAAiB;EACnB;CACF,CACF;AACF;AAEA,SAAS,oBAAoB,OAA2B;CACtD,IAAI,CAAC,OAAO,OAAO,KAAA;CACnB,OAAO,MACJ,YAAY,CAAC,CACb,QAAQ,gBAAgB,GAAG,CAAC,CAC5B,QAAQ,YAAY,EAAE,CAAC,CACvB,MAAM,GAAG,EAAE;AAChB;AAEA,SAAS,oBAAoB;CAC3B,OAAO,QAAQ,KAAK,OAAO,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC;AACtD;AAEA,SAAS,SAAS,QAAgB;CAChC,OAAO,GAAG,OAAO,GAAG,OAAO,WAAW,CAAC,CAAC,QAAQ,MAAM,EAAE;AAC1D;AAEA,SAAS,gBAAgB,OAAe;CACtC,OAAO,MACJ,QAAQ,gBAAgB,EAAE,CAAC,CAC3B,QAAQ,QAAQ,EAAE,CAAC,CACnB,QAAQ,QAAQ,EAAE;AACvB;AAEA,SAAS,YAAY,WAAmB,WAAmB;CACzD,OAAO,GAAG,UAAU,GAAG;AACzB;AAEA,SAAS,MAAM,OAAe,KAAa,KAAa;CACtD,IAAI,CAAC,OAAO,SAAS,KAAK,GAAG,OAAO;CACpC,OAAO,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,KAAK,CAAC;AAC3C;AAEA,SAAS,YAAY,UAAkB;CACrC,OACE,aAAa,eACb,SAAS,SAAS,YAAY,KAC9B,aAAa,eACb,aAAa;AAEjB;AAEA,IAAM,mBAAN,cAA+B,MAAM;CACnC,YACE,QACA,SACA;EACA,MAAM,OAAO;EAHN,KAAA,SAAA;CAIT;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farm.js/preview-gateway",
3
- "version": "0.1.1-beta.2",
3
+ "version": "0.1.1",
4
4
  "description": "Farm.js preview gateway for Vercel-hosted instant preview URLs",
5
5
  "license": "MIT",
6
6
  "repository": {