@jant/core 0.3.50 → 0.4.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.
Files changed (45) hide show
  1. package/dist/{app-C7CtIQM-.js → app-DQgkp6yV.js} +155 -130
  2. package/dist/app-DYJLFZaM.js +6 -0
  3. package/dist/client/.vite/manifest.json +3 -3
  4. package/dist/client/_assets/client-BbJ0FhON.css +2 -0
  5. package/dist/client/_assets/client-DqsPJKiP.js +272 -0
  6. package/dist/client/_assets/{client-auth-Ce5WEAVS.js → client-auth-N6fiJcOg.js} +82 -82
  7. package/dist/{export-ZBlfKSKm.js → export-DwH3ga3Y.js} +2 -2
  8. package/dist/{github-sync-C593r22F.js → github-sync-D2FO19Re.js} +2 -2
  9. package/dist/{github-sync-bL1hnx3Q.js → github-sync-eHOTYZGO.js} +1 -1
  10. package/dist/index.js +3 -3
  11. package/dist/node.js +4 -4
  12. package/package.json +1 -1
  13. package/src/client/__tests__/compose-shortcuts.test.ts +1 -4
  14. package/src/client/components/__tests__/jant-compose-dialog.test.ts +1 -1
  15. package/src/client/components/__tests__/jant-media-lightbox.test.ts +89 -0
  16. package/src/client/components/compose-types.ts +6 -1
  17. package/src/client/components/jant-compose-dialog.ts +2 -0
  18. package/src/client/components/jant-compose-editor.ts +2 -1
  19. package/src/client/components/jant-media-lightbox.ts +33 -10
  20. package/src/client/compose-bridge.ts +88 -25
  21. package/src/client/compose-launch.ts +0 -13
  22. package/src/client/palette-search-trigger.ts +35 -0
  23. package/src/client/thread-context.ts +1 -140
  24. package/src/client/upload-session.ts +77 -31
  25. package/src/client-auth.ts +1 -0
  26. package/src/i18n/locales/public/en.po +0 -4
  27. package/src/i18n/locales/public/zh-Hans.po +0 -4
  28. package/src/i18n/locales/public/zh-Hant.po +0 -4
  29. package/src/lib/__tests__/hosted-domain.test.ts +1 -1
  30. package/src/lib/hosted-domain-check.ts +21 -80
  31. package/src/lib/hosted-domain.ts +1 -1
  32. package/src/routes/api/internal/__tests__/sites.test.ts +168 -0
  33. package/src/routes/api/internal/sites.ts +63 -10
  34. package/src/routes/hosted/__tests__/domain-check.test.ts +30 -19
  35. package/src/routes/hosted/domain-check.ts +9 -14
  36. package/src/services/export-theme/assets/client-site.js +1 -1
  37. package/src/services/site-admin.ts +62 -1
  38. package/src/styles/tokens.css +0 -1
  39. package/src/styles/ui.css +0 -71
  40. package/src/ui/feed/ThreadPreview.tsx +34 -65
  41. package/src/ui/feed/__tests__/thread-preview.test.ts +64 -58
  42. package/src/ui/feed/thread-preview-state.ts +0 -48
  43. package/dist/app-CIx9SSOi.js +0 -6
  44. package/dist/client/_assets/client-BoUn7xBo.css +0 -2
  45. package/dist/client/_assets/client-dSfWfMe9.js +0 -272
@@ -1,7 +1,7 @@
1
1
  import { a as getSitePathPrefix, c as normalizePath, d as sanitizeUrl, f as slugify, g as toPublicPath, h as toPublicHref, i as getSiteOrigin, m as toAbsoluteSiteUrl, n as extractDisplayDomain, o as isFullUrl, p as stripSitePathPrefix, r as extractDomain, s as isSafeInternalRedirect, t as buildSiteUrl, u as normalizeSiteUrl, v as __exportAll } from "./url-umUptr5z.js";
2
- import { A as JANT_POSITIVE_LOGO_PNG_FILENAME, B as getJantLogoHref, C as formatYearMonth, D as HOME_BRANDING_LINK_LABEL, E as toISOString, F as getJantBundledAsset, G as base64ToUint8Array, H as JANT_LOGO_PATH_DATA, I as getJantIconFilename, L as getJantIconHref, M as getDefaultJantAppleTouchIconBytes, N as getDefaultJantFaviconIcoBytes, O as HOME_BRANDING_PREFIX, P as getJantBrandPackHref, R as getJantLogoFilename, S as formatTime, U as JANT_LOGO_VIEW_BOX, V as getJantPositiveLogoPngHref, W as arrayBufferToBase64, _ as getMediaUrl, b as formatRelativeAge, d as extractSummaryHtml, f as renderTiptapDocument, g as getImageUrl, h as escapeHtml, i as tiptapJsonToMarkdown, j as JANT_REPO_URL, k as JANT_BRAND_PACK_FILENAME, l as extractBodyText, m as trimTiptapBody, o as render, p as renderTiptapJson, s as toPlainText, t as createExportService, u as extractSummary, v as getPublicUrlForProvider, w as now, x as formatRelativeTime, y as formatDate, z as getJantLogoFills } from "./export-ZBlfKSKm.js";
2
+ import { A as JANT_POSITIVE_LOGO_PNG_FILENAME, B as getJantLogoHref, C as formatYearMonth, D as HOME_BRANDING_LINK_LABEL, E as toISOString, F as getJantBundledAsset, G as base64ToUint8Array, H as JANT_LOGO_PATH_DATA, I as getJantIconFilename, L as getJantIconHref, M as getDefaultJantAppleTouchIconBytes, N as getDefaultJantFaviconIcoBytes, O as HOME_BRANDING_PREFIX, P as getJantBrandPackHref, R as getJantLogoFilename, S as formatTime, U as JANT_LOGO_VIEW_BOX, V as getJantPositiveLogoPngHref, W as arrayBufferToBase64, _ as getMediaUrl, b as formatRelativeAge, d as extractSummaryHtml, f as renderTiptapDocument, g as getImageUrl, h as escapeHtml, i as tiptapJsonToMarkdown, j as JANT_REPO_URL, k as JANT_BRAND_PACK_FILENAME, l as extractBodyText, m as trimTiptapBody, o as render, p as renderTiptapJson, s as toPlainText, t as createExportService, u as extractSummary, v as getPublicUrlForProvider, w as now, x as formatRelativeTime, y as formatDate, z as getJantLogoFills } from "./export-DwH3ga3Y.js";
3
3
  import { C as coalesceDisplayText, S as shouldUseSecureCookies, _ as getInternalAdminToken, a as getConfiguredSingleSiteUrl, b as getSiteResolutionMode, c as getDevApiToken, d as getHostedControlPlaneBaseUrl, f as getHostedControlPlaneDomainCheckSecret, g as getHostedControlPlaneSsoSecret, h as getHostedControlPlaneProviderLabel$1, i as getConfiguredSingleSitePathPrefix, l as getEnvString, m as getHostedControlPlaneInternalToken, n as getAuthSecret, o as getConfiguredStorageDriver, p as getHostedControlPlaneInternalBaseUrl, r as getConfiguredSingleSiteOrigin, s as getCorsOrigins, u as getGitHubAppConfig, v as getLocalStoragePath } from "./env-CgaH9Mut.js";
4
- import { l as markdownToTiptapJson, o as createGitHubSyncService } from "./github-sync-bL1hnx3Q.js";
4
+ import { l as markdownToTiptapJson, o as createGitHubSyncService } from "./github-sync-eHOTYZGO.js";
5
5
  import { a as listInstallationReposPage, n as getInstallation, o as searchInstallationRepos, t as buildInstallUrl } from "./github-app-D0GvNnqp.js";
6
6
  import { r as parseRepoSlug, t as createGitHubClient } from "./github-api-Bh0PH3zr.js";
7
7
  import { I18n } from "@lingui/core";
@@ -3418,10 +3418,10 @@ function normalizeThemeColorForMeta(color) {
3418
3418
  * internal paths (e.g. `/_assets/client-HASH.js`) embedded by the Worker build
3419
3419
  * from the Vite client manifest. Used only in production (IS_VITE_DEV=false).
3420
3420
  */ var IS_VITE_DEV = typeof __JANT_DEV__ !== "undefined" && __JANT_DEV__ === true;
3421
- var CORE_VERSION = "0.3.50-947a76e8ff575c8d";
3422
- var CLIENT_JS_FILE = "/_assets/client-dSfWfMe9.js";
3423
- var CLIENT_AUTH_JS_FILE = "/_assets/client-auth-Ce5WEAVS.js";
3424
- var CLIENT_CSS_FILE = "/_assets/client-BoUn7xBo.css";
3421
+ var CORE_VERSION = "0.4.1-ec7ca04d562a5649";
3422
+ var CLIENT_JS_FILE = "/_assets/client-DqsPJKiP.js";
3423
+ var CLIENT_AUTH_JS_FILE = "/_assets/client-auth-N6fiJcOg.js";
3424
+ var CLIENT_CSS_FILE = "/_assets/client-BbJ0FhON.css";
3425
3425
  var CLIENT_CJK_CSS_FILE = "/_assets/client-cjk-B7Z0snDu.css";
3426
3426
  var CLIENT_CJK_TC_CSS_FILE = "/_assets/client-cjk-tc-BesJYrb2.css";
3427
3427
  var CLIENT_CJK_JP_CSS_FILE = "/_assets/client-cjk-jp-DZwrTzQC.css";
@@ -3739,7 +3739,7 @@ var IconSprite = () => {
3739
3739
  const cjkSerifFont = appConfig?.cjkSerifFont ?? "off";
3740
3740
  const cjkStylesheetPath = cjkSerifFont === "zh-Hans" ? IS_VITE_DEV ? assetPath("/src/style-cjk.css") : toPublicAssetPath(CLIENT_CJK_CSS_FILE, assetBasePath) : cjkSerifFont === "zh-Hant" ? IS_VITE_DEV ? assetPath("/src/style-cjk-tc.css") : toPublicAssetPath(CLIENT_CJK_TC_CSS_FILE, assetBasePath) : cjkSerifFont === "ja" ? IS_VITE_DEV ? assetPath("/src/style-cjk-jp.css") : toPublicAssetPath(CLIENT_CJK_JP_CSS_FILE, assetBasePath) : cjkSerifFont === "ko" ? IS_VITE_DEV ? assetPath("/src/style-cjk-kr.css") : toPublicAssetPath(CLIENT_CJK_KR_CSS_FILE, assetBasePath) : null;
3741
3741
  const clientScriptPath = IS_VITE_DEV ? resolvedClientBundle === "full" ? assetPath("/src/client-auth.ts") : assetPath("/src/client.ts") : toPublicAssetPath(resolvedClientBundle === "full" ? CLIENT_AUTH_JS_FILE : CLIENT_JS_FILE, assetBasePath);
3742
- const faviconAssetVersion = resolvedFaviconVersion || "0.3.50-947a76e8ff575c8d";
3742
+ const faviconAssetVersion = resolvedFaviconVersion || "0.4.1-ec7ca04d562a5649";
3743
3743
  const resolvedFaviconHref = faviconHref ?? (faviconAssetVersion ? toPublicPath(`/favicon.ico?v=${faviconAssetVersion}`, sitePathPrefix) : toPublicPath("/favicon.ico", sitePathPrefix));
3744
3744
  const resolvedAppleTouchHref = appleTouchHref ?? (faviconAssetVersion ? toPublicPath(`/apple-touch-icon.png?v=${faviconAssetVersion}`, sitePathPrefix) : toPublicPath("/apple-touch-icon.png", sitePathPrefix));
3745
3745
  const socialImageHref = resolvedSocialImagePath && (isFullUrl(resolvedSocialImagePath) || resolvedSocialImagePath.startsWith("//") ? resolvedSocialImagePath : toAbsoluteSiteUrl(resolvedSocialImagePath, appConfig?.siteUrl || "", sitePathPrefix));
@@ -6397,83 +6397,44 @@ hostedSsoRoutes.get("/__sso", async (c) => {
6397
6397
  }
6398
6398
  });
6399
6399
  //#endregion
6400
- //#region src/lib/crypto.ts
6401
- /**
6402
- * Compare two byte arrays using a constant-time loop.
6403
- *
6404
- * This avoids relying on runtime-specific crypto helpers so the same behavior
6405
- * works in Node.js and Workers.
6406
- *
6407
- * @param a - First byte array
6408
- * @param b - Second byte array
6409
- * @returns `true` when the inputs are byte-for-byte identical
6410
- *
6411
- * @example
6412
- * ```ts
6413
- * const isEqual = timingSafeEqualBytes(
6414
- * new TextEncoder().encode("abc"),
6415
- * new TextEncoder().encode("abc"),
6416
- * );
6417
- * ```
6418
- */ function timingSafeEqualBytes(a, b) {
6419
- if (a.byteLength !== b.byteLength) return false;
6420
- let mismatch = 0;
6421
- for (let index = 0; index < a.byteLength; index += 1) mismatch |= (a[index] ?? 0) ^ (b[index] ?? 0);
6422
- return mismatch === 0;
6423
- }
6424
- /**
6425
- * Compare two strings using a constant-time byte comparison.
6426
- *
6427
- * @param a - First string
6428
- * @param b - Second string
6429
- * @returns `true` when the UTF-8 byte sequences are identical
6430
- *
6431
- * @example
6432
- * ```ts
6433
- * const isEqual = timingSafeEqualText("token-a", "token-b");
6434
- * ```
6435
- */ function timingSafeEqualText(a, b) {
6436
- const encoder = new TextEncoder();
6437
- return timingSafeEqualBytes(encoder.encode(a), encoder.encode(b));
6438
- }
6439
- //#endregion
6440
6400
  //#region src/lib/hosted-domain-check.ts
6401
+ var VERIFICATION_HMAC_VERSION = "v1";
6441
6402
  var textEncoder$3 = new TextEncoder();
6442
- new TextDecoder();
6443
- function toBase64Url$1(bytes) {
6444
- let binary = "";
6445
- for (const byte of bytes) binary += String.fromCharCode(byte);
6446
- return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
6403
+ function bytesToHex$1(bytes) {
6404
+ let out = "";
6405
+ for (const byte of bytes) out += byte.toString(16).padStart(2, "0");
6406
+ return out;
6447
6407
  }
6448
- async function createHmacSignature$1(secret, payload) {
6408
+ /**
6409
+ * Compute the plaintext HMAC token returned from
6410
+ * `/.well-known/jant-verification`.
6411
+ *
6412
+ * The control plane sends a nonce in the query string; the site replies with
6413
+ * `jant-verification=<hex>` where `<hex>` is `HMAC-SHA256(secret, payload)`
6414
+ * over `payload = "v1:" + host + ":" + nonce`. The shared secret is
6415
+ * `HOSTED_CONTROL_PLANE_DOMAIN_CHECK_SECRET`.
6416
+ */ async function computeHostedVerificationToken(secret, host, nonce) {
6449
6417
  const key = await crypto.subtle.importKey("raw", textEncoder$3.encode(secret), {
6450
6418
  name: "HMAC",
6451
6419
  hash: "SHA-256"
6452
6420
  }, false, ["sign"]);
6453
- return new Uint8Array(await crypto.subtle.sign("HMAC", key, textEncoder$3.encode(payload)));
6454
- }
6455
- async function signHostedDomainCheckToken(secret, claims) {
6456
- const payload = toBase64Url$1(textEncoder$3.encode(JSON.stringify(claims)));
6457
- return `${payload}.${toBase64Url$1(await createHmacSignature$1(secret, payload))}`;
6421
+ const payload = `${VERIFICATION_HMAC_VERSION}:${host.trim().toLowerCase()}:${nonce}`;
6422
+ return bytesToHex$1(new Uint8Array(await crypto.subtle.sign("HMAC", key, textEncoder$3.encode(payload))));
6458
6423
  }
6459
6424
  //#endregion
6460
6425
  //#region src/routes/hosted/domain-check.ts
6461
6426
  var hostedDomainCheckRoutes = new Hono();
6462
- hostedDomainCheckRoutes.get("/.well-known/jant-domain-check", async (c) => {
6427
+ hostedDomainCheckRoutes.get("/.well-known/jant-verification", async (c) => {
6463
6428
  const secret = getHostedControlPlaneDomainCheckSecret(c.env);
6464
- if (!secret) throw new NotFoundError("Hosted domain check endpoint");
6465
- if (!c.var.currentSiteDomain) throw new NotFoundError("Hosted domain check endpoint");
6429
+ if (!secret) throw new NotFoundError("Hosted domain verification endpoint");
6430
+ if (!c.var.currentSiteDomain) throw new NotFoundError("Hosted domain verification endpoint");
6466
6431
  const nonce = c.req.query("nonce")?.trim();
6467
- if (!nonce) return c.json({ error: "Missing nonce." }, 400);
6468
- const token = await signHostedDomainCheckToken(secret, {
6469
- aud: "jant-cloud",
6470
- domainId: c.var.currentSiteDomain.id,
6471
- host: c.var.currentSiteDomain.host.trim().toLowerCase(),
6472
- iat: Math.floor(Date.now() / 1e3),
6473
- iss: "jant-core",
6474
- nonce
6475
- });
6476
- return c.json({ token }, 200, { "Cache-Control": "no-store" });
6432
+ if (!nonce) return c.text("Missing nonce.", 400);
6433
+ const token = await computeHostedVerificationToken(secret, c.var.currentSiteDomain.host.trim().toLowerCase(), nonce);
6434
+ return c.text(`jant-verification=${token}\n`, 200, {
6435
+ "Cache-Control": "no-store",
6436
+ "Content-Type": "text/plain; charset=utf-8"
6437
+ });
6477
6438
  });
6478
6439
  //#endregion
6479
6440
  //#region src/lib/collection-paths.ts
@@ -9935,7 +9896,7 @@ function getThreadPreviewState({ secondReply, penultimateReply, latestReply, tot
9935
9896
  /**
9936
9897
  * Thread Preview
9937
9898
  *
9938
- * Shows latest reply as the hero post with faded ancestor context above.
9899
+ * Shows latest reply as the hero post with ancestor context above.
9939
9900
  * Thread line connects all posts via `.thread-group` / `.thread-item`.
9940
9901
  */ var ROOT_CONTEXT_DISPLAY = { footer: { hideReply: true } };
9941
9902
  var CONTEXT_DISPLAY = {
@@ -9945,8 +9906,6 @@ var CONTEXT_DISPLAY = {
9945
9906
  var HERO_DISPLAY = {};
9946
9907
  var ThreadPreview = ({ rootPost, secondReply, penultimateReply, latestReply, totalReplyCount }) => {
9947
9908
  const { i18n } = useLingui();
9948
- const showMoreLabel = i18n._({ id: "fMPkxb" });
9949
- const showLessLabel = i18n._({ id: "6lGV3K" });
9950
9909
  const { hiddenCount } = getThreadPreviewState({
9951
9910
  secondReply,
9952
9911
  penultimateReply,
@@ -9956,56 +9915,41 @@ var ThreadPreview = ({ rootPost, secondReply, penultimateReply, latestReply, tot
9956
9915
  const hiddenPostsLabel = i18n._({ id: "oO0hKx" }, { count: hiddenCount });
9957
9916
  const renderedSecondReply = secondReply && secondReply.id !== latestReply.id ? secondReply : void 0;
9958
9917
  const renderedPenultimateReply = penultimateReply && penultimateReply.id !== latestReply.id && penultimateReply.id !== secondReply?.id ? penultimateReply : void 0;
9918
+ const gapHref = renderedSecondReply?.permalink ?? latestReply.permalink;
9959
9919
  return /* @__PURE__ */ jsxDEV$1("div", {
9960
9920
  class: "thread-group thread-group-preview",
9961
9921
  children: [
9962
9922
  /* @__PURE__ */ jsxDEV$1("div", {
9963
- class: "thread-context-shell thread-context-collapsed",
9964
- "data-thread-context": true,
9965
- children: [
9966
- /* @__PURE__ */ jsxDEV$1("div", {
9967
- class: "thread-item thread-item-context",
9968
- children: /* @__PURE__ */ jsxDEV$1(TimelineItemFromPost, {
9969
- post: rootPost,
9970
- mode: "feed",
9971
- display: ROOT_CONTEXT_DISPLAY
9972
- })
9973
- }),
9974
- renderedSecondReply && /* @__PURE__ */ jsxDEV$1("div", {
9975
- class: "thread-item thread-item-context",
9976
- children: /* @__PURE__ */ jsxDEV$1(TimelineItemFromPost, {
9977
- post: renderedSecondReply,
9978
- mode: "feed",
9979
- display: CONTEXT_DISPLAY
9980
- })
9981
- }),
9982
- hiddenCount > 0 && /* @__PURE__ */ jsxDEV$1("div", {
9983
- class: "thread-item thread-item-gap",
9984
- children: /* @__PURE__ */ jsxDEV$1("a", {
9985
- href: latestReply.permalink,
9986
- class: "thread-gap-link",
9987
- children: hiddenPostsLabel
9988
- })
9989
- }),
9990
- renderedPenultimateReply && /* @__PURE__ */ jsxDEV$1("div", {
9991
- class: "thread-item thread-item-context",
9992
- children: /* @__PURE__ */ jsxDEV$1(TimelineItemFromPost, {
9993
- post: renderedPenultimateReply,
9994
- mode: "feed",
9995
- display: CONTEXT_DISPLAY
9996
- })
9997
- }),
9998
- /* @__PURE__ */ jsxDEV$1("div", { class: "thread-context-fade" })
9999
- ]
9923
+ class: "thread-item thread-item-context",
9924
+ children: /* @__PURE__ */ jsxDEV$1(TimelineItemFromPost, {
9925
+ post: rootPost,
9926
+ mode: "feed",
9927
+ display: ROOT_CONTEXT_DISPLAY
9928
+ })
10000
9929
  }),
10001
- /* @__PURE__ */ jsxDEV$1("button", {
10002
- type: "button",
10003
- class: "thread-context-toggle text-muted-foreground hover:text-foreground hidden",
10004
- "data-thread-context-toggle": true,
10005
- "data-label-more": showMoreLabel,
10006
- "data-label-less": showLessLabel,
10007
- "aria-expanded": "false",
10008
- children: showMoreLabel
9930
+ renderedSecondReply && /* @__PURE__ */ jsxDEV$1("div", {
9931
+ class: "thread-item thread-item-context",
9932
+ children: /* @__PURE__ */ jsxDEV$1(TimelineItemFromPost, {
9933
+ post: renderedSecondReply,
9934
+ mode: "feed",
9935
+ display: CONTEXT_DISPLAY
9936
+ })
9937
+ }),
9938
+ hiddenCount > 0 && /* @__PURE__ */ jsxDEV$1("div", {
9939
+ class: "thread-item thread-item-gap",
9940
+ children: /* @__PURE__ */ jsxDEV$1("a", {
9941
+ href: gapHref,
9942
+ class: "thread-gap-link",
9943
+ children: hiddenPostsLabel
9944
+ })
9945
+ }),
9946
+ renderedPenultimateReply && /* @__PURE__ */ jsxDEV$1("div", {
9947
+ class: "thread-item thread-item-context",
9948
+ children: /* @__PURE__ */ jsxDEV$1(TimelineItemFromPost, {
9949
+ post: renderedPenultimateReply,
9950
+ mode: "feed",
9951
+ display: CONTEXT_DISPLAY
9952
+ })
10009
9953
  }),
10010
9954
  /* @__PURE__ */ jsxDEV$1("div", {
10011
9955
  class: "thread-item thread-item-hero",
@@ -20233,7 +20177,7 @@ async function syncHostedControlPlaneSiteAvatar(input) {
20233
20177
  return;
20234
20178
  }
20235
20179
  await markSyncPending(settings);
20236
- const { createGitHubSyncService } = await import("./github-sync-C593r22F.js");
20180
+ const { createGitHubSyncService } = await import("./github-sync-D2FO19Re.js");
20237
20181
  const { getGitHubAppConfig } = await import("./env-CgaH9Mut.js").then((n) => n.t);
20238
20182
  const run = runBackgroundSync(settings, createGitHubSyncService(c.var.services, c.var.currentSite.id, await buildSyncSiteConfig(c), {
20239
20183
  storage: c.var.storage,
@@ -21020,7 +20964,7 @@ settingsRoutes.post("/github-sync/connect", async (c) => {
21020
20964
  await c.var.services.settings.set("GITHUB_SYNC_AUTH_MODE", "pat");
21021
20965
  await c.var.services.settings.set("GITHUB_SYNC_APP_INSTALLATION_ID", "");
21022
20966
  await c.var.services.settings.set("GITHUB_SYNC_ENABLED", "true");
21023
- const { createGitHubSyncService } = await import("./github-sync-C593r22F.js");
20967
+ const { createGitHubSyncService } = await import("./github-sync-D2FO19Re.js");
21024
20968
  const syncService = createGitHubSyncService(c.var.services, c.var.currentSite.id, await buildSyncSiteConfig(c), {
21025
20969
  storage: c.var.storage,
21026
20970
  githubApp: getGitHubAppConfig(c.env)
@@ -21039,7 +20983,7 @@ settingsRoutes.post("/github-sync/connect", async (c) => {
21039
20983
  return dsRedirect(publicPath(c, "/settings/github-sync"));
21040
20984
  });
21041
20985
  settingsRoutes.post("/github-sync/push", async (c) => {
21042
- const { createGitHubSyncService } = await import("./github-sync-C593r22F.js");
20986
+ const { createGitHubSyncService } = await import("./github-sync-D2FO19Re.js");
21043
20987
  const syncService = createGitHubSyncService(c.var.services, c.var.currentSite.id, await buildSyncSiteConfig(c), {
21044
20988
  storage: c.var.storage,
21045
20989
  githubApp: getGitHubAppConfig(c.env)
@@ -21059,7 +21003,7 @@ settingsRoutes.post("/github-sync/push", async (c) => {
21059
21003
  });
21060
21004
  });
21061
21005
  settingsRoutes.post("/github-sync/disconnect", async (c) => {
21062
- const { createGitHubSyncService } = await import("./github-sync-C593r22F.js");
21006
+ const { createGitHubSyncService } = await import("./github-sync-D2FO19Re.js");
21063
21007
  await createGitHubSyncService(c.var.services, c.var.currentSite.id, await buildSyncSiteConfig(c), { githubApp: getGitHubAppConfig(c.env) }).teardownWebhook();
21064
21008
  return dsRedirect(publicPath(c, "/settings/github-sync"));
21065
21009
  });
@@ -21250,7 +21194,7 @@ function buildRepoPickerLabels(c) {
21250
21194
  const { parseRepoSlug, createGitHubClient } = await import("./github-api-Bh0PH3zr.js").then((n) => n.n);
21251
21195
  const parsed = parseRepoSlug(repo);
21252
21196
  if (!parsed) return wantsJson ? c.json({ error: "Invalid repository format." }, 400) : c.text("Invalid repository format.", 400);
21253
- const { classifyRepoForSync } = await import("./github-sync-C593r22F.js");
21197
+ const { classifyRepoForSync } = await import("./github-sync-D2FO19Re.js");
21254
21198
  const ghClient = createGitHubClient(() => getInstallationTokenFromApp(app, installationId));
21255
21199
  let classification;
21256
21200
  try {
@@ -21280,7 +21224,7 @@ function buildRepoPickerLabels(c) {
21280
21224
  await c.var.services.settings.set("GITHUB_SYNC_REPO", repo);
21281
21225
  await c.var.services.settings.set("GITHUB_SYNC_TOKEN", "");
21282
21226
  await c.var.services.settings.set("GITHUB_SYNC_ENABLED", "true");
21283
- const { createGitHubSyncService } = await import("./github-sync-C593r22F.js");
21227
+ const { createGitHubSyncService } = await import("./github-sync-D2FO19Re.js");
21284
21228
  const syncService = createGitHubSyncService(c.var.services, c.var.currentSite.id, await buildSyncSiteConfig(c), {
21285
21229
  storage: c.var.storage,
21286
21230
  githubApp: app
@@ -21396,7 +21340,7 @@ function requireGitHubApp(c) {
21396
21340
  const { parseRepoSlug, createGitHubClient } = await import("./github-api-Bh0PH3zr.js").then((n) => n.n);
21397
21341
  const parsed = parseRepoSlug(repo);
21398
21342
  if (!parsed) return c.json({ error: "Invalid repository format." }, 400);
21399
- const { classifyRepoForSync } = await import("./github-sync-C593r22F.js");
21343
+ const { classifyRepoForSync } = await import("./github-sync-D2FO19Re.js");
21400
21344
  const client = createGitHubClient(() => getInstallationTokenFromApp(app, installationId));
21401
21345
  try {
21402
21346
  const classification = await classifyRepoForSync(client, parsed.owner, parsed.repo, c.var.currentSite.id);
@@ -24752,8 +24696,10 @@ internalApiTokensRoutes.post("/purge", requireInternalAdminApi(), async (c) => {
24752
24696
  });
24753
24697
  //#endregion
24754
24698
  //#region src/routes/api/internal/sites.ts
24699
+ var ManagedSiteKeySchema = z.string().trim().toLowerCase().min(3).max(40).regex(/^[a-z0-9](?:[a-z0-9-]{1,38}[a-z0-9])?$/, "Site key must use lowercase letters, numbers, or hyphens.");
24700
+ var SiteKeyAvailabilityQuerySchema = z.object({ key: ManagedSiteKeySchema });
24755
24701
  var CreateManagedSiteSchema = z.object({
24756
- key: z.string().trim().toLowerCase().min(3).max(40).regex(/^[a-z0-9](?:[a-z0-9-]{1,38}[a-z0-9])?$/, "Site key must use lowercase letters, numbers, or hyphens."),
24702
+ key: ManagedSiteKeySchema,
24757
24703
  primaryHost: z.string().trim().toLowerCase().min(1).max(255).regex(/^(?=.{1,255}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/, "Primary host must be a valid hostname."),
24758
24704
  siteName: z.string().trim().min(1).max(120),
24759
24705
  siteLanguage: z.string().trim().max(35).optional(),
@@ -24778,6 +24724,12 @@ internalSitesRoutes.post("/", requireInternalAdminApi(), async (c) => {
24778
24724
  status: result.site.status
24779
24725
  }, 201);
24780
24726
  });
24727
+ internalSitesRoutes.get("/availability", requireInternalAdminApi(), async (c) => {
24728
+ assertHostBasedMode(c.env);
24729
+ const query = parseValidated(SiteKeyAvailabilityQuerySchema, { key: c.req.query("key") ?? "" });
24730
+ const result = await c.var.services.siteAdmin.isManagedSiteKeyAvailable(query.key);
24731
+ return c.json(result);
24732
+ });
24781
24733
  internalSitesRoutes.delete("/:siteId", requireInternalAdminApi(), async (c) => {
24782
24734
  assertHostBasedMode(c.env);
24783
24735
  await c.var.services.siteAdmin.deleteManagedSite(c.req.param("siteId"), { storage: c.var.storage });
@@ -24847,6 +24799,18 @@ internalSitesRoutes.post("/:siteId/domains/:domainId/primary", requireInternalAd
24847
24799
  redirectToPrimary: domain.redirectToPrimary
24848
24800
  })) });
24849
24801
  });
24802
+ var ManagedSiteDomainRedirectSchema = z.object({ redirectToPrimary: z.boolean() });
24803
+ internalSitesRoutes.post("/:siteId/domains/:domainId/redirect", requireInternalAdminApi(), async (c) => {
24804
+ assertHostBasedMode(c.env);
24805
+ const body = parseValidated(ManagedSiteDomainRedirectSchema, await c.req.json());
24806
+ const domains = await c.var.services.siteAdmin.setManagedSiteDomainRedirect(c.req.param("siteId"), c.req.param("domainId"), body.redirectToPrimary);
24807
+ return c.json({ domains: domains.map((domain) => ({
24808
+ host: domain.host,
24809
+ id: domain.id,
24810
+ kind: domain.kind,
24811
+ redirectToPrimary: domain.redirectToPrimary
24812
+ })) });
24813
+ });
24850
24814
  internalSitesRoutes.delete("/:siteId/domains/:domainId", requireInternalAdminApi(), async (c) => {
24851
24815
  assertHostBasedMode(c.env);
24852
24816
  const domains = await c.var.services.siteAdmin.deleteManagedSiteDomain(c.req.param("siteId"), c.req.param("domainId"));
@@ -26616,7 +26580,7 @@ var cors = (options) => {
26616
26580
  //#endregion
26617
26581
  //#region src/lib/hosted-domain.ts
26618
26582
  var CANONICAL_REDIRECT_BYPASS_PREFIXES = [
26619
- "/.well-known/jant-domain-check",
26583
+ "/.well-known/jant-verification",
26620
26584
  "/__dev",
26621
26585
  "/__sso",
26622
26586
  "/api/",
@@ -26963,6 +26927,46 @@ function createD1RateLimiter(db, schema, now = () => Math.floor(Date.now() / 1e3
26963
26927
  } };
26964
26928
  }
26965
26929
  //#endregion
26930
+ //#region src/lib/crypto.ts
26931
+ /**
26932
+ * Compare two byte arrays using a constant-time loop.
26933
+ *
26934
+ * This avoids relying on runtime-specific crypto helpers so the same behavior
26935
+ * works in Node.js and Workers.
26936
+ *
26937
+ * @param a - First byte array
26938
+ * @param b - Second byte array
26939
+ * @returns `true` when the inputs are byte-for-byte identical
26940
+ *
26941
+ * @example
26942
+ * ```ts
26943
+ * const isEqual = timingSafeEqualBytes(
26944
+ * new TextEncoder().encode("abc"),
26945
+ * new TextEncoder().encode("abc"),
26946
+ * );
26947
+ * ```
26948
+ */ function timingSafeEqualBytes(a, b) {
26949
+ if (a.byteLength !== b.byteLength) return false;
26950
+ let mismatch = 0;
26951
+ for (let index = 0; index < a.byteLength; index += 1) mismatch |= (a[index] ?? 0) ^ (b[index] ?? 0);
26952
+ return mismatch === 0;
26953
+ }
26954
+ /**
26955
+ * Compare two strings using a constant-time byte comparison.
26956
+ *
26957
+ * @param a - First string
26958
+ * @param b - Second string
26959
+ * @returns `true` when the UTF-8 byte sequences are identical
26960
+ *
26961
+ * @example
26962
+ * ```ts
26963
+ * const isEqual = timingSafeEqualText("token-a", "token-b");
26964
+ * ```
26965
+ */ function timingSafeEqualText(a, b) {
26966
+ const encoder = new TextEncoder();
26967
+ return timingSafeEqualBytes(encoder.encode(a), encoder.encode(b));
26968
+ }
26969
+ //#endregion
26966
26970
  //#region src/lib/hosted-sso.ts
26967
26971
  var textEncoder = new TextEncoder();
26968
26972
  var textDecoder = new TextDecoder();
@@ -30755,6 +30759,14 @@ function createSiteAdminService(db, databaseSchema = sqliteSchemaBundle, databas
30755
30759
  if (supportsDrizzleTransaction(db, databaseDialect)) return db.transaction(async (tx) => createWithDatabase(tx, input));
30756
30760
  return createWithDatabase(db, input);
30757
30761
  },
30762
+ async isManagedSiteKeyAvailable(key) {
30763
+ assertManagedSiteOperationsEnabled();
30764
+ const normalizedKey = key.trim();
30765
+ return {
30766
+ available: !(await db.select({ id: sites.id }).from(sites).where(eq(sites.key, normalizedKey)).limit(1))[0],
30767
+ key: normalizedKey
30768
+ };
30769
+ },
30758
30770
  async getManagedSiteMediaUsage(siteId) {
30759
30771
  assertManagedSiteOperationsEnabled();
30760
30772
  return getManagedSiteMediaUsage(siteId);
@@ -30785,7 +30797,7 @@ function createSiteAdminService(db, databaseSchema = sqliteSchemaBundle, databas
30785
30797
  const themeCss = buildThemeStyle(activeTheme, appConfig.themeMode, fontOverrides);
30786
30798
  const navItemList = await navItems.list();
30787
30799
  const appleTouchKey = allSettings[SETTINGS_KEYS.SITE_FAVICON_APPLE_TOUCH];
30788
- const { createExportService } = await import("./export-ZBlfKSKm.js").then((n) => n.n);
30800
+ const { createExportService } = await import("./export-DwH3ga3Y.js").then((n) => n.n);
30789
30801
  const exportService = createExportService({
30790
30802
  collections,
30791
30803
  media: mediaService,
@@ -30877,7 +30889,7 @@ function createSiteAdminService(db, databaseSchema = sqliteSchemaBundle, databas
30877
30889
  const timestamp = now();
30878
30890
  if (input.makePrimary) await targetDb.update(siteDomains).set({
30879
30891
  kind: "alias",
30880
- redirectToPrimary: true,
30892
+ redirectToPrimary: false,
30881
30893
  updatedAt: timestamp
30882
30894
  }).where(eq(siteDomains.siteId, normalizedSiteId));
30883
30895
  await targetDb.insert(siteDomains).values({
@@ -30892,6 +30904,19 @@ function createSiteAdminService(db, databaseSchema = sqliteSchemaBundle, databas
30892
30904
  });
30893
30905
  });
30894
30906
  },
30907
+ async setManagedSiteDomainRedirect(siteId, domainId, redirectToPrimary) {
30908
+ assertManagedSiteOperationsEnabled();
30909
+ return mutateSiteDomains(siteId, async (targetDb, normalizedSiteId) => {
30910
+ await requireSite(targetDb, normalizedSiteId);
30911
+ const normalizedDomainId = domainId.trim();
30912
+ if (!(await targetDb.select({ id: siteDomains.id }).from(siteDomains).where(sql`${siteDomains.id} = ${normalizedDomainId} AND ${siteDomains.siteId} = ${normalizedSiteId}`).limit(1))[0]) throw new NotFoundError("Site domain");
30913
+ const timestamp = now();
30914
+ await targetDb.update(siteDomains).set({
30915
+ redirectToPrimary,
30916
+ updatedAt: timestamp
30917
+ }).where(eq(siteDomains.id, normalizedDomainId));
30918
+ });
30919
+ },
30895
30920
  async setManagedSitePrimaryDomain(siteId, domainId) {
30896
30921
  assertManagedSiteOperationsEnabled();
30897
30922
  return mutateSiteDomains(siteId, async (targetDb, normalizedSiteId) => {
@@ -0,0 +1,6 @@
1
+ import "./url-umUptr5z.js";
2
+ import { t as createApp } from "./app-DQgkp6yV.js";
3
+ import "./export-DwH3ga3Y.js";
4
+ import "./env-CgaH9Mut.js";
5
+ import "./github-sync-eHOTYZGO.js";
6
+ export { createApp };
@@ -146,7 +146,7 @@
146
146
  "name": "url"
147
147
  },
148
148
  "src/client-auth.ts": {
149
- "file": "_assets/client-auth-Ce5WEAVS.js",
149
+ "file": "_assets/client-auth-N6fiJcOg.js",
150
150
  "name": "client-auth",
151
151
  "src": "src/client-auth.ts",
152
152
  "isEntry": true,
@@ -163,7 +163,7 @@
163
163
  ]
164
164
  },
165
165
  "src/client.ts": {
166
- "file": "_assets/client-dSfWfMe9.js",
166
+ "file": "_assets/client-DqsPJKiP.js",
167
167
  "name": "client",
168
168
  "src": "src/client.ts",
169
169
  "isEntry": true,
@@ -858,7 +858,7 @@
858
858
  ]
859
859
  },
860
860
  "src/style.css": {
861
- "file": "_assets/client-BoUn7xBo.css",
861
+ "file": "_assets/client-BbJ0FhON.css",
862
862
  "name": "style",
863
863
  "names": [
864
864
  "style.css"