camox 0.31.0 → 0.31.2

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.
@@ -21,41 +21,7 @@ declare function createPageLoader(apiUrl: string, projectSlug: string, environme
21
21
  queryClient: QueryClient;
22
22
  };
23
23
  }) => Promise<{
24
- page: {
25
- page: {
26
- status: undefined;
27
- modifiedReason: undefined | null;
28
- blockIds: number[];
29
- id: number;
30
- createdAt: number;
31
- updatedAt: number;
32
- projectId: number;
33
- environmentId: number;
34
- pathSegment: string;
35
- fullPath: string;
36
- parentPageId: number | null;
37
- layoutId: number;
38
- livePublishedCheckpointId: number | null;
39
- contentUpdatedAt: number;
40
- nickname: string;
41
- metaTitle: string | null;
42
- metaDescription: string | null;
43
- aiSeoEnabled: boolean | null;
44
- customOgImageBlobId: string | null;
45
- customOgImageUrl: string | null;
46
- };
47
- layout: {
48
- id: number;
49
- layoutId: string;
50
- beforeBlockIds: number[];
51
- afterBlockIds: number[];
52
- } | null;
53
- projectName: string;
54
- project: {
55
- id: number;
56
- updatedAt: number;
57
- };
58
- };
24
+ page: PageStructure;
59
25
  origin: string;
60
26
  faviconUrl: string;
61
27
  }>;
@@ -1,4 +1,4 @@
1
- import { getAuthCookieHeader, getServerAuthCookieHeader } from "../../lib/auth.js";
1
+ import { buildClearServerAuthCookieHeader, getAuthCookieHeader, getServerAuthCookieHeader } from "../../lib/auth.js";
2
2
  import { seedBlockCaches } from "../../lib/normalized-data.js";
3
3
  import { CamoxPreview, PageContent } from "../preview/CamoxPreview.js";
4
4
  import { trackEvent } from "../../lib/telemetry.js";
@@ -9,7 +9,7 @@ import { queryKeys } from "@camox/api-contract/query-keys";
9
9
  import { createORPCClient } from "@orpc/client";
10
10
  import { RPCLink } from "@orpc/client/fetch";
11
11
  import { createMiddleware, createServerFn } from "@tanstack/react-start";
12
- import { getRequest } from "@tanstack/react-start/server";
12
+ import { getRequest, setResponseHeader } from "@tanstack/react-start/server";
13
13
 
14
14
  //#region src/features/routes/pageRoute.tsx
15
15
  function parseQuality(part) {
@@ -38,6 +38,88 @@ function createServerApiClient(apiUrl, environmentName, options) {
38
38
  }
39
39
  }));
40
40
  }
41
+ function getErrorDetails(error) {
42
+ if (!error || typeof error !== "object") return { message: String(error) };
43
+ const errorRecord = error;
44
+ return {
45
+ code: typeof errorRecord.code === "string" ? errorRecord.code : void 0,
46
+ status: typeof errorRecord.status === "number" ? errorRecord.status : void 0,
47
+ message: error instanceof Error ? error.message : typeof errorRecord.message === "string" ? errorRecord.message : ""
48
+ };
49
+ }
50
+ function isAuthSessionError(error) {
51
+ const { code, status, message } = getErrorDetails(error);
52
+ if (code === "UNAUTHORIZED" || code === "FORBIDDEN") return true;
53
+ if (status === 401 || status === 403) return true;
54
+ const lowerMessage = message.toLowerCase();
55
+ return lowerMessage.includes("unauthorized") || lowerMessage.includes("forbidden") || lowerMessage.includes("invalid session") || lowerMessage.includes("session expired") || lowerMessage.includes("expired session");
56
+ }
57
+ function isNotFoundError(error) {
58
+ const { code, status } = getErrorDetails(error);
59
+ return code === "NOT_FOUND" || status === 404;
60
+ }
61
+ function clearServerAuthCookie() {
62
+ if (typeof window !== "undefined") return;
63
+ setResponseHeader("Set-Cookie", buildClearServerAuthCookieHeader());
64
+ }
65
+ async function loadPage({ apiUrl, authCookieHeader, context, environmentName, pathname, projectSlug, source }) {
66
+ const serverApi = createServerApiClient(apiUrl, environmentName, { authCookieHeader });
67
+ return context.queryClient.ensureQueryData({
68
+ queryKey: queryKeys.pages.getByPath(pathname, source),
69
+ queryFn: async () => {
70
+ const [data, pagesList] = await Promise.all([serverApi.pages.getByPath({
71
+ path: pathname,
72
+ projectSlug,
73
+ source
74
+ }), serverApi.pages.listBySlug({ projectSlug }).catch(() => null)]);
75
+ seedBlockCaches(context.queryClient, data, source);
76
+ context.queryClient.setQueryData(queryKeys.pages.getByPath(pathname, source), {
77
+ page: data.page,
78
+ layout: data.layout,
79
+ projectName: data.projectName,
80
+ project: data.project
81
+ });
82
+ if (source === "live") {
83
+ seedBlockCaches(context.queryClient, data, "draft");
84
+ context.queryClient.setQueryData(queryKeys.pages.getByPath(pathname, "draft"), {
85
+ page: data.page,
86
+ layout: data.layout,
87
+ projectName: data.projectName,
88
+ project: data.project
89
+ });
90
+ }
91
+ if (pagesList) context.queryClient.setQueryData(queryKeys.pages.list, pagesList);
92
+ return {
93
+ page: data.page,
94
+ layout: data.layout,
95
+ projectName: data.projectName,
96
+ project: data.project
97
+ };
98
+ },
99
+ staleTime: Infinity
100
+ });
101
+ }
102
+ function throwNotFoundOrRethrow(error) {
103
+ if (isNotFoundError(error)) throw notFound();
104
+ throw error;
105
+ }
106
+ async function loadLivePage(options) {
107
+ try {
108
+ return await loadPage({
109
+ ...options,
110
+ source: "live"
111
+ });
112
+ } catch (error) {
113
+ throwNotFoundOrRethrow(error);
114
+ }
115
+ }
116
+ async function buildLoaderData(apiUrl, page) {
117
+ return {
118
+ page,
119
+ origin: await getOrigin(),
120
+ faviconUrl: `${apiUrl}/favicons/${page.project.id}?v=${page.project.updatedAt}`
121
+ };
122
+ }
41
123
  const getOrigin = createServerFn({ method: "GET" }).handler(async () => {
42
124
  const request = getRequest();
43
125
  return new URL(request.url).origin;
@@ -74,51 +156,26 @@ function createMarkdownMiddleware(apiUrl, projectSlug, environmentName) {
74
156
  }
75
157
  function createPageLoader(apiUrl, projectSlug, environmentName) {
76
158
  return async ({ location, context }) => {
159
+ const authCookieHeader = typeof window !== "undefined" ? getAuthCookieHeader() : await getServerLoaderAuthCookieHeader();
160
+ const loadOptions = {
161
+ apiUrl,
162
+ context,
163
+ environmentName,
164
+ pathname: location.pathname,
165
+ projectSlug
166
+ };
167
+ if (!authCookieHeader) return buildLoaderData(apiUrl, await loadLivePage(loadOptions));
77
168
  try {
78
- const authCookieHeader = typeof window !== "undefined" ? getAuthCookieHeader() : await getServerLoaderAuthCookieHeader();
79
- const source = authCookieHeader ? "draft" : "live";
80
- const serverApi = createServerApiClient(apiUrl, environmentName, { authCookieHeader });
81
- const [page, origin] = await Promise.all([context.queryClient.ensureQueryData({
82
- queryKey: queryKeys.pages.getByPath(location.pathname, source),
83
- queryFn: async () => {
84
- const [data, pagesList] = await Promise.all([serverApi.pages.getByPath({
85
- path: location.pathname,
86
- projectSlug,
87
- source
88
- }), serverApi.pages.listBySlug({ projectSlug }).catch(() => null)]);
89
- seedBlockCaches(context.queryClient, data, source);
90
- context.queryClient.setQueryData(queryKeys.pages.getByPath(location.pathname, source), {
91
- page: data.page,
92
- layout: data.layout,
93
- projectName: data.projectName,
94
- project: data.project
95
- });
96
- if (source === "live") {
97
- seedBlockCaches(context.queryClient, data, "draft");
98
- context.queryClient.setQueryData(queryKeys.pages.getByPath(location.pathname, "draft"), {
99
- page: data.page,
100
- layout: data.layout,
101
- projectName: data.projectName,
102
- project: data.project
103
- });
104
- }
105
- if (pagesList) context.queryClient.setQueryData(queryKeys.pages.list, pagesList);
106
- return {
107
- page: data.page,
108
- layout: data.layout,
109
- projectName: data.projectName,
110
- project: data.project
111
- };
112
- },
113
- staleTime: Infinity
114
- }), getOrigin()]);
115
- return {
116
- page,
117
- origin,
118
- faviconUrl: `${apiUrl}/favicons/${page.project.id}?v=${page.project.updatedAt}`
119
- };
120
- } catch {
121
- throw notFound();
169
+ return buildLoaderData(apiUrl, await loadPage({
170
+ ...loadOptions,
171
+ authCookieHeader,
172
+ source: "draft"
173
+ }));
174
+ } catch (error) {
175
+ if (!isAuthSessionError(error)) throwNotFoundOrRethrow(error);
176
+ clearServerAuthCookie();
177
+ console.warn("[camox] Ignoring stale camox_auth_cookie and retrying published page load.");
178
+ return buildLoaderData(apiUrl, await loadLivePage(loadOptions));
122
179
  }
123
180
  };
124
181
  }
@@ -179,9 +236,9 @@ function createPageHead(camoxApp) {
179
236
  }
180
237
  const PageRouteComponent = () => {
181
238
  const $ = c(2);
182
- if ($[0] !== "c7f5f058711714d2b4ce2a6ee4fb3259e56b272fafb972520b82049c0f52d9df") {
239
+ if ($[0] !== "267af96f07026fe91743b4ae5c2344f8ee96641a8dad8dc84ff7bf6a551f191e") {
183
240
  for (let $i = 0; $i < 2; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
184
- $[0] = "c7f5f058711714d2b4ce2a6ee4fb3259e56b272fafb972520b82049c0f52d9df";
241
+ $[0] = "267af96f07026fe91743b4ae5c2344f8ee96641a8dad8dc84ff7bf6a551f191e";
185
242
  }
186
243
  let t0;
187
244
  if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
@@ -168,6 +168,9 @@ function camox(options) {
168
168
  "camox > lexical",
169
169
  "camox > lucide-react",
170
170
  "camox > posthog-js",
171
+ "camox > streamdown",
172
+ "camox > streamdown > hast-util-to-jsx-runtime",
173
+ "camox > streamdown > hast-util-to-jsx-runtime > style-to-js",
171
174
  "camox > @tanstack/react-query-devtools/production",
172
175
  "camox > @tanstack/react-router > @tanstack/router-core",
173
176
  "camox > @tanstack/react-router > @tanstack/router-core/isServer",
package/dist/lib/auth.js CHANGED
@@ -6,11 +6,14 @@ import { createAuthClient } from "better-auth/react";
6
6
 
7
7
  //#region src/lib/auth.ts
8
8
  const SERVER_AUTH_COOKIE_NAME = "camox_auth_cookie";
9
+ function buildClearServerAuthCookieHeader() {
10
+ return `${SERVER_AUTH_COOKIE_NAME}=; Path=/; SameSite=Lax; Max-Age=0`;
11
+ }
9
12
  function writeServerAuthCookie(cookie) {
10
13
  if (typeof document === "undefined") return;
11
14
  const secure = window.location.protocol === "https:" ? "; Secure" : "";
12
15
  if (!cookie) {
13
- document.cookie = `${SERVER_AUTH_COOKIE_NAME}=; Path=/; SameSite=Lax; Max-Age=0${secure}`;
16
+ document.cookie = `${buildClearServerAuthCookieHeader()}${secure}`;
14
17
  return;
15
18
  }
16
19
  document.cookie = `${SERVER_AUTH_COOKIE_NAME}=${encodeURIComponent(cookie)}; Path=/; SameSite=Lax; Max-Age=2592000${secure}`;
@@ -192,9 +195,9 @@ function createCamoxAuthClient(apiUrl) {
192
195
  */
193
196
  function useProcessOtt(authClient) {
194
197
  const $ = c(5);
195
- if ($[0] !== "97f43c32d646fc292143fe500680f94eb5bc70c14cc8fdf41a809589b231501d") {
198
+ if ($[0] !== "61ec5b8d3f12e56931574a7cb47e1e3b938bb68c5135cf058423162e804c4032") {
196
199
  for (let $i = 0; $i < 5; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
197
- $[0] = "97f43c32d646fc292143fe500680f94eb5bc70c14cc8fdf41a809589b231501d";
200
+ $[0] = "61ec5b8d3f12e56931574a7cb47e1e3b938bb68c5135cf058423162e804c4032";
198
201
  }
199
202
  const [ready, setReady] = React.useState(_temp);
200
203
  let t0;
@@ -237,9 +240,9 @@ function _temp() {
237
240
  const AuthContext = React.createContext(null);
238
241
  function useAuthContext() {
239
242
  const $ = c(1);
240
- if ($[0] !== "97f43c32d646fc292143fe500680f94eb5bc70c14cc8fdf41a809589b231501d") {
243
+ if ($[0] !== "61ec5b8d3f12e56931574a7cb47e1e3b938bb68c5135cf058423162e804c4032") {
241
244
  for (let $i = 0; $i < 1; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
242
- $[0] = "97f43c32d646fc292143fe500680f94eb5bc70c14cc8fdf41a809589b231501d";
245
+ $[0] = "61ec5b8d3f12e56931574a7cb47e1e3b938bb68c5135cf058423162e804c4032";
243
246
  }
244
247
  const ctx = React.useContext(AuthContext);
245
248
  if (!ctx) throw new Error("Missing CamoxProvider");
@@ -247,9 +250,9 @@ function useAuthContext() {
247
250
  }
248
251
  function useProjectSlug() {
249
252
  const $ = c(1);
250
- if ($[0] !== "97f43c32d646fc292143fe500680f94eb5bc70c14cc8fdf41a809589b231501d") {
253
+ if ($[0] !== "61ec5b8d3f12e56931574a7cb47e1e3b938bb68c5135cf058423162e804c4032") {
251
254
  for (let $i = 0; $i < 1; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
252
- $[0] = "97f43c32d646fc292143fe500680f94eb5bc70c14cc8fdf41a809589b231501d";
255
+ $[0] = "61ec5b8d3f12e56931574a7cb47e1e3b938bb68c5135cf058423162e804c4032";
253
256
  }
254
257
  return useAuthContext().projectSlug;
255
258
  }
@@ -263,18 +266,18 @@ function useAuthState() {
263
266
  }
264
267
  function useIsAuthenticated() {
265
268
  const $ = c(1);
266
- if ($[0] !== "97f43c32d646fc292143fe500680f94eb5bc70c14cc8fdf41a809589b231501d") {
269
+ if ($[0] !== "61ec5b8d3f12e56931574a7cb47e1e3b938bb68c5135cf058423162e804c4032") {
267
270
  for (let $i = 0; $i < 1; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
268
- $[0] = "97f43c32d646fc292143fe500680f94eb5bc70c14cc8fdf41a809589b231501d";
271
+ $[0] = "61ec5b8d3f12e56931574a7cb47e1e3b938bb68c5135cf058423162e804c4032";
269
272
  }
270
273
  const { isAuthenticated } = useAuthState();
271
274
  return isAuthenticated;
272
275
  }
273
276
  function useSignInRedirect() {
274
277
  const $ = c(3);
275
- if ($[0] !== "97f43c32d646fc292143fe500680f94eb5bc70c14cc8fdf41a809589b231501d") {
278
+ if ($[0] !== "61ec5b8d3f12e56931574a7cb47e1e3b938bb68c5135cf058423162e804c4032") {
276
279
  for (let $i = 0; $i < 3; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
277
- $[0] = "97f43c32d646fc292143fe500680f94eb5bc70c14cc8fdf41a809589b231501d";
280
+ $[0] = "61ec5b8d3f12e56931574a7cb47e1e3b938bb68c5135cf058423162e804c4032";
278
281
  }
279
282
  const { authenticationUrl } = useAuthContext();
280
283
  let t0;
@@ -295,9 +298,9 @@ function useSignInRedirect() {
295
298
  */
296
299
  function useAuthActions() {
297
300
  const $ = c(5);
298
- if ($[0] !== "97f43c32d646fc292143fe500680f94eb5bc70c14cc8fdf41a809589b231501d") {
301
+ if ($[0] !== "61ec5b8d3f12e56931574a7cb47e1e3b938bb68c5135cf058423162e804c4032") {
299
302
  for (let $i = 0; $i < 5; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
300
- $[0] = "97f43c32d646fc292143fe500680f94eb5bc70c14cc8fdf41a809589b231501d";
303
+ $[0] = "61ec5b8d3f12e56931574a7cb47e1e3b938bb68c5135cf058423162e804c4032";
301
304
  }
302
305
  const { authClient, authenticationUrl } = useAuthContext();
303
306
  let t0;
@@ -355,4 +358,4 @@ function _temp2() {
355
358
  }
356
359
 
357
360
  //#endregion
358
- export { AuthContext, createCamoxAuthClient, getAuthCookieHeader, getServerAuthCookieHeader, useAuthActions, useAuthContext, useAuthState, useIsAuthenticated, useProcessOtt, useProjectSlug, useSignInRedirect };
361
+ export { AuthContext, buildClearServerAuthCookieHeader, createCamoxAuthClient, getAuthCookieHeader, getServerAuthCookieHeader, useAuthActions, useAuthContext, useAuthState, useIsAuthenticated, useProcessOtt, useProjectSlug, useSignInRedirect };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "camox",
3
- "version": "0.31.0",
3
+ "version": "0.31.2",
4
4
  "bin": {
5
5
  "camox": "./bin/camox.mjs"
6
6
  },
@@ -128,9 +128,9 @@
128
128
  "shiki": "^4.1.0",
129
129
  "streamdown": "^2.5.0",
130
130
  "zod": "^4.4.3",
131
- "@camox/api-contract": "0.31.0",
132
- "@camox/ui": "0.31.0",
133
- "@camox/cli": "0.31.0"
131
+ "@camox/api-contract": "0.31.2",
132
+ "@camox/ui": "0.31.2",
133
+ "@camox/cli": "0.31.2"
134
134
  },
135
135
  "devDependencies": {
136
136
  "@babel/core": "^7.29.0",