@axcode/axcloud-sdk 0.1.7-dev.580a922 → 0.1.7-dev.bf490b9
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 +200 -124
- package/dist/index.d.mts +78 -3
- package/dist/index.d.ts +78 -3
- package/dist/index.js +339 -42
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +334 -44
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -12,10 +12,10 @@ npm install @axcode/axcloud-sdk
|
|
|
12
12
|
|
|
13
13
|
The SDK is published with two tags for different environments:
|
|
14
14
|
|
|
15
|
-
| Tag
|
|
16
|
-
|
|
17
|
-
| `latest` | Production
|
|
18
|
-
| `next`
|
|
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
19
|
|
|
20
20
|
**Recommended setup:**
|
|
21
21
|
|
|
@@ -42,46 +42,51 @@ This allows you to test new SDK features in your staging environment before they
|
|
|
42
42
|
### 1. Wrap your app with AxCloudProvider
|
|
43
43
|
|
|
44
44
|
```tsx
|
|
45
|
-
import { AxCloudProvider } from '@axcode/axcloud-sdk'
|
|
45
|
+
import { AxCloudProvider } from '@axcode/axcloud-sdk';
|
|
46
46
|
|
|
47
47
|
function App() {
|
|
48
48
|
return (
|
|
49
49
|
<AxCloudProvider
|
|
50
|
-
orgId="org_ba81807b"
|
|
50
|
+
orgId="org_ba81807b" // Your organization ID
|
|
51
51
|
apiUrl="https://api.axcloud.app"
|
|
52
|
-
apiKey="pk_live_..."
|
|
52
|
+
apiKey="pk_live_..." // Public API key from Settings → API Keys
|
|
53
53
|
auth0={{
|
|
54
|
-
domain:
|
|
55
|
-
clientId:
|
|
56
|
-
redirectUri: window.location.origin
|
|
54
|
+
domain: 'auth.axcloud.app',
|
|
55
|
+
clientId: 'your-client-id',
|
|
56
|
+
redirectUri: window.location.origin,
|
|
57
|
+
organization: 'org_auth0_...', // Optional Auth0 Organization ID for invite-only member sites
|
|
57
58
|
}}
|
|
58
59
|
>
|
|
59
60
|
<Router>
|
|
60
61
|
<Routes />
|
|
61
62
|
</Router>
|
|
62
63
|
</AxCloudProvider>
|
|
63
|
-
)
|
|
64
|
+
);
|
|
64
65
|
}
|
|
65
66
|
```
|
|
66
67
|
|
|
68
|
+
`orgId` is the AxCloud organization ID used by CMS APIs, for example `org_ba81807b`.
|
|
69
|
+
`auth0.organization` is the Auth0 Organization ID used during login, for example `org_o960yILn77d4vY6u`.
|
|
70
|
+
Set `auth0.organization` for invite-only customer member sites so Universal Login is scoped to that customer organization.
|
|
71
|
+
|
|
67
72
|
### 2. Fetch content
|
|
68
73
|
|
|
69
74
|
```tsx
|
|
70
|
-
import { useNews } from '@axcode/axcloud-sdk'
|
|
75
|
+
import { useNews } from '@axcode/axcloud-sdk';
|
|
71
76
|
|
|
72
77
|
function NewsPage() {
|
|
73
|
-
const { data: news, isLoading, isError } = useNews()
|
|
78
|
+
const { data: news, isLoading, isError } = useNews();
|
|
74
79
|
|
|
75
|
-
if (isLoading) return <Spinner
|
|
76
|
-
if (isError) return <Error
|
|
80
|
+
if (isLoading) return <Spinner />;
|
|
81
|
+
if (isError) return <Error />;
|
|
77
82
|
|
|
78
83
|
return (
|
|
79
84
|
<ul>
|
|
80
|
-
{news.map(item => (
|
|
85
|
+
{news.map((item) => (
|
|
81
86
|
<li key={item.id}>{item.title}</li>
|
|
82
87
|
))}
|
|
83
88
|
</ul>
|
|
84
|
-
)
|
|
89
|
+
);
|
|
85
90
|
}
|
|
86
91
|
```
|
|
87
92
|
|
|
@@ -90,31 +95,31 @@ function NewsPage() {
|
|
|
90
95
|
Pages and news can be stored once in AxCloud with translations and requested per language:
|
|
91
96
|
|
|
92
97
|
```tsx
|
|
93
|
-
import { useContentItem, useNews } from '@axcode/axcloud-sdk'
|
|
98
|
+
import { useContentItem, useNews } from '@axcode/axcloud-sdk';
|
|
94
99
|
|
|
95
100
|
function AboutPage({ locale }: { locale: 'sv' | 'en' }) {
|
|
96
101
|
const { data: page } = useContentItem('pages', 'about-us', {
|
|
97
102
|
lang: locale,
|
|
98
103
|
publicOnly: true,
|
|
99
|
-
})
|
|
104
|
+
});
|
|
100
105
|
|
|
101
|
-
return <h1>{page?.title}</h1
|
|
106
|
+
return <h1>{page?.title}</h1>;
|
|
102
107
|
}
|
|
103
108
|
|
|
104
109
|
function NewsList({ locale }: { locale: 'sv' | 'en' }) {
|
|
105
|
-
const { data: news } = useNews({ lang: locale })
|
|
110
|
+
const { data: news } = useNews({ lang: locale });
|
|
106
111
|
|
|
107
|
-
return news.map(item => <article key={item.id}>{item.title}</article>)
|
|
112
|
+
return news.map((item) => <article key={item.id}>{item.title}</article>);
|
|
108
113
|
}
|
|
109
114
|
```
|
|
110
115
|
|
|
111
116
|
### 3. Add authentication
|
|
112
117
|
|
|
113
118
|
```tsx
|
|
114
|
-
import { useAuth, LoginButton, LogoutButton, ProtectedRoute } from '@axcode/axcloud-sdk'
|
|
119
|
+
import { useAuth, LoginButton, LogoutButton, ProtectedRoute } from '@axcode/axcloud-sdk';
|
|
115
120
|
|
|
116
121
|
function Header() {
|
|
117
|
-
const { isAuthenticated, user } = useAuth()
|
|
122
|
+
const { isAuthenticated, user } = useAuth();
|
|
118
123
|
|
|
119
124
|
return (
|
|
120
125
|
<header>
|
|
@@ -127,7 +132,7 @@ function Header() {
|
|
|
127
132
|
<LoginButton>Sign In</LoginButton>
|
|
128
133
|
)}
|
|
129
134
|
</header>
|
|
130
|
-
)
|
|
135
|
+
);
|
|
131
136
|
}
|
|
132
137
|
|
|
133
138
|
function MemberArea() {
|
|
@@ -135,87 +140,158 @@ function MemberArea() {
|
|
|
135
140
|
<ProtectedRoute>
|
|
136
141
|
<MemberContent />
|
|
137
142
|
</ProtectedRoute>
|
|
138
|
-
)
|
|
143
|
+
);
|
|
139
144
|
}
|
|
140
145
|
```
|
|
141
146
|
|
|
147
|
+
The SDK keeps verification UI inside the product site. Render your own branded
|
|
148
|
+
notice when `needsEmailVerification` is true:
|
|
149
|
+
|
|
150
|
+
```tsx
|
|
151
|
+
const { needsEmailVerification, requestEmailVerification, refreshUser } = useAuth();
|
|
152
|
+
|
|
153
|
+
if (needsEmailVerification) {
|
|
154
|
+
// Render the product's own verification notice.
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
await requestEmailVerification({ returnTo: window.location.href });
|
|
158
|
+
await refreshUser();
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
The API resolves the recipient and organization from the authenticated token.
|
|
162
|
+
Return URLs are restricted to the organization's configured member site.
|
|
163
|
+
|
|
164
|
+
### 3b. Add privacy-safe analytics
|
|
165
|
+
|
|
166
|
+
Analytics is optional and provider-independent. AxCloud resolves the public
|
|
167
|
+
tracker configuration from the organization, site key, deployment stage, and
|
|
168
|
+
current hostname. SDK consumers never receive the Umami API key.
|
|
169
|
+
|
|
170
|
+
```tsx
|
|
171
|
+
import { AxCloudProvider, useAnalytics } from '@axcode/axcloud-sdk';
|
|
172
|
+
|
|
173
|
+
<AxCloudProvider
|
|
174
|
+
orgId="org_ba81807b"
|
|
175
|
+
apiUrl="https://api.axcloud.app"
|
|
176
|
+
stage="prod"
|
|
177
|
+
analytics={{
|
|
178
|
+
enabled: analyticsConsent === 'accepted',
|
|
179
|
+
siteKey: 'member-site',
|
|
180
|
+
normalizePath: (path) => path.replace(/^\/member\/news\/[^/]+/, '/member/news/:id'),
|
|
181
|
+
}}
|
|
182
|
+
auth0={auth0Config}
|
|
183
|
+
>
|
|
184
|
+
<App />
|
|
185
|
+
</AxCloudProvider>;
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
The SDK tracks SPA page changes after the tracker is ready. Query strings and
|
|
189
|
+
fragments are removed, common opaque IDs are replaced with `:id`, Do Not Track
|
|
190
|
+
is honored, and localhost collection is disabled unless `trackLocalhost: true`
|
|
191
|
+
is explicitly set.
|
|
192
|
+
|
|
193
|
+
Custom events must be allowlisted in the AxCloud analytics site configuration:
|
|
194
|
+
|
|
195
|
+
```tsx
|
|
196
|
+
function NewsArticle() {
|
|
197
|
+
const analytics = useAnalytics();
|
|
198
|
+
|
|
199
|
+
const handleOpen = () => {
|
|
200
|
+
analytics.track('news_opened', { surface: 'member_news' });
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Only send coarse enums, booleans, and counts. The SDK rejects sensitive
|
|
206
|
+
property names and email-like values, but applications remain responsible for
|
|
207
|
+
never supplying names, email addresses, user IDs, document titles, search text,
|
|
208
|
+
chat text, or other personal/content data. Production trackers should only be
|
|
209
|
+
enabled after the product's privacy notice and Content Security Policy have
|
|
210
|
+
been reviewed.
|
|
211
|
+
|
|
142
212
|
### 4. Use sections
|
|
143
213
|
|
|
144
214
|
```tsx
|
|
145
|
-
import { useMySection } from '@axcode/axcloud-sdk'
|
|
215
|
+
import { useMySection } from '@axcode/axcloud-sdk';
|
|
146
216
|
|
|
147
217
|
function MySectionPage() {
|
|
148
|
-
const { section, members, isLoading } = useMySection()
|
|
218
|
+
const { section, members, isLoading } = useMySection();
|
|
149
219
|
|
|
150
|
-
if (isLoading) return <Spinner
|
|
151
|
-
if (!section) return <p>You are not assigned to a section</p
|
|
220
|
+
if (isLoading) return <Spinner />;
|
|
221
|
+
if (!section) return <p>You are not assigned to a section</p>;
|
|
152
222
|
|
|
153
223
|
return (
|
|
154
224
|
<div>
|
|
155
225
|
<h1>{section.name}</h1>
|
|
156
226
|
<h2>Members ({members.length})</h2>
|
|
157
227
|
<ul>
|
|
158
|
-
{members.map(m =>
|
|
228
|
+
{members.map((m) => (
|
|
229
|
+
<li key={m.id}>{m.name}</li>
|
|
230
|
+
))}
|
|
159
231
|
</ul>
|
|
160
232
|
</div>
|
|
161
|
-
)
|
|
233
|
+
);
|
|
162
234
|
}
|
|
163
235
|
```
|
|
164
236
|
|
|
165
237
|
### 4b. Filter content by section
|
|
166
238
|
|
|
167
239
|
```tsx
|
|
168
|
-
import { useNews, useMySection } from '@axcode/axcloud-sdk'
|
|
240
|
+
import { useNews, useMySection } from '@axcode/axcloud-sdk';
|
|
169
241
|
|
|
170
242
|
function SectionNews() {
|
|
171
|
-
const { sectionId } = useMySection()
|
|
243
|
+
const { sectionId } = useMySection();
|
|
172
244
|
|
|
173
245
|
// Fetch news for user's section only
|
|
174
|
-
const { data: sectionNews } = useNews({ sectionId })
|
|
246
|
+
const { data: sectionNews } = useNews({ sectionId });
|
|
175
247
|
|
|
176
248
|
// Or fetch all organization news
|
|
177
|
-
const { data: allNews } = useNews()
|
|
249
|
+
const { data: allNews } = useNews();
|
|
178
250
|
|
|
179
251
|
return (
|
|
180
252
|
<div>
|
|
181
253
|
<h2>Section News</h2>
|
|
182
|
-
{sectionNews.map(item =>
|
|
254
|
+
{sectionNews.map((item) => (
|
|
255
|
+
<NewsCard key={item.id} item={item} />
|
|
256
|
+
))}
|
|
183
257
|
|
|
184
258
|
<h2>All Organization News</h2>
|
|
185
|
-
{allNews.map(item =>
|
|
259
|
+
{allNews.map((item) => (
|
|
260
|
+
<NewsCard key={item.id} item={item} />
|
|
261
|
+
))}
|
|
186
262
|
</div>
|
|
187
|
-
)
|
|
263
|
+
);
|
|
188
264
|
}
|
|
189
265
|
```
|
|
190
266
|
|
|
191
267
|
### 5. Fetch organization branding and media
|
|
192
268
|
|
|
193
269
|
```tsx
|
|
194
|
-
import { useBranding, useLogo, useMedia } from '@axcode/axcloud-sdk'
|
|
270
|
+
import { useBranding, useLogo, useMedia } from '@axcode/axcloud-sdk';
|
|
195
271
|
|
|
196
272
|
function Header() {
|
|
197
|
-
const { logoUrl, branding, organizationName } = useBranding()
|
|
273
|
+
const { logoUrl, branding, organizationName } = useBranding();
|
|
198
274
|
|
|
199
275
|
return (
|
|
200
276
|
<header style={{ backgroundColor: branding?.primaryColor }}>
|
|
201
277
|
{logoUrl && <img src={logoUrl} alt={organizationName} />}
|
|
202
278
|
<h1>{organizationName}</h1>
|
|
203
279
|
</header>
|
|
204
|
-
)
|
|
280
|
+
);
|
|
205
281
|
}
|
|
206
282
|
|
|
207
283
|
// Or just the logo
|
|
208
284
|
function Logo() {
|
|
209
|
-
const { logoUrl, isLoading } = useLogo()
|
|
285
|
+
const { logoUrl, isLoading } = useLogo();
|
|
210
286
|
|
|
211
|
-
if (isLoading) return <Skeleton
|
|
212
|
-
return logoUrl ? <img src={logoUrl} alt="Logo" /> : <DefaultLogo
|
|
287
|
+
if (isLoading) return <Skeleton />;
|
|
288
|
+
return logoUrl ? <img src={logoUrl} alt="Logo" /> : <DefaultLogo />;
|
|
213
289
|
}
|
|
214
290
|
|
|
215
291
|
// Fetch specific media file
|
|
216
292
|
function HeroImage() {
|
|
217
|
-
const { mediaUrl } = useMedia('hero-banner.jpg')
|
|
218
|
-
return mediaUrl ? <img src={mediaUrl} alt="Hero" /> : null
|
|
293
|
+
const { mediaUrl } = useMedia('hero-banner.jpg');
|
|
294
|
+
return mediaUrl ? <img src={mediaUrl} alt="Hero" /> : null;
|
|
219
295
|
}
|
|
220
296
|
```
|
|
221
297
|
|
|
@@ -224,26 +300,26 @@ function HeroImage() {
|
|
|
224
300
|
### 6. Fetch public documents
|
|
225
301
|
|
|
226
302
|
```tsx
|
|
227
|
-
import { useDocuments, useDocumentDownload } from '@axcode/axcloud-sdk'
|
|
303
|
+
import { useDocuments, useDocumentDownload } from '@axcode/axcloud-sdk';
|
|
228
304
|
|
|
229
305
|
function PublicDocuments() {
|
|
230
|
-
const { documents, isLoading } = useDocuments({ tags: ['handbook'], limit: 20 })
|
|
306
|
+
const { documents, isLoading } = useDocuments({ tags: ['handbook'], limit: 20 });
|
|
231
307
|
|
|
232
|
-
if (isLoading) return <Skeleton
|
|
308
|
+
if (isLoading) return <Skeleton />;
|
|
233
309
|
return (
|
|
234
310
|
<ul>
|
|
235
|
-
{documents.map(doc => (
|
|
311
|
+
{documents.map((doc) => (
|
|
236
312
|
<li key={doc.document_id}>
|
|
237
313
|
<DocumentLink documentId={doc.document_id} title={doc.title} />
|
|
238
314
|
</li>
|
|
239
315
|
))}
|
|
240
316
|
</ul>
|
|
241
|
-
)
|
|
317
|
+
);
|
|
242
318
|
}
|
|
243
319
|
|
|
244
320
|
function DocumentLink({ documentId, title }: { documentId: string; title: string }) {
|
|
245
|
-
const { documentUrl } = useDocumentDownload(documentId)
|
|
246
|
-
return documentUrl ? <a href={documentUrl}>{title}</a> : <span>{title}</span
|
|
321
|
+
const { documentUrl } = useDocumentDownload(documentId);
|
|
322
|
+
return documentUrl ? <a href={documentUrl}>{title}</a> : <span>{title}</span>;
|
|
247
323
|
}
|
|
248
324
|
```
|
|
249
325
|
|
|
@@ -252,10 +328,10 @@ Only documents with `status: "published"` and `audience: "public"` are exposed t
|
|
|
252
328
|
### 7. Gate features by module
|
|
253
329
|
|
|
254
330
|
```tsx
|
|
255
|
-
import { ModuleGate, useOrg } from '@axcode/axcloud-sdk'
|
|
331
|
+
import { ModuleGate, useOrg } from '@axcode/axcloud-sdk';
|
|
256
332
|
|
|
257
333
|
function Dashboard() {
|
|
258
|
-
const { hasModule } = useOrg()
|
|
334
|
+
const { hasModule } = useOrg();
|
|
259
335
|
|
|
260
336
|
return (
|
|
261
337
|
<div>
|
|
@@ -267,7 +343,7 @@ function Dashboard() {
|
|
|
267
343
|
|
|
268
344
|
{hasModule('ai') && <AIChatWidget />}
|
|
269
345
|
</div>
|
|
270
|
-
)
|
|
346
|
+
);
|
|
271
347
|
}
|
|
272
348
|
```
|
|
273
349
|
|
|
@@ -275,85 +351,85 @@ function Dashboard() {
|
|
|
275
351
|
|
|
276
352
|
### Provider
|
|
277
353
|
|
|
278
|
-
| Component
|
|
279
|
-
|
|
354
|
+
| Component | Description |
|
|
355
|
+
| ----------------- | --------------------------------------------------------------- |
|
|
280
356
|
| `AxCloudProvider` | Main provider that combines Auth0, React Query, and SDK context |
|
|
281
357
|
|
|
282
358
|
### Hooks
|
|
283
359
|
|
|
284
|
-
| Hook
|
|
285
|
-
|
|
286
|
-
| `useAuth()`
|
|
287
|
-
| `useUser()`
|
|
288
|
-
| `useOrg()`
|
|
289
|
-
| `useNews(options?)`
|
|
290
|
-
| `useContent(type, options?)`
|
|
291
|
-
| `useMySection()`
|
|
292
|
-
| `useSections()`
|
|
293
|
-
| `useBranding()`
|
|
294
|
-
| `useLogo()`
|
|
295
|
-
| `useMedia(mediaId)`
|
|
296
|
-
| `useAvatar(userId)`
|
|
297
|
-
| `useMediaList(type)`
|
|
298
|
-
| `useDocuments(options?)`
|
|
299
|
-
| `useDocumentDownload(documentId)` | Fetch a signed document download URL
|
|
360
|
+
| Hook | Description |
|
|
361
|
+
| --------------------------------- | -------------------------------------------------------------------------------------------- |
|
|
362
|
+
| `useAuth()` | Login/logout, email-verification state, resend, and profile refresh |
|
|
363
|
+
| `useUser()` | Fetch current user with role and permissions |
|
|
364
|
+
| `useOrg()` | Fetch organization info and check modules |
|
|
365
|
+
| `useNews(options?)` | Fetch news articles. Returns public + internal + members content when authenticated. |
|
|
366
|
+
| `useContent(type, options?)` | Fetch any content type. For `pages` and `events`, returns member content when authenticated. |
|
|
367
|
+
| `useMySection()` | Fetch current user's section with members |
|
|
368
|
+
| `useSections()` | Fetch all organization sections |
|
|
369
|
+
| `useBranding()` | Fetch organization branding (logo, colors) |
|
|
370
|
+
| `useLogo()` | Fetch organization logo URL |
|
|
371
|
+
| `useMedia(mediaId)` | Fetch specific media file URL |
|
|
372
|
+
| `useAvatar(userId)` | Fetch user avatar URL |
|
|
373
|
+
| `useMediaList(type)` | List media files |
|
|
374
|
+
| `useDocuments(options?)` | List published public documents |
|
|
375
|
+
| `useDocumentDownload(documentId)` | Fetch a signed document download URL |
|
|
300
376
|
|
|
301
377
|
### Content Query Options
|
|
302
378
|
|
|
303
379
|
```typescript
|
|
304
380
|
interface ContentQueryOptions {
|
|
305
|
-
page?: number
|
|
306
|
-
pageSize?: number
|
|
307
|
-
status?: 'draft' | 'published' | 'archived'
|
|
308
|
-
visibility?: 'draft' | 'published' | 'archived'
|
|
309
|
-
audience?: 'public' | 'internal' | 'members' | 'section'
|
|
310
|
-
sectionId?: string
|
|
311
|
-
search?: string
|
|
312
|
-
sortBy?: string
|
|
313
|
-
sortOrder?: 'asc' | 'desc'
|
|
314
|
-
lang?: string
|
|
315
|
-
publicOnly?: boolean
|
|
381
|
+
page?: number; // Page number (default: 1)
|
|
382
|
+
pageSize?: number; // Items per page (default: 20)
|
|
383
|
+
status?: 'draft' | 'published' | 'archived';
|
|
384
|
+
visibility?: 'draft' | 'published' | 'archived';
|
|
385
|
+
audience?: 'public' | 'internal' | 'members' | 'section';
|
|
386
|
+
sectionId?: string; // Filter by specific section
|
|
387
|
+
search?: string; // Search in title/content
|
|
388
|
+
sortBy?: string; // Sort field (default: 'createdAt')
|
|
389
|
+
sortOrder?: 'asc' | 'desc';
|
|
390
|
+
lang?: string; // Return translated title/slug/blocks when available
|
|
391
|
+
publicOnly?: boolean; // Force public endpoint even when authenticated
|
|
316
392
|
}
|
|
317
393
|
```
|
|
318
394
|
|
|
319
395
|
### Components
|
|
320
396
|
|
|
321
|
-
| Component
|
|
322
|
-
|
|
323
|
-
| `<LoginButton>`
|
|
324
|
-
| `<LogoutButton>`
|
|
325
|
-
| `<ProtectedRoute>` | Require authentication to view content
|
|
326
|
-
| `<ModuleGate>`
|
|
397
|
+
| Component | Description |
|
|
398
|
+
| ------------------ | --------------------------------------------- |
|
|
399
|
+
| `<LoginButton>` | Trigger Auth0 login |
|
|
400
|
+
| `<LogoutButton>` | Trigger logout |
|
|
401
|
+
| `<ProtectedRoute>` | Require authentication to view content |
|
|
402
|
+
| `<ModuleGate>` | Conditionally render based on enabled modules |
|
|
327
403
|
|
|
328
404
|
## Advanced Usage
|
|
329
405
|
|
|
330
406
|
### Custom API calls
|
|
331
407
|
|
|
332
408
|
```tsx
|
|
333
|
-
import { useAuth } from '@axcode/axcloud-sdk'
|
|
409
|
+
import { useAuth } from '@axcode/axcloud-sdk';
|
|
334
410
|
|
|
335
411
|
function MyComponent() {
|
|
336
|
-
const { getAccessToken } = useAuth()
|
|
412
|
+
const { getAccessToken } = useAuth();
|
|
337
413
|
|
|
338
414
|
const fetchCustomData = async () => {
|
|
339
|
-
const token = await getAccessToken()
|
|
415
|
+
const token = await getAccessToken();
|
|
340
416
|
const response = await fetch('/api/custom', {
|
|
341
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
342
|
-
})
|
|
343
|
-
return response.json()
|
|
344
|
-
}
|
|
417
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
418
|
+
});
|
|
419
|
+
return response.json();
|
|
420
|
+
};
|
|
345
421
|
}
|
|
346
422
|
```
|
|
347
423
|
|
|
348
424
|
### Access the underlying Auth0 client
|
|
349
425
|
|
|
350
426
|
```tsx
|
|
351
|
-
const { getAuth0Client } = useAuth()
|
|
352
|
-
const auth0 = getAuth0Client()
|
|
427
|
+
const { getAuth0Client } = useAuth();
|
|
428
|
+
const auth0 = getAuth0Client();
|
|
353
429
|
|
|
354
430
|
// Full Auth0 API available
|
|
355
|
-
await auth0.getAccessTokenSilently()
|
|
356
|
-
await auth0.getIdTokenClaims()
|
|
431
|
+
await auth0.getAccessTokenSilently();
|
|
432
|
+
await auth0.getIdTokenClaims();
|
|
357
433
|
```
|
|
358
434
|
|
|
359
435
|
## Security Model
|
|
@@ -362,58 +438,58 @@ await auth0.getIdTokenClaims()
|
|
|
362
438
|
|
|
363
439
|
The SDK uses two types of credentials:
|
|
364
440
|
|
|
365
|
-
1. **API Key** (`apiKey` prop): A
|
|
441
|
+
1. **API Key** (`apiKey` prop): A _public identifier_ for your organization
|
|
366
442
|
- Safe to include in client-side code
|
|
367
443
|
- Used for public content endpoints
|
|
368
444
|
- Backend validates and rate-limits based on API key
|
|
369
445
|
- Without a valid API key, requests return 401
|
|
370
446
|
|
|
371
|
-
2. **Authentication Token** (via Auth0): A
|
|
447
|
+
2. **Authentication Token** (via Auth0): A _secret_ JWT token
|
|
372
448
|
- Automatically managed by the SDK
|
|
373
449
|
- Required for member-only content and user data
|
|
374
450
|
- Never exposed in client code
|
|
375
451
|
|
|
376
452
|
### What the API Key Protects
|
|
377
453
|
|
|
378
|
-
| Without API Key | With API Key
|
|
379
|
-
|
|
380
|
-
| Nothing
|
|
381
|
-
|
|
|
382
|
-
|
|
|
383
|
-
|
|
|
454
|
+
| Without API Key | With API Key | With Auth Token |
|
|
455
|
+
| --------------- | --------------------- | --------------- |
|
|
456
|
+
| Nothing | Public news/content | Member content |
|
|
457
|
+
| | Organization branding | User profile |
|
|
458
|
+
| | Public media | Linked accounts |
|
|
459
|
+
| | | Admin functions |
|
|
384
460
|
|
|
385
461
|
### Endpoint Security
|
|
386
462
|
|
|
387
|
-
| Endpoint Type
|
|
388
|
-
|
|
389
|
-
| Public content | API key (x-api-key header) | `/content/public/{orgId}/news`
|
|
390
|
-
| Member content | JWT Bearer token
|
|
391
|
-
| User data
|
|
392
|
-
| Admin
|
|
463
|
+
| Endpoint Type | Authentication | Example |
|
|
464
|
+
| -------------- | -------------------------- | ------------------------------- |
|
|
465
|
+
| Public content | API key (x-api-key header) | `/content/public/{orgId}/news` |
|
|
466
|
+
| Member content | JWT Bearer token | `/content/members/{orgId}/news` |
|
|
467
|
+
| User data | JWT Bearer token | `/api/users/me` |
|
|
468
|
+
| Admin | JWT Bearer token + role | `/api/admin/forms/submissions` |
|
|
393
469
|
|
|
394
470
|
## Error Handling
|
|
395
471
|
|
|
396
472
|
```tsx
|
|
397
|
-
import { useNews, isAxCloudError } from '@axcode/axcloud-sdk'
|
|
473
|
+
import { useNews, isAxCloudError } from '@axcode/axcloud-sdk';
|
|
398
474
|
|
|
399
475
|
function NewsPage() {
|
|
400
|
-
const { data, error, isError } = useNews()
|
|
476
|
+
const { data, error, isError } = useNews();
|
|
401
477
|
|
|
402
478
|
if (isError) {
|
|
403
479
|
if (isAxCloudError(error)) {
|
|
404
480
|
switch (error.code) {
|
|
405
481
|
case 'UNAUTHORIZED':
|
|
406
|
-
return <LoginPrompt
|
|
482
|
+
return <LoginPrompt />;
|
|
407
483
|
case 'NOT_FOUND':
|
|
408
|
-
return <NotFound
|
|
484
|
+
return <NotFound />;
|
|
409
485
|
default:
|
|
410
|
-
return <Error message={error.message}
|
|
486
|
+
return <Error message={error.message} />;
|
|
411
487
|
}
|
|
412
488
|
}
|
|
413
|
-
return <Error message="Something went wrong"
|
|
489
|
+
return <Error message="Something went wrong" />;
|
|
414
490
|
}
|
|
415
491
|
|
|
416
|
-
return <NewsList items={data}
|
|
492
|
+
return <NewsList items={data} />;
|
|
417
493
|
}
|
|
418
494
|
```
|
|
419
495
|
|