@jamwidgets/astro 0.1.1 → 0.3.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/dist/index.d.ts CHANGED
@@ -6,6 +6,6 @@
6
6
  */
7
7
  export { DEFAULT_ENDPOINT, API_PATH, VISITOR_STORAGE_KEY, type JamWidgetsConfig, type SeriphConfig, // deprecated alias
8
8
  type Comment, type ReactionCounts, type FormSubmitResponse, type SubscribeResponse, type JamwidgetsPost, type SeriphPost, // deprecated alias
9
- type Announcement, type AnnouncementType, type Poll, type PollOption, type PollSettings, type PollWithResults, type ShowResultsMode, type FeedbackType, buildUrl, getSiteKey, getVisitorId, setVisitorId, type SubmitFormOptions, submitForm, type FetchCommentsOptions, fetchComments, type PostCommentOptions, postComment, type FetchReactionsOptions, type FetchReactionsResponse, fetchReactions, type AddReactionOptions, addReaction, type RemoveReactionOptions, removeReaction, type SubscribeOptions, subscribe, type JoinWaitlistOptions, type JoinWaitlistResponse, joinWaitlist, type ViewCountsOptions, type ViewCounts, type RecordViewResponse, getViewCounts, recordView, type SubmitFeedbackOptions, type SubmitFeedbackResponse, submitFeedback, type FetchPollOptions, fetchPoll, type VotePollOptions, type VotePollResponse, votePoll, type FetchAnnouncementsOptions, fetchAnnouncements, type DismissAnnouncementOptions, dismissAnnouncement, type FetchPostsOptions, fetchPosts, type FetchPostOptions, fetchPost, type ControllerStatus, type ControllerListener, type SubscribeState, type FormState, type ReactionsState, type CommentsState, type WaitlistState, type ViewCountsState, type FeedbackState, type PollState, type AnnouncementsState, SubscribeController, WaitlistController, FormController, ReactionsController, CommentsController, ViewCountsController, FeedbackController, PollController, AnnouncementsController, } from "@jamwidgets/core";
9
+ type Announcement, type AnnouncementType, type Poll, type PollOption, type PollSettings, type PollWithResults, type ShowResultsMode, type FeedbackType, buildUrl, getSiteKey, getVisitorId, setVisitorId, type SubmitFormOptions, submitForm, type FetchCommentsOptions, fetchComments, type PostCommentOptions, postComment, type FetchReactionsOptions, type FetchReactionsResponse, fetchReactions, type AddReactionOptions, addReaction, type RemoveReactionOptions, removeReaction, type SubscribeOptions, subscribe, type JoinWaitlistOptions, type JoinWaitlistResponse, joinWaitlist, type ViewCountsOptions, type ViewCounts, type RecordViewResponse, getViewCounts, recordView, type SubmitFeedbackOptions, type SubmitFeedbackResponse, submitFeedback, type FetchPollOptions, fetchPoll, type VotePollOptions, type VotePollResponse, votePoll, type FetchAnnouncementsOptions, fetchAnnouncements, type DismissAnnouncementOptions, dismissAnnouncement, type FetchPostsOptions, fetchPosts, type FetchPostOptions, fetchPost, type SiteConfig, fetchSiteConfig, POWERED_BY_URL, POWERED_BY_LABEL, type ControllerStatus, type ControllerListener, type SubscribeState, type FormState, type ReactionsState, type CommentsState, type WaitlistState, type ViewCountsState, type FeedbackState, type PollState, type AnnouncementsState, SubscribeController, WaitlistController, FormController, ReactionsController, CommentsController, ViewCountsController, FeedbackController, PollController, AnnouncementsController, } from "@jamwidgets/core";
10
10
  export { jamwidgetsPostsLoader, seriphPostsLoader, // deprecated alias
11
11
  type JamwidgetsPostsLoaderOptions, type SeriphPostsLoaderOptions, } from "./loader.js";
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ export {
9
9
  // Constants
10
10
  DEFAULT_ENDPOINT, API_PATH, VISITOR_STORAGE_KEY,
11
11
  // Helpers
12
- buildUrl, getSiteKey, getVisitorId, setVisitorId, submitForm, fetchComments, postComment, fetchReactions, addReaction, removeReaction, subscribe, joinWaitlist, getViewCounts, recordView, submitFeedback, fetchPoll, votePoll, fetchAnnouncements, dismissAnnouncement, fetchPosts, fetchPost, SubscribeController, WaitlistController, FormController, ReactionsController, CommentsController, ViewCountsController, FeedbackController, PollController, AnnouncementsController, } from "@jamwidgets/core";
12
+ buildUrl, getSiteKey, getVisitorId, setVisitorId, submitForm, fetchComments, postComment, fetchReactions, addReaction, removeReaction, subscribe, joinWaitlist, getViewCounts, recordView, submitFeedback, fetchPoll, votePoll, fetchAnnouncements, dismissAnnouncement, fetchPosts, fetchPost, fetchSiteConfig, POWERED_BY_URL, POWERED_BY_LABEL, SubscribeController, WaitlistController, FormController, ReactionsController, CommentsController, ViewCountsController, FeedbackController, PollController, AnnouncementsController, } from "@jamwidgets/core";
13
13
  // Re-export loader (Astro-specific)
14
14
  export { jamwidgetsPostsLoader, seriphPostsLoader, // deprecated alias
15
15
  } from "./loader.js";
package/dist/loader.d.ts CHANGED
@@ -18,6 +18,7 @@
18
18
  */
19
19
  import { fetchPosts as coreFetchPosts, fetchPost as coreFetchPost, type JamwidgetsPost, type SeriphPost, // deprecated alias
20
20
  type FetchPostsOptions, type FetchPostOptions } from "@jamwidgets/core";
21
+ import type { Loader } from "astro/loaders";
21
22
  export type { JamwidgetsPost, SeriphPost, FetchPostsOptions, FetchPostOptions };
22
23
  export { coreFetchPosts as fetchPosts, coreFetchPost as fetchPost };
23
24
  export interface JamwidgetsPostsLoaderOptions {
@@ -34,29 +35,11 @@ export interface JamwidgetsPostsLoaderOptions {
34
35
  }
35
36
  /** @deprecated Use JamwidgetsPostsLoaderOptions instead */
36
37
  export type SeriphPostsLoaderOptions = JamwidgetsPostsLoaderOptions;
37
- interface LoaderContext {
38
- store: {
39
- set: (entry: {
40
- id: string;
41
- data: JamwidgetsPost;
42
- }) => void;
43
- clear: () => void;
44
- };
45
- logger: {
46
- info: (message: string) => void;
47
- warn: (message: string) => void;
48
- error: (message: string) => void;
49
- };
50
- generateDigest: (data: unknown) => string;
51
- }
52
38
  /**
53
39
  * Creates an Astro content loader that fetches posts from Jamwidgets.
54
40
  *
55
41
  * Posts are fetched at build time and cached by Astro.
56
42
  */
57
- export declare function jamwidgetsPostsLoader(options: JamwidgetsPostsLoaderOptions): {
58
- name: string;
59
- load(context: LoaderContext): Promise<void>;
60
- };
43
+ export declare function jamwidgetsPostsLoader(options: JamwidgetsPostsLoaderOptions): Loader;
61
44
  /** @deprecated Use jamwidgetsPostsLoader instead */
62
45
  export declare const seriphPostsLoader: typeof jamwidgetsPostsLoader;
package/dist/loader.js CHANGED
@@ -43,6 +43,8 @@ export function jamwidgetsPostsLoader(options) {
43
43
  "X-Jamwidgets-Key": siteKey,
44
44
  // Legacy headers for backward compatibility
45
45
  "X-Seriph-Key": siteKey,
46
+ // User-Agent to avoid bot detection (Cloudflare, etc.)
47
+ "User-Agent": "JamwidgetsAstroLoader/1.0",
46
48
  },
47
49
  });
48
50
  if (!response.ok) {
@@ -53,7 +55,7 @@ export function jamwidgetsPostsLoader(options) {
53
55
  for (const post of data.posts) {
54
56
  store.set({
55
57
  id: post.slug,
56
- data: post,
58
+ data: { ...post },
57
59
  });
58
60
  }
59
61
  logger.info(`Loaded ${data.posts.length} posts from Jamwidgets`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jamwidgets/astro",
3
- "version": "0.1.1",
3
+ "version": "0.3.1",
4
4
  "description": "Astro components and content loader for Jamwidgets (forms, comments, reactions, posts)",
5
5
  "repository": {
6
6
  "type": "git",
@@ -23,8 +23,10 @@
23
23
  },
24
24
  "./Form": "./src/Form.astro",
25
25
  "./Comments": "./src/Comments.astro",
26
+ "./PoweredBy": "./src/PoweredBy.astro",
26
27
  "./Reactions": "./src/Reactions.astro",
27
28
  "./Views": "./src/Views.astro",
29
+ "./Analytics": "./src/Analytics.astro",
28
30
  "./Poll": "./src/Poll.astro",
29
31
  "./Subscribe": "./src/Subscribe.astro",
30
32
  "./SubscribeForm": "./src/SubscribeForm.astro",
@@ -37,6 +39,10 @@
37
39
  "src",
38
40
  "dist"
39
41
  ],
42
+ "scripts": {
43
+ "build": "tsc",
44
+ "dev": "tsc --watch"
45
+ },
40
46
  "keywords": [
41
47
  "astro",
42
48
  "jamwidgets",
@@ -50,16 +56,13 @@
50
56
  ],
51
57
  "license": "MIT",
52
58
  "dependencies": {
53
- "@jamwidgets/core": "0.1.1"
59
+ "@jamwidgets/core": "^0.3.1"
54
60
  },
55
61
  "peerDependencies": {
56
- "astro": "^5.0.0"
62
+ "astro": "^5.0.0 || ^6.0.0 || ^7.0.0"
57
63
  },
58
64
  "devDependencies": {
59
- "typescript": "^5.7.3"
60
- },
61
- "scripts": {
62
- "build": "tsc",
63
- "dev": "tsc --watch"
65
+ "astro": "^7.1.3",
66
+ "typescript": "^5.9.3"
64
67
  }
65
- }
68
+ }
@@ -0,0 +1,30 @@
1
+ ---
2
+ interface Props {
3
+ /** Falls back to the `jamwidgets-site-key` meta tag. */
4
+ siteKey?: string;
5
+ /** Defaults to `https://jamwidgets.com`. */
6
+ endpoint?: string;
7
+ }
8
+
9
+ const { siteKey = "", endpoint = "" } = Astro.props;
10
+ ---
11
+
12
+ <div
13
+ data-jamwidgets-analytics
14
+ data-site-key={siteKey}
15
+ data-endpoint={endpoint}
16
+ hidden
17
+ >
18
+ </div>
19
+
20
+ <script>
21
+ import { initAnalytics } from "@jamwidgets/core";
22
+
23
+ const el = document.querySelector("[data-jamwidgets-analytics]") as
24
+ | HTMLElement
25
+ | null;
26
+ initAnalytics({
27
+ siteKey: el?.dataset.siteKey || undefined,
28
+ endpoint: el?.dataset.endpoint || undefined,
29
+ });
30
+ </script>
@@ -35,6 +35,8 @@ const {
35
35
 
36
36
  // Build the API URL
37
37
  const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
38
+
39
+ import PoweredBy from "./PoweredBy.astro";
38
40
  ---
39
41
 
40
42
  <div
@@ -85,9 +87,13 @@ const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
85
87
  <div class="jamwidgets-comment-replies"></div>
86
88
  </div>
87
89
  </template>
90
+
91
+ <PoweredBy siteKey={siteKey} endpoint={endpoint} />
88
92
  </div>
89
93
 
90
94
  <script>
95
+ import { getAttributionHeaders, getVisitorId } from "@jamwidgets/core";
96
+
91
97
  interface Comment {
92
98
  id: string;
93
99
  authorName: string;
@@ -96,17 +102,6 @@ const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
96
102
  replies: Comment[];
97
103
  }
98
104
 
99
- // Get or create a visitor ID for anonymous users
100
- const VISITOR_KEY = "jamwidgets_visitor_id";
101
- function getVisitorId(): string {
102
- let id = localStorage.getItem(VISITOR_KEY);
103
- if (!id) {
104
- id = crypto.randomUUID();
105
- localStorage.setItem(VISITOR_KEY, id);
106
- }
107
- return id;
108
- }
109
-
110
105
  // Store form load timestamps for spam detection
111
106
  const formTimestamps = new WeakMap<Element, number>();
112
107
 
@@ -126,6 +121,7 @@ const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
126
121
 
127
122
  const visitorId = getVisitorId();
128
123
  const headers = {
124
+ ...getAttributionHeaders(pageId),
129
125
  "X-JamWidgets-Key": siteKey,
130
126
  "X-JamWidgets-Visitor": visitorId,
131
127
  };
@@ -38,6 +38,8 @@ const {
38
38
 
39
39
  // Build the API URL
40
40
  const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
41
+
42
+ import PoweredBy from "./PoweredBy.astro";
41
43
  ---
42
44
 
43
45
  <div
@@ -81,6 +83,8 @@ const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
81
83
  </button>
82
84
  </div>
83
85
  </form>
86
+
87
+ <PoweredBy siteKey={siteKey} endpoint={endpoint} />
84
88
  </div>
85
89
 
86
90
  <script>
package/src/Form.astro CHANGED
@@ -40,6 +40,8 @@ const {
40
40
 
41
41
  // Build the API URL
42
42
  const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
43
+
44
+ import PoweredBy from "./PoweredBy.astro";
43
45
  ---
44
46
 
45
47
  <form
@@ -58,25 +60,18 @@ const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
58
60
 
59
61
  <!-- Success/Error message container (hidden by default) -->
60
62
  <div class="jamwidgets-form-message" style="display: none;" aria-live="polite"></div>
63
+
64
+ <PoweredBy siteKey={siteKey} endpoint={endpoint} />
61
65
  </form>
62
66
 
63
67
  <script>
68
+ import { getAttributionHeaders, getVisitorId } from "@jamwidgets/core";
69
+
64
70
  interface FormSubmitResponse {
65
71
  success: boolean;
66
72
  message: string;
67
73
  }
68
74
 
69
- // Get or create a visitor ID for anonymous users
70
- const VISITOR_KEY = "jamwidgets_visitor_id";
71
- function getVisitorId(): string {
72
- let id = localStorage.getItem(VISITOR_KEY);
73
- if (!id) {
74
- id = crypto.randomUUID();
75
- localStorage.setItem(VISITOR_KEY, id);
76
- }
77
- return id;
78
- }
79
-
80
75
  // Store form load timestamps for spam detection
81
76
  const formTimestamps = new WeakMap<Element, number>();
82
77
 
@@ -130,6 +125,7 @@ const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
130
125
  const response = await fetch(`${endpoint}/forms/${formSlug}/submit`, {
131
126
  method: "POST",
132
127
  headers: {
128
+ ...getAttributionHeaders(),
133
129
  "Content-Type": "application/json",
134
130
  "X-JamWidgets-Key": siteKey,
135
131
  "X-JamWidgets-Visitor": getVisitorId(),
package/src/Poll.astro CHANGED
@@ -38,6 +38,8 @@ const {
38
38
 
39
39
  // Build the API URL
40
40
  const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
41
+
42
+ import PoweredBy from "./PoweredBy.astro";
41
43
  ---
42
44
 
43
45
  <div
@@ -60,6 +62,8 @@ const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
60
62
  <button type="button" class="jamwidgets-poll-submit" style="display: none;">Vote</button>
61
63
  </div>
62
64
  <div class="jamwidgets-poll-error" style="display: none;"></div>
65
+
66
+ <PoweredBy siteKey={siteKey} endpoint={endpoint} />
63
67
  </div>
64
68
 
65
69
  <script>
@@ -0,0 +1,83 @@
1
+ ---
2
+ /**
3
+ * JamWidgets "Powered by" footer.
4
+ *
5
+ * Rendered at the bottom of a drop-in widget. It hydrates hidden and only
6
+ * reveals itself when the site is on the Free tier (no custom branding) — the
7
+ * gate is the `hasCustomBranding` flag from `GET /api/v1/site`. Paid tiers
8
+ * (Pro/Team) keep it hidden, and if the config fetch fails we stay hidden so a
9
+ * paying site is never wrongly branded.
10
+ *
11
+ * Included by the block widgets (Comments, Form, Subscribe, Poll, Feedback,
12
+ * Waitlist); not the ambient ones (Reactions, Views, Announcements).
13
+ */
14
+ interface Props {
15
+ /** Your site key (required) */
16
+ siteKey: string;
17
+ /** Base URL of your JamWidgets instance (default: 'https://jamwidgets.com') */
18
+ endpoint?: string;
19
+ }
20
+
21
+ const DEFAULT_ENDPOINT = "https://jamwidgets.com";
22
+ const API_PATH = "/api/v1";
23
+
24
+ const { siteKey, endpoint = DEFAULT_ENDPOINT } = Astro.props;
25
+ const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
26
+ ---
27
+
28
+ <div
29
+ class="jamwidgets-powered-by"
30
+ data-jamwidgets-branding
31
+ data-endpoint={baseUrl}
32
+ data-site-key={siteKey}
33
+ hidden
34
+ >
35
+ <a href="https://jamwidgets.com/?utm_source=powered_by" target="_blank" rel="noopener noreferrer">
36
+ Powered by JamWidgets
37
+ </a>
38
+ </div>
39
+
40
+ <script>
41
+ // Reveal the footer only for sites without custom branding (Free tier).
42
+ // Site config is fetched once per (endpoint, siteKey); on any failure we
43
+ // leave the footer hidden (fail closed — never brand a paying site).
44
+ const brandingCache = new Map<string, Promise<boolean>>();
45
+
46
+ function hasCustomBranding(baseUrl: string, siteKey: string): Promise<boolean> {
47
+ const key = `${baseUrl}::${siteKey}`;
48
+ let pending = brandingCache.get(key);
49
+ if (!pending) {
50
+ pending = fetch(`${baseUrl}/site`, { headers: { "X-JamWidgets-Key": siteKey } })
51
+ .then((res) => (res.ok ? res.json() : null))
52
+ .then((data) => data?.site_config?.hasCustomBranding === true)
53
+ .catch(() => true);
54
+ brandingCache.set(key, pending);
55
+ }
56
+ return pending;
57
+ }
58
+
59
+ document.querySelectorAll<HTMLElement>("[data-jamwidgets-branding]").forEach((el) => {
60
+ const baseUrl = el.dataset.endpoint;
61
+ const siteKey = el.dataset.siteKey;
62
+ if (!baseUrl || !siteKey) return;
63
+ hasCustomBranding(baseUrl, siteKey).then((custom) => {
64
+ if (!custom) el.removeAttribute("hidden");
65
+ });
66
+ });
67
+ </script>
68
+
69
+ <style>
70
+ .jamwidgets-powered-by {
71
+ margin-top: 0.75rem;
72
+ text-align: center;
73
+ font-size: 0.75rem;
74
+ line-height: 1;
75
+ }
76
+ .jamwidgets-powered-by a {
77
+ color: var(--jamwidgets-muted-color, #9ca3af);
78
+ text-decoration: none;
79
+ }
80
+ .jamwidgets-powered-by a:hover {
81
+ text-decoration: underline;
82
+ }
83
+ </style>
@@ -87,16 +87,7 @@ const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
87
87
  </div>
88
88
 
89
89
  <script>
90
- // Get or create a visitor ID for anonymous users
91
- const VISITOR_KEY = "jamwidgets_visitor_id";
92
- function getVisitorId(): string {
93
- let id = localStorage.getItem(VISITOR_KEY);
94
- if (!id) {
95
- id = crypto.randomUUID();
96
- localStorage.setItem(VISITOR_KEY, id);
97
- }
98
- return id;
99
- }
90
+ import { getAttributionHeaders, getVisitorId } from "@jamwidgets/core";
100
91
 
101
92
  document.querySelectorAll("[data-jamwidgets-reactions]").forEach((container) => {
102
93
  const endpoint = (container as HTMLElement).dataset.endpoint;
@@ -107,6 +98,7 @@ const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
107
98
 
108
99
  const visitorId = getVisitorId();
109
100
  const headers = {
101
+ ...getAttributionHeaders(pageId),
110
102
  "X-JamWidgets-Key": siteKey,
111
103
  "X-JamWidgets-Visitor": visitorId,
112
104
  };
@@ -49,6 +49,8 @@ const {
49
49
 
50
50
  // Build the API URL
51
51
  const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
52
+
53
+ import PoweredBy from "./PoweredBy.astro";
52
54
  ---
53
55
 
54
56
  <form
@@ -79,6 +81,8 @@ const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
79
81
 
80
82
  <!-- Success/Error message container (hidden by default) -->
81
83
  <div class="jamwidgets-subscribe-message" style="display: none;" aria-live="polite"></div>
84
+
85
+ <PoweredBy siteKey={siteKey} endpoint={endpoint} />
82
86
  </form>
83
87
 
84
88
  <script>
@@ -71,22 +71,13 @@ const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
71
71
  </form>
72
72
 
73
73
  <script>
74
+ import { getAttributionHeaders, getVisitorId } from "@jamwidgets/core";
75
+
74
76
  interface SubscribeResponse {
75
77
  success: boolean;
76
78
  message: string;
77
79
  }
78
80
 
79
- // Get or create a visitor ID for anonymous users
80
- const VISITOR_KEY = "jamwidgets_visitor_id";
81
- function getVisitorId(): string {
82
- let id = localStorage.getItem(VISITOR_KEY);
83
- if (!id) {
84
- id = crypto.randomUUID();
85
- localStorage.setItem(VISITOR_KEY, id);
86
- }
87
- return id;
88
- }
89
-
90
81
  document.querySelectorAll("[data-jamwidgets-subscribe-form]").forEach((form) => {
91
82
  form.addEventListener("submit", async (e) => {
92
83
  e.preventDefault();
@@ -114,6 +105,7 @@ const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
114
105
  const response = await fetch(`${endpoint}/subscribe`, {
115
106
  method: "POST",
116
107
  headers: {
108
+ ...getAttributionHeaders(),
117
109
  "Content-Type": "application/json",
118
110
  "X-JamWidgets-Key": siteKey,
119
111
  "X-JamWidgets-Visitor": getVisitorId(),
@@ -40,6 +40,8 @@ const {
40
40
 
41
41
  // Build the API URL
42
42
  const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
43
+
44
+ import PoweredBy from "./PoweredBy.astro";
43
45
  ---
44
46
 
45
47
  <div
@@ -62,6 +64,8 @@ const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
62
64
  </button>
63
65
  </form>
64
66
  <div class="jamwidgets-waitlist-message" style="display: none;"></div>
67
+
68
+ <PoweredBy siteKey={siteKey} endpoint={endpoint} />
65
69
  </div>
66
70
 
67
71
  <script>
package/src/index.ts CHANGED
@@ -95,6 +95,12 @@ export {
95
95
  type FetchPostOptions,
96
96
  fetchPost,
97
97
 
98
+ // Site config + branding
99
+ type SiteConfig,
100
+ fetchSiteConfig,
101
+ POWERED_BY_URL,
102
+ POWERED_BY_LABEL,
103
+
98
104
  // Controllers
99
105
  type ControllerStatus,
100
106
  type ControllerListener,
package/src/loader.ts CHANGED
@@ -28,6 +28,7 @@ import {
28
28
  type FetchPostsOptions,
29
29
  type FetchPostOptions,
30
30
  } from "@jamwidgets/core";
31
+ import type { Loader, LoaderContext } from "astro/loaders";
31
32
 
32
33
  // Re-export types and functions from core
33
34
  export type { JamwidgetsPost, SeriphPost, FetchPostsOptions, FetchPostOptions };
@@ -49,19 +50,6 @@ export interface JamwidgetsPostsLoaderOptions {
49
50
  /** @deprecated Use JamwidgetsPostsLoaderOptions instead */
50
51
  export type SeriphPostsLoaderOptions = JamwidgetsPostsLoaderOptions;
51
52
 
52
- interface LoaderContext {
53
- store: {
54
- set: (entry: { id: string; data: JamwidgetsPost }) => void;
55
- clear: () => void;
56
- };
57
- logger: {
58
- info: (message: string) => void;
59
- warn: (message: string) => void;
60
- error: (message: string) => void;
61
- };
62
- generateDigest: (data: unknown) => string;
63
- }
64
-
65
53
  interface ApiResponse {
66
54
  posts: JamwidgetsPost[];
67
55
  total: number;
@@ -72,7 +60,7 @@ interface ApiResponse {
72
60
  *
73
61
  * Posts are fetched at build time and cached by Astro.
74
62
  */
75
- export function jamwidgetsPostsLoader(options: JamwidgetsPostsLoaderOptions) {
63
+ export function jamwidgetsPostsLoader(options: JamwidgetsPostsLoaderOptions): Loader {
76
64
  const {
77
65
  endpoint = DEFAULT_ENDPOINT,
78
66
  tag,
@@ -103,6 +91,8 @@ export function jamwidgetsPostsLoader(options: JamwidgetsPostsLoaderOptions) {
103
91
  "X-Jamwidgets-Key": siteKey,
104
92
  // Legacy headers for backward compatibility
105
93
  "X-Seriph-Key": siteKey,
94
+ // User-Agent to avoid bot detection (Cloudflare, etc.)
95
+ "User-Agent": "JamwidgetsAstroLoader/1.0",
106
96
  },
107
97
  });
108
98
 
@@ -119,7 +109,7 @@ export function jamwidgetsPostsLoader(options: JamwidgetsPostsLoaderOptions) {
119
109
  for (const post of data.posts) {
120
110
  store.set({
121
111
  id: post.slug,
122
- data: post,
112
+ data: { ...post },
123
113
  });
124
114
  }
125
115