@deepsel/cms-utils 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,12 +1,14 @@
1
1
  # @deepsel/cms-utils
2
2
 
3
- A collection of light-weight helper functions for building DeepCMS features in JavaScript/TypeScript apps.
3
+ Framework-agnostic utilities for building DeepCMS themes in any JavaScript framework.
4
4
 
5
5
  `@deepsel/cms-utils` is designed to be:
6
6
 
7
- - **Framework-agnostic** – works with Node, React, Astro, Next.js, etc.
7
+ - **Framework-agnostic** – works with React, Vue, Angular, Astro, Next.js, etc.
8
8
  - **TypeScript-friendly** – fully typed helpers.
9
- - **CMS-oriented** – utilities for menus, slugs, URLs, localization, content trees, etc.
9
+ - **CMS-oriented** – utilities for menus, slugs, URLs, localization, page data fetching, etc.
10
+
11
+ > **Note:** For React-specific hooks and components, use [`@deepsel/deep-cms-react`](https://github.com/DeepselSystems/deep-cms-react)
10
12
 
11
13
  ---
12
14
 
@@ -21,11 +23,11 @@ npm install @deepsel/cms-utils
21
23
  Example (TypeScript / ESM):
22
24
 
23
25
  ```typescript
24
- import { getMenusForCurrentLang } from '@deepsel/cms-utils';
26
+ import { isValidLanguageCode } from '@deepsel/cms-utils';
25
27
 
26
- const menus = getMenusForCurrentLang();
28
+ const isValidLanguageCode = isValidLanguageCode('en');
27
29
 
28
- console.log(menus);
30
+ console.log({ isValidLanguageCode });
29
31
  ```
30
32
 
31
33
  ## Local Development
@@ -89,9 +91,11 @@ In your app (`my-app`), import from `@deepsel/cms-utils` as if it were a regular
89
91
 
90
92
  ```typescript
91
93
  // inside my-app
92
- import { getMenusForCurrentLang } from '@deepsel/cms-utils';
94
+ import { isValidLanguageCode } from '@deepsel/cms-utils';
95
+
96
+ const isValidLanguageCode = isValidLanguageCode('en');
93
97
 
94
- const menus = getMenusForCurrentLang();
98
+ console.log({ isValidLanguageCode });
95
99
  ```
96
100
 
97
101
  ### 5. Build the Package for Production
@@ -1 +1,2 @@
1
- export * from './getCurrentLangMenus';
1
+ export * from './isActiveMenu';
2
+ export * from './types';
@@ -1 +1,2 @@
1
- export * from './getCurrentLangMenus';
1
+ export * from './isActiveMenu';
2
+ export * from './types';
@@ -0,0 +1,3 @@
1
+ import type { MenuItem } from './types';
2
+ import type { PageData } from '../page';
3
+ export declare const isActiveMenu: (menuItem: MenuItem, pageData: PageData) => boolean;
@@ -0,0 +1,23 @@
1
+ // Check if a menu item should be marked as active
2
+ export const isActiveMenu = (menuItem, pageData) => {
3
+ // return if not browser
4
+ if (typeof window === 'undefined') {
5
+ return false;
6
+ }
7
+ const location = window.location;
8
+ const currentLang = pageData.lang;
9
+ let result;
10
+ if (menuItem.url === '/') {
11
+ result =
12
+ location.pathname === '/' ||
13
+ location.pathname === `/${currentLang}` ||
14
+ location.pathname === `/${currentLang}/`;
15
+ }
16
+ else {
17
+ result =
18
+ location.pathname === menuItem?.url ||
19
+ location.pathname === `/${currentLang}${menuItem.url}` ||
20
+ location.pathname === `/${currentLang}${menuItem.url}/`;
21
+ }
22
+ return result;
23
+ };
@@ -1,22 +1,8 @@
1
1
  export interface MenuItem {
2
- id: number;
3
- parent_id: number | null;
4
- position: number;
5
- translations: Record<string, {
6
- open_in_new_tab: boolean;
7
- page_content_id: number;
8
- title: string;
9
- url: string | null;
10
- use_custom_url: boolean;
11
- use_page_title: boolean;
12
- }>;
13
- children: MenuItem[];
14
- }
15
- export interface ProcessedMenuItem {
16
2
  id: number;
17
3
  position: number;
18
4
  title: string;
19
5
  url: string | null;
20
6
  open_in_new_tab: boolean;
21
- children: ProcessedMenuItem[];
7
+ children: MenuItem[];
22
8
  }
@@ -1,5 +1,4 @@
1
- import type { ApiResponse } from './types';
2
1
  /**
3
2
  * Fetches Form data from the backend by language and slug
4
3
  */
5
- export declare function fetchFormData(lang: string, slug: string, backendHost?: string): Promise<ApiResponse>;
4
+ export declare function fetchFormData(lang: string, slug: string, backendHost?: string): Promise<Record<string, unknown>>;
@@ -1,5 +1,5 @@
1
- import type { ApiResponse } from './types';
1
+ import type { PageData } from './types';
2
2
  /**
3
3
  * Fetches page data from the backend by language and slug
4
4
  */
5
- export declare function fetchPageData(lang: string | null, slug: string, isPreview?: boolean, authToken?: string | null, astroRequest?: Request | null, backendHost?: string): Promise<ApiResponse>;
5
+ export declare function fetchPageData(lang: string | null, slug: string, isPreview?: boolean, authToken?: string | null, astroRequest?: Request | null, backendHost?: string): Promise<PageData>;
@@ -46,26 +46,14 @@ export async function fetchPageData(lang, slug, isPreview = false, authToken = n
46
46
  // Note: Cannot override Host header due to browser security restrictions
47
47
  }
48
48
  // Add authentication headers if token exists (for both preview and protected content)
49
- let token = authToken;
50
- // If no token provided and we're in browser environment, try Capacitor Preferences
51
- if (!token && typeof window !== 'undefined') {
52
- try {
53
- const { Preferences } = await import('@capacitor/preferences');
54
- const tokenResult = await Preferences.get({ key: 'token' });
55
- token = tokenResult.value;
56
- }
57
- catch (e) {
58
- console.warn('Could not get token from Preferences:', e);
59
- }
60
- }
61
- if (token) {
62
- fetchOptions.headers['Authorization'] = `Bearer ${token}`;
49
+ if (authToken) {
50
+ fetchOptions.headers['Authorization'] = `Bearer ${authToken}`;
63
51
  }
64
52
  // Fetch the page data from the backend
65
53
  const response = await fetch(url, fetchOptions);
66
54
  // Handle authentication errors
67
55
  if (response.status === 401) {
68
- return { error: true, status: 401, message: 'Authentication required' };
56
+ throw new Error('Authentication required');
69
57
  }
70
58
  // Only treat actual 404 as not found
71
59
  if (response.status === 404) {
@@ -79,12 +67,12 @@ export async function fetchPageData(lang, slug, isPreview = false, authToken = n
79
67
  status: 404,
80
68
  detail,
81
69
  public_settings: siteSettings,
82
- lang: lang || siteSettings?.default_language?.iso_code || 'en',
70
+ lang: lang || siteSettings.default_language?.iso_code || 'en',
83
71
  };
84
72
  }
85
73
  catch (settingsError) {
86
74
  console.warn('Could not fetch site settings for 404 page:', settingsError);
87
- return { notFound: true, status: 404, detail };
75
+ throw new Error(`Page not found: ${detail}`);
88
76
  }
89
77
  }
90
78
  try {
@@ -94,11 +82,11 @@ export async function fetchPageData(lang, slug, isPreview = false, authToken = n
94
82
  }
95
83
  catch (parseError) {
96
84
  console.error(`Failed to parse response: ${parseError.message}`);
97
- return { error: true, parseError: parseError.message };
85
+ throw new Error(`Failed to parse response: ${parseError.message}`);
98
86
  }
99
87
  }
100
88
  catch (error) {
101
89
  console.error('Error fetching page data:', error);
102
- return { error: true, message: error.message };
90
+ throw error;
103
91
  }
104
92
  }
@@ -2,4 +2,4 @@ import type { SiteSettings } from '../types';
2
2
  /**
3
3
  * Fetches public settings from the backend
4
4
  */
5
- export declare function fetchPublicSettings(orgId?: number | null, astroRequest?: Request | null, lang?: string | null, backendHost?: string): Promise<SiteSettings | null>;
5
+ export declare function fetchPublicSettings(orgId?: number | null, astroRequest?: Request | null, lang?: string | null, backendHost?: string): Promise<SiteSettings>;
@@ -58,6 +58,6 @@ export async function fetchPublicSettings(orgId = null, astroRequest = null, lan
58
58
  }
59
59
  catch (error) {
60
60
  console.error('Error fetching public settings:', error);
61
- return null;
61
+ throw error;
62
62
  }
63
63
  }
@@ -1,4 +1,4 @@
1
1
  /**
2
2
  * Extracts the authentication token from cookies or URL parameter (for iframe preview)
3
3
  */
4
- export declare function getAuthToken(astro: any): any;
4
+ export declare function getAuthToken(astro: any): string | null;
@@ -1,8 +1,6 @@
1
- export * from './fetchBlogListData.js';
2
- export * from './fetchBlogPostData.js';
3
- export * from './fetchFormData.js';
4
- export * from './fetchPageData.js';
5
- export * from './fetchPublicSettings.js';
6
- export * from './getAuthToken.js';
7
- export * from './parseSlugForLangAndPath.js';
8
- export * from './types.js';
1
+ export * from './fetchFormData';
2
+ export * from './fetchPageData';
3
+ export * from './fetchPublicSettings';
4
+ export * from './getAuthToken';
5
+ export * from './parseSlugForLangAndPath';
6
+ export * from './types';
@@ -1,8 +1,6 @@
1
- export * from './fetchBlogListData.js';
2
- export * from './fetchBlogPostData.js';
3
- export * from './fetchFormData.js';
4
- export * from './fetchPageData.js';
5
- export * from './fetchPublicSettings.js';
6
- export * from './getAuthToken.js';
7
- export * from './parseSlugForLangAndPath.js';
8
- export * from './types.js';
1
+ export * from './fetchFormData';
2
+ export * from './fetchPageData';
3
+ export * from './fetchPublicSettings';
4
+ export * from './getAuthToken';
5
+ export * from './parseSlugForLangAndPath';
6
+ export * from './types';
@@ -1,21 +1,75 @@
1
+ import type { SiteSettings } from '../types';
2
+ import type { MenuItem } from '../menus/types';
1
3
  export interface SlugParseResult {
2
4
  lang: string | null;
3
5
  path: string;
4
6
  }
5
- export interface ErrorResponse {
6
- error: true;
7
- status?: number;
8
- message?: string;
9
- parseError?: string;
7
+ export interface Language {
8
+ id: number;
9
+ name: string;
10
+ iso_code: string;
11
+ svg_flag: string;
12
+ }
13
+ export interface SpecialTemplate {
14
+ name: string;
15
+ html?: string;
16
+ component_name?: string;
17
+ }
18
+ export type Menus = MenuItem[];
19
+ export interface ContentField {
20
+ 'ds-label': string;
21
+ 'ds-type': string;
22
+ 'ds-value': string;
23
+ }
24
+ export interface Content {
25
+ main: ContentField;
10
26
  }
11
- export interface NotFoundResponse {
12
- notFound: true;
13
- status: number;
14
- detail: string;
15
- public_settings?: Record<string, unknown>;
16
- lang?: string;
27
+ export interface SeoMetadata {
28
+ title: string;
29
+ description: string | null;
30
+ featured_image_id: number | null;
31
+ featured_image_name: string | null;
32
+ allow_indexing: boolean;
17
33
  }
18
- export interface BlogListResponse {
19
- posts: Record<string, unknown>[];
34
+ export interface LanguageAlternative {
35
+ slug: string;
36
+ locale: Language;
37
+ }
38
+ export interface BlogPostAuthor {
39
+ id: number;
40
+ display_name?: string;
41
+ username: string;
42
+ image?: string;
43
+ }
44
+ export interface BlogPostListItem {
45
+ id: number;
46
+ title: string;
47
+ slug: string;
48
+ excerpt?: string;
49
+ featured_image_id?: number;
50
+ publish_date?: string;
51
+ author?: BlogPostAuthor;
52
+ lang: string;
53
+ }
54
+ export interface PageData {
55
+ id?: number;
56
+ title?: string;
57
+ content?: Content;
58
+ lang: string;
59
+ public_settings: SiteSettings;
60
+ seo_metadata?: SeoMetadata;
61
+ language_alternatives?: LanguageAlternative[];
62
+ is_frontend_page?: boolean | null;
63
+ string_id?: string | null;
64
+ contents?: unknown;
65
+ page_custom_code?: string | null;
66
+ custom_code?: string | null;
67
+ require_login?: boolean;
68
+ blog_posts?: BlogPostListItem[];
69
+ featured_image_id?: number;
70
+ publish_date?: string;
71
+ author?: BlogPostAuthor;
72
+ notFound?: boolean;
73
+ status?: number;
74
+ detail?: string;
20
75
  }
21
- export type ApiResponse = Record<string, unknown> | ErrorResponse | NotFoundResponse | BlogListResponse;
package/dist/types.d.ts CHANGED
@@ -1,5 +1,8 @@
1
1
  import type { MenuItem } from './menus/types';
2
+ import type { SpecialTemplate } from './page/types';
2
3
  export interface SiteSettings {
4
+ id: number;
5
+ name: string;
3
6
  domains: string[];
4
7
  available_languages: Array<{
5
8
  id: number;
@@ -22,4 +25,15 @@ export interface SiteSettings {
22
25
  show_chatbox: boolean;
23
26
  website_custom_code: string | null;
24
27
  menus: MenuItem[];
28
+ access_token_expire_minutes: number;
29
+ require_2fa_all_users: boolean;
30
+ allow_public_signup: boolean;
31
+ is_enabled_google_sign_in: boolean;
32
+ is_enabled_saml: boolean;
33
+ saml_sp_entity_id: string | null;
34
+ auto_translate_components: boolean;
35
+ has_openai_api_key: boolean;
36
+ ai_default_writing_model_id: number;
37
+ special_templates: Record<string, SpecialTemplate>;
38
+ selected_theme: string;
25
39
  }
package/package.json CHANGED
@@ -1,24 +1,91 @@
1
1
  {
2
2
  "name": "@deepsel/cms-utils",
3
- "version": "1.0.0",
4
- "description": "Helper utilities for DeepCMS",
3
+ "version": "1.1.0",
4
+ "description": "Helper utilities for Deepsel CMS",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
8
- "repository": {
9
- "type": "git",
10
- "url": "git+https://github.com/DeepselSystems/cms-utils.git"
11
- },
12
8
  "author": "Tim Tran <tim.tran@deepsel.com> (https://deepsel.com)",
13
9
  "license": "MIT",
14
10
  "bugs": {
15
- "url": "https://github.com/DeepselSystems/cms-utils/issues"
11
+ "url": "https://github.com/DeepselSystems/deepsel-cms/issues"
16
12
  },
17
- "homepage": "https://github.com/DeepselSystems/cms-utils#readme",
13
+ "homepage": "https://github.com/DeepselSystems/deepsel-cms/tree/main/packages/cms-utils#readme",
18
14
  "type": "module",
19
15
  "main": "dist/index.js",
20
16
  "module": "dist/index.js",
21
17
  "types": "dist/index.d.ts",
18
+ "sideEffects": false,
19
+ "exports": {
20
+ ".": {
21
+ "import": "./dist/index.js",
22
+ "types": "./dist/index.d.ts"
23
+ },
24
+ "./types": {
25
+ "import": "./dist/types.js",
26
+ "types": "./dist/types.d.ts"
27
+ },
28
+ "./language": {
29
+ "import": "./dist/language/index.js",
30
+ "types": "./dist/language/index.d.ts"
31
+ },
32
+ "./language/isValidLanguageCode": {
33
+ "import": "./dist/language/isValidLanguageCode.js",
34
+ "types": "./dist/language/isValidLanguageCode.d.ts"
35
+ },
36
+ "./menus": {
37
+ "import": "./dist/menus/index.js",
38
+ "types": "./dist/menus/index.d.ts"
39
+ },
40
+ "./menus/isActiveMenu": {
41
+ "import": "./dist/menus/isActiveMenu.js",
42
+ "types": "./dist/menus/isActiveMenu.d.ts"
43
+ },
44
+ "./menus/types": {
45
+ "import": "./dist/menus/types.js",
46
+ "types": "./dist/menus/types.d.ts"
47
+ },
48
+ "./page": {
49
+ "import": "./dist/page/index.js",
50
+ "types": "./dist/page/index.d.ts"
51
+ },
52
+ "./page/constants": {
53
+ "import": "./dist/page/constants.js",
54
+ "types": "./dist/page/constants.d.ts"
55
+ },
56
+ "./page/types": {
57
+ "import": "./dist/page/types.js",
58
+ "types": "./dist/page/types.d.ts"
59
+ },
60
+ "./page/fetchBlogListData": {
61
+ "import": "./dist/page/fetchBlogListData.js",
62
+ "types": "./dist/page/fetchBlogListData.d.ts"
63
+ },
64
+ "./page/fetchBlogPostData": {
65
+ "import": "./dist/page/fetchBlogPostData.js",
66
+ "types": "./dist/page/fetchBlogPostData.d.ts"
67
+ },
68
+ "./page/fetchFormData": {
69
+ "import": "./dist/page/fetchFormData.js",
70
+ "types": "./dist/page/fetchFormData.d.ts"
71
+ },
72
+ "./page/fetchPageData": {
73
+ "import": "./dist/page/fetchPageData.js",
74
+ "types": "./dist/page/fetchPageData.d.ts"
75
+ },
76
+ "./page/fetchPublicSettings": {
77
+ "import": "./dist/page/fetchPublicSettings.js",
78
+ "types": "./dist/page/fetchPublicSettings.d.ts"
79
+ },
80
+ "./page/getAuthToken": {
81
+ "import": "./dist/page/getAuthToken.js",
82
+ "types": "./dist/page/getAuthToken.d.ts"
83
+ },
84
+ "./page/parseSlugForLangAndPath": {
85
+ "import": "./dist/page/parseSlugForLangAndPath.js",
86
+ "types": "./dist/page/parseSlugForLangAndPath.d.ts"
87
+ }
88
+ },
22
89
  "files": [
23
90
  "dist"
24
91
  ],
@@ -1,3 +0,0 @@
1
- import { ProcessedMenuItem } from './types';
2
- import type { SiteSettings } from '../types';
3
- export declare const getCurrentLangMenus: (settings: SiteSettings) => ProcessedMenuItem[] | null;
@@ -1,41 +0,0 @@
1
- /*
2
- Get menus based on the current selected language
3
- */
4
- export const getCurrentLangMenus = (settings) => {
5
- if (!settings || !settings.menus || !settings.menus.length)
6
- return null;
7
- let currentLang = localStorage.getItem('i18nextLng');
8
- // If no language is selected, use the default language
9
- if (!currentLang) {
10
- currentLang = settings.default_language.iso_code;
11
- }
12
- return settings.menus
13
- .map((menu) => processMenuItem(menu, currentLang))
14
- .filter((item) => item !== null);
15
- };
16
- /*
17
- Process a menu item and its children recursively:
18
- - Remove null items
19
- - Only keep fields that are needed from the translation for the current language
20
- - Remove other translations
21
- */
22
- function processMenuItem(menuItem, currentLang) {
23
- const translation = menuItem.translations[currentLang];
24
- if (!translation) {
25
- return null;
26
- }
27
- // Process children recursively and filter out null items
28
- const children = menuItem.children && menuItem.children.length > 0
29
- ? menuItem.children
30
- .map((child) => processMenuItem(child, currentLang))
31
- .filter((item) => item !== null)
32
- : [];
33
- return {
34
- id: menuItem.id,
35
- position: menuItem.position,
36
- title: translation.title,
37
- url: translation.url,
38
- open_in_new_tab: translation.open_in_new_tab,
39
- children,
40
- };
41
- }
@@ -1,5 +0,0 @@
1
- import type { ApiResponse } from './types';
2
- /**
3
- * Fetches blog list data from the backend by language
4
- */
5
- export declare function fetchBlogListData(lang: string, authToken?: string | null, astroRequest?: Request | null, backendHost?: string): Promise<ApiResponse>;
@@ -1,75 +0,0 @@
1
- /**
2
- * Fetches blog list data from the backend by language
3
- */
4
- export async function fetchBlogListData(lang, authToken = null, astroRequest = null, backendHost = 'http://localhost:8000') {
5
- try {
6
- // Determine the URL based on whether a language is provided
7
- const url = lang && lang !== 'default'
8
- ? `${backendHost}/blog_post/website/${lang}`
9
- : `${backendHost}/blog_post/website/default`;
10
- // Prepare fetch options
11
- const fetchOptions = {
12
- method: 'GET',
13
- headers: {
14
- 'Content-Type': 'application/json',
15
- },
16
- };
17
- // Send the current hostname to the backend for proper domain detection
18
- let hostname = null;
19
- // Server-side: Extract hostname from Astro request
20
- if (astroRequest) {
21
- const url = new URL(astroRequest.url);
22
- hostname = url.hostname;
23
- }
24
- // Client-side: Extract hostname from window
25
- else if (typeof window !== 'undefined') {
26
- hostname = window.location.hostname;
27
- }
28
- if (hostname) {
29
- fetchOptions.headers['X-Original-Host'] = hostname;
30
- fetchOptions.headers['X-Frontend-Host'] = hostname;
31
- // Note: Cannot override Host header due to browser security restrictions
32
- }
33
- // Add authentication headers if token exists (for both preview and protected content)
34
- let token = authToken;
35
- // If no token provided and we're in browser environment, try Capacitor Preferences
36
- if (!token && typeof window !== 'undefined') {
37
- try {
38
- const { Preferences } = await import('@capacitor/preferences');
39
- const tokenResult = await Preferences.get({ key: 'token' });
40
- token = tokenResult.value;
41
- }
42
- catch (e) {
43
- console.warn('Could not get token from Preferences:', e);
44
- }
45
- }
46
- if (token) {
47
- fetchOptions.headers['Authorization'] = `Bearer ${token}`;
48
- }
49
- // Fetch the blog list data from the backend
50
- const response = await fetch(url, fetchOptions);
51
- // Handle authentication errors
52
- if (response.status === 401) {
53
- return { error: true, status: 401, message: 'Authentication required' };
54
- }
55
- // Only treat actual 404 as not found
56
- if (response.status === 404) {
57
- const { detail } = await response.json();
58
- console.warn('404', url, { detail });
59
- return { notFound: true, status: 404, detail };
60
- }
61
- try {
62
- // Parse the JSON
63
- const posts = await response.json();
64
- return { posts };
65
- }
66
- catch (parseError) {
67
- console.error(`Failed to parse response: ${parseError.message}`);
68
- return { error: true, parseError: parseError.message };
69
- }
70
- }
71
- catch (error) {
72
- console.error('Error fetching blog list data:', error);
73
- return { error: true, message: error.message };
74
- }
75
- }
@@ -1,5 +0,0 @@
1
- import type { ApiResponse } from './types';
2
- /**
3
- * Fetches Blog-Post-Content data from the backend by language and slug
4
- */
5
- export declare function fetchBlogPostData(lang: string, slug: string, authToken?: string | null, astroRequest?: Request | null, backendHost?: string): Promise<ApiResponse>;
@@ -1,76 +0,0 @@
1
- /**
2
- * Fetches Blog-Post-Content data from the backend by language and slug
3
- */
4
- export async function fetchBlogPostData(lang, slug, authToken = null, astroRequest = null, backendHost = 'http://localhost:8000') {
5
- try {
6
- // Format the slug properly
7
- const formattedSlug = slug.replace(/^\/blog\//, '');
8
- // Determine the URL based on whether a language is provided
9
- const url = lang && lang !== 'default'
10
- ? `${backendHost}/blog_post/website/${lang}/${formattedSlug}`
11
- : `${backendHost}/blog_post/website/default/${formattedSlug}`;
12
- // Prepare fetch options
13
- const fetchOptions = {
14
- method: 'GET',
15
- headers: {
16
- 'Content-Type': 'application/json',
17
- },
18
- };
19
- // Send the current hostname to the backend for proper domain detection
20
- let hostname = null;
21
- // Server-side: Extract hostname from Astro request
22
- if (astroRequest) {
23
- const url = new URL(astroRequest.url);
24
- hostname = url.hostname;
25
- }
26
- // Client-side: Extract hostname from window
27
- else if (typeof window !== 'undefined') {
28
- hostname = window.location.hostname;
29
- }
30
- if (hostname) {
31
- fetchOptions.headers['X-Original-Host'] = hostname;
32
- fetchOptions.headers['X-Frontend-Host'] = hostname;
33
- // Note: Cannot override Host header due to browser security restrictions
34
- }
35
- // Add authentication headers if token exists (for both preview and protected content)
36
- let token = authToken;
37
- // If no token provided and we're in browser environment, try Capacitor Preferences
38
- if (!token && typeof window !== 'undefined') {
39
- try {
40
- const { Preferences } = await import('@capacitor/preferences');
41
- const tokenResult = await Preferences.get({ key: 'token' });
42
- token = tokenResult.value;
43
- }
44
- catch (e) {
45
- console.warn('Could not get token from Preferences:', e);
46
- }
47
- }
48
- if (token) {
49
- fetchOptions.headers['Authorization'] = `Bearer ${token}`;
50
- }
51
- // Fetch the page data from the backend
52
- const response = await fetch(url, fetchOptions);
53
- // Handle authentication errors
54
- if (response.status === 401) {
55
- return { error: true, status: 401, message: 'Authentication required' };
56
- }
57
- // Only treat actual 404 as not found
58
- if (response.status === 404) {
59
- const { detail } = await response.json();
60
- console.warn('404', url, { detail });
61
- return { notFound: true, status: 404, detail };
62
- }
63
- try {
64
- // Parse the JSON
65
- return await response.json();
66
- }
67
- catch (parseError) {
68
- console.error(`Failed to parse response: ${parseError.message}`);
69
- return { error: true, parseError: parseError.message };
70
- }
71
- }
72
- catch (error) {
73
- console.error('Error fetching page data:', error);
74
- return { error: true, message: error.message };
75
- }
76
- }