@axcode/axcloud-sdk 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,6 +8,35 @@ SDK for building websites with AxCloud CMS and member management.
8
8
  npm install @axcode/axcloud-sdk
9
9
  ```
10
10
 
11
+ ### Version Tags
12
+
13
+ The SDK is published with two tags for different environments:
14
+
15
+ | Tag | Purpose | Stability | Install |
16
+ |-----|---------|-----------|---------|
17
+ | `latest` | Production | Stable, tested | `npm install @axcode/axcloud-sdk` |
18
+ | `next` | Development/Staging | Preview, may have breaking changes | `npm install @axcode/axcloud-sdk@next` |
19
+
20
+ **Recommended setup:**
21
+
22
+ ```json
23
+ // Production project (package.json)
24
+ {
25
+ "dependencies": {
26
+ "@axcode/axcloud-sdk": "^0.1.0"
27
+ }
28
+ }
29
+
30
+ // Development/Staging project (package.json)
31
+ {
32
+ "dependencies": {
33
+ "@axcode/axcloud-sdk": "next"
34
+ }
35
+ }
36
+ ```
37
+
38
+ This allows you to test new SDK features in your staging environment before they reach production.
39
+
11
40
  ## Quick Start
12
41
 
13
42
  ### 1. Wrap your app with AxCloudProvider
@@ -20,7 +49,7 @@ function App() {
20
49
  <AxCloudProvider
21
50
  orgId="org_ba81807b" // Your organization ID
22
51
  apiUrl="https://api.axcloud.app"
23
- apiKey="sk_live_..." // API key from Settings → API Keys
52
+ apiKey="pk_live_..." // Public API key from Settings → API Keys
24
53
  auth0={{
25
54
  domain: "auth.axcloud.app",
26
55
  clientId: "your-client-id",
package/dist/index.d.mts CHANGED
@@ -37,6 +37,11 @@ declare class AxCloudClient {
37
37
  * Build URL with query parameters
38
38
  */
39
39
  private buildUrl;
40
+ /**
41
+ * API key should only be sent to public endpoints.
42
+ * This avoids leaking public keys to authenticated/internal routes.
43
+ */
44
+ private shouldAttachApiKey;
40
45
  /**
41
46
  * Make an authenticated request
42
47
  */
@@ -761,7 +766,7 @@ declare function useOrg(): {
761
766
  * @example
762
767
  * // With pagination
763
768
  * ```tsx
764
- * const { data, hasMore, fetchNextPage } = useNews({
769
+ * const { data, hasMore, refetch } = useNews({
765
770
  * pageSize: 10,
766
771
  * status: 'published'
767
772
  * })
package/dist/index.d.ts CHANGED
@@ -37,6 +37,11 @@ declare class AxCloudClient {
37
37
  * Build URL with query parameters
38
38
  */
39
39
  private buildUrl;
40
+ /**
41
+ * API key should only be sent to public endpoints.
42
+ * This avoids leaking public keys to authenticated/internal routes.
43
+ */
44
+ private shouldAttachApiKey;
40
45
  /**
41
46
  * Make an authenticated request
42
47
  */
@@ -761,7 +766,7 @@ declare function useOrg(): {
761
766
  * @example
762
767
  * // With pagination
763
768
  * ```tsx
764
- * const { data, hasMore, fetchNextPage } = useNews({
769
+ * const { data, hasMore, refetch } = useNews({
765
770
  * pageSize: 10,
766
771
  * status: 'published'
767
772
  * })
package/dist/index.js CHANGED
@@ -118,6 +118,14 @@ var AxCloudClient = class {
118
118
  }
119
119
  return url.toString();
120
120
  }
121
+ /**
122
+ * API key should only be sent to public endpoints.
123
+ * This avoids leaking public keys to authenticated/internal routes.
124
+ */
125
+ shouldAttachApiKey(path) {
126
+ const normalized = path.startsWith("/") ? path : `/${path}`;
127
+ return normalized.startsWith("/public/") || normalized.startsWith("/api/public/") || normalized.startsWith("/content/public/");
128
+ }
121
129
  /**
122
130
  * Make an authenticated request
123
131
  */
@@ -130,7 +138,7 @@ var AxCloudClient = class {
130
138
  const customHeadersRecord = customHeaders;
131
139
  Object.assign(headers, customHeadersRecord);
132
140
  }
133
- if (this.apiKey) {
141
+ if (this.apiKey && this.shouldAttachApiKey(path)) {
134
142
  headers["x-api-key"] = this.apiKey;
135
143
  }
136
144
  if (this.getAccessToken) {
@@ -249,30 +257,55 @@ function userHasAllPermissions(user, permissions) {
249
257
 
250
258
  // src/core/forms.ts
251
259
  async function submitForm(apiUrl, submission) {
252
- const url = `${apiUrl.replace(/\/$/, "")}/public/submit`;
253
- const response = await fetch(url, {
254
- method: "POST",
255
- headers: {
256
- "Content-Type": "application/json"
257
- },
258
- body: JSON.stringify({
259
- api_key: submission.apiKey,
260
- site: submission.site,
261
- form_type: submission.formType,
262
- email: submission.email,
263
- data: submission.data || {}
264
- })
265
- });
266
- if (!response.ok) {
267
- const error = await response.json().catch(() => ({ message: "Form submission failed" }));
268
- throw new Error(error.message || "Form submission failed");
260
+ const baseUrl = apiUrl.replace(/\/$/, "");
261
+ try {
262
+ const response = await fetch(`${baseUrl}/public/submit`, {
263
+ method: "POST",
264
+ headers: {
265
+ "Content-Type": "application/json"
266
+ },
267
+ body: JSON.stringify({
268
+ api_key: submission.apiKey,
269
+ site: submission.site,
270
+ form_type: submission.formType,
271
+ email: submission.email,
272
+ data: submission.data || {}
273
+ })
274
+ });
275
+ if (!response.ok) {
276
+ const error = await response.json().catch(() => ({ message: "Form submission failed" }));
277
+ throw new Error(error.message || "Form submission failed");
278
+ }
279
+ const result = await response.json();
280
+ return {
281
+ success: result.success,
282
+ message: result.message,
283
+ submissionId: result.submission_id
284
+ };
285
+ } catch (primaryError) {
286
+ try {
287
+ const notifyResponse = await fetch(`${baseUrl}/public/notify`, {
288
+ method: "POST",
289
+ headers: { "Content-Type": "application/json" },
290
+ body: JSON.stringify({
291
+ api_key: submission.apiKey,
292
+ site: submission.site,
293
+ form_type: submission.formType,
294
+ email: submission.email
295
+ })
296
+ });
297
+ if (notifyResponse.ok) {
298
+ const result = await notifyResponse.json();
299
+ return {
300
+ success: true,
301
+ message: result.message || "Thanks! We'll notify you when we launch.",
302
+ submissionId: "fallback"
303
+ };
304
+ }
305
+ } catch {
306
+ }
307
+ throw primaryError;
269
308
  }
270
- const result = await response.json();
271
- return {
272
- success: result.success,
273
- message: result.message,
274
- submissionId: result.submission_id
275
- };
276
309
  }
277
310
  function createFormHelper(config) {
278
311
  return {
@@ -572,8 +605,8 @@ function useUser() {
572
605
  };
573
606
  }
574
607
  function useOrg() {
575
- const { client, orgId, stage, config } = useAxCloudContext();
576
- const usePublicEndpoint = Boolean(config.apiKey);
608
+ const { client, orgId, stage, isPublicMode } = useAxCloudContext();
609
+ const usePublicEndpoint = isPublicMode;
577
610
  const query = reactQuery.useQuery({
578
611
  queryKey: ["axcloud", "organization", orgId, stage, usePublicEndpoint],
579
612
  queryFn: async () => {
@@ -632,7 +665,20 @@ function useNews(options = {}) {
632
665
  } = options;
633
666
  const usePublicEndpoint = isPublicMode || publicOnly;
634
667
  const query = reactQuery.useQuery({
635
- queryKey: ["axcloud", "news", orgId, usePublicEndpoint, options],
668
+ queryKey: [
669
+ "axcloud",
670
+ "news",
671
+ orgId,
672
+ usePublicEndpoint,
673
+ page,
674
+ pageSize,
675
+ status,
676
+ visibility,
677
+ sectionId,
678
+ search,
679
+ sortBy,
680
+ sortOrder
681
+ ],
636
682
  queryFn: async () => {
637
683
  const endpoint = usePublicEndpoint ? `/content/public/${orgId}/news` : `/content/members/${orgId}/news`;
638
684
  return client.get(endpoint, {
@@ -663,6 +709,7 @@ var MEMBER_ENDPOINT_TYPES = ["news", "pages", "events"];
663
709
  function useContent(contentType, options = {}) {
664
710
  const { client, orgId, isPublicMode } = useAxCloudContext();
665
711
  const {
712
+ publicOnly = false,
666
713
  page = 1,
667
714
  pageSize = 20,
668
715
  status = "published",
@@ -673,9 +720,24 @@ function useContent(contentType, options = {}) {
673
720
  sortOrder = "desc"
674
721
  } = options;
675
722
  const query = reactQuery.useQuery({
676
- queryKey: ["axcloud", "content", orgId, contentType, isPublicMode, options],
723
+ queryKey: [
724
+ "axcloud",
725
+ "content",
726
+ orgId,
727
+ contentType,
728
+ isPublicMode,
729
+ publicOnly,
730
+ page,
731
+ pageSize,
732
+ status,
733
+ visibility,
734
+ sectionId,
735
+ search,
736
+ sortBy,
737
+ sortOrder
738
+ ],
677
739
  queryFn: async () => {
678
- const useMemberEndpoint = !isPublicMode && MEMBER_ENDPOINT_TYPES.includes(contentType);
740
+ const useMemberEndpoint = !isPublicMode && !publicOnly && MEMBER_ENDPOINT_TYPES.includes(contentType);
679
741
  const endpoint = useMemberEndpoint ? `/content/members/${orgId}/${contentType}` : `/content/public/${orgId}/${contentType}`;
680
742
  return client.get(endpoint, {
681
743
  page,
@@ -781,7 +843,7 @@ function useForms(options = {}) {
781
843
  const { client, stage } = useAxCloudContext();
782
844
  const { formType, status, limit = 50, cursor } = options;
783
845
  const query = reactQuery.useQuery({
784
- queryKey: ["axcloud", "forms", "submissions", stage, options],
846
+ queryKey: ["axcloud", "forms", "submissions", stage, formType, status, limit, cursor],
785
847
  queryFn: async () => {
786
848
  const params = { limit };
787
849
  if (formType) params.form_type = formType;
@@ -842,13 +904,13 @@ function useUpdateFormSubmission() {
842
904
  }
843
905
  var LINKED_ACCOUNTS_QUERY_KEY = ["axcloud", "linked-accounts"];
844
906
  function useLinkedAccounts() {
907
+ const { client, stage } = useAxCloudContext();
845
908
  const { isAuthenticated } = useAuth();
846
909
  const queryClient = reactQuery.useQueryClient();
847
910
  const query = reactQuery.useQuery({
848
- queryKey: LINKED_ACCOUNTS_QUERY_KEY,
911
+ queryKey: [...LINKED_ACCOUNTS_QUERY_KEY, stage],
849
912
  queryFn: async () => {
850
- const client = getClient();
851
- return client.get("/api/users/me/identities");
913
+ return client.get(`/${stage}/api/users/me/identities`);
852
914
  },
853
915
  enabled: isAuthenticated,
854
916
  staleTime: 1e3 * 60 * 5
@@ -856,9 +918,8 @@ function useLinkedAccounts() {
856
918
  });
857
919
  const linkMutation = reactQuery.useMutation({
858
920
  mutationFn: async (provider) => {
859
- const client = getClient();
860
921
  const response = await client.post(
861
- `/api/users/me/identities/${provider}/link`,
922
+ `/${stage}/api/users/me/identities/${provider}/link`,
862
923
  {}
863
924
  );
864
925
  if (response.authorization_url) {
@@ -872,9 +933,8 @@ function useLinkedAccounts() {
872
933
  });
873
934
  const unlinkMutation = reactQuery.useMutation({
874
935
  mutationFn: async (provider) => {
875
- const client = getClient();
876
936
  return client.delete(
877
- `/api/users/me/identities/${provider}`
937
+ `/${stage}/api/users/me/identities/${provider}`
878
938
  );
879
939
  },
880
940
  onSuccess: () => {