@cstar.help/react 0.9.0 → 0.10.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 +64 -1
- package/dist/index.cjs +63 -16
- package/dist/index.d.cts +35 -16
- package/dist/index.d.ts +35 -16
- package/dist/index.js +74 -27
- package/package.json +7 -6
package/README.md
CHANGED
|
@@ -300,11 +300,74 @@ function CommunitySearch() {
|
|
|
300
300
|
| `useLibraryClient()` | `LibraryClient` | Raw client from nearest `CStarLibraryProvider` |
|
|
301
301
|
| `useCommunityClient()` | `CommunityClient` | Raw client from nearest `CStarCommunityProvider` |
|
|
302
302
|
|
|
303
|
+
## Using with Next.js App Router (RSC)
|
|
304
|
+
|
|
305
|
+
The dashboard provider (`CStarDashboardProvider`) wraps a `CStarClient`,
|
|
306
|
+
which is a class instance — and React Server Components can't serialize
|
|
307
|
+
class instances across the server/client boundary. The provider therefore
|
|
308
|
+
takes one of two prop shapes:
|
|
309
|
+
|
|
310
|
+
**Recommended — pass a plain config object.** The provider builds (and
|
|
311
|
+
disposes) the client inside the `'use client'` boundary, so nothing
|
|
312
|
+
class-shaped crosses the RSC boundary:
|
|
313
|
+
|
|
314
|
+
```tsx
|
|
315
|
+
// app/providers.tsx
|
|
316
|
+
'use client';
|
|
317
|
+
import { CStarDashboardProvider } from '@cstar.help/react';
|
|
318
|
+
|
|
319
|
+
export function Providers({ children }: { children: React.ReactNode }) {
|
|
320
|
+
return (
|
|
321
|
+
<CStarDashboardProvider
|
|
322
|
+
config={{
|
|
323
|
+
apiKey: process.env.NEXT_PUBLIC_CSTAR_PUBLISHABLE_KEY!,
|
|
324
|
+
teamId: process.env.NEXT_PUBLIC_CSTAR_TEAM_ID!
|
|
325
|
+
}}
|
|
326
|
+
>
|
|
327
|
+
{children}
|
|
328
|
+
</CStarDashboardProvider>
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
Use the `apiKey` field for a publishable key (`pk_live_*` / `pk_test_*`),
|
|
334
|
+
NOT a secret key — the bundle ships to the browser. For server-only flows
|
|
335
|
+
(an admin route handler, a server action), construct `CStarClient` with the
|
|
336
|
+
secret key inline; never thread it into a provider.
|
|
337
|
+
|
|
338
|
+
**Legacy — pass a pre-built client (deprecated).** If you already have a
|
|
339
|
+
client instance you want to share, the `client` prop still works, but you
|
|
340
|
+
have to construct it inside `'use client'` and you take on responsibility
|
|
341
|
+
for tearing down the realtime subscription:
|
|
342
|
+
|
|
343
|
+
```tsx
|
|
344
|
+
'use client';
|
|
345
|
+
import { useMemo } from 'react';
|
|
346
|
+
import { CStarClient } from '@cstar.help/js';
|
|
347
|
+
import { CStarDashboardProvider } from '@cstar.help/react';
|
|
348
|
+
|
|
349
|
+
export function Providers({ children }) {
|
|
350
|
+
const client = useMemo(
|
|
351
|
+
() =>
|
|
352
|
+
new CStarClient({
|
|
353
|
+
apiKey: process.env.NEXT_PUBLIC_CSTAR_PUBLISHABLE_KEY!,
|
|
354
|
+
teamId: process.env.NEXT_PUBLIC_CSTAR_TEAM_ID!
|
|
355
|
+
}),
|
|
356
|
+
[]
|
|
357
|
+
);
|
|
358
|
+
return <CStarDashboardProvider client={client}>{children}</CStarDashboardProvider>;
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
Either way, mark the file `'use client'`. Server components can sit above
|
|
363
|
+
the provider in the tree — only the provider boundary needs to be a client
|
|
364
|
+
component.
|
|
365
|
+
|
|
303
366
|
## Requirements
|
|
304
367
|
|
|
305
368
|
- React 18+
|
|
306
369
|
- @cstar.help/js 0.1.0+
|
|
307
|
-
- Node.js
|
|
370
|
+
- Node.js 22+ for SSR with `@cstar.help/js` 0.17
|
|
308
371
|
|
|
309
372
|
## License
|
|
310
373
|
|
package/dist/index.cjs
CHANGED
|
@@ -107,9 +107,14 @@ function useChat() {
|
|
|
107
107
|
const [isRealtimeReady, setIsRealtimeReady] = (0, import_react3.useState)(false);
|
|
108
108
|
const [error, setError] = (0, import_react3.useState)(null);
|
|
109
109
|
(0, import_react3.useEffect)(() => {
|
|
110
|
-
const
|
|
111
|
-
if (
|
|
112
|
-
|
|
110
|
+
const subscribe = client.subscribe?.bind(client);
|
|
111
|
+
if (!subscribe) return;
|
|
112
|
+
return subscribe(() => {
|
|
113
|
+
const mode = client.getAuthMode();
|
|
114
|
+
setAuthMode(mode);
|
|
115
|
+
setIsAuthenticated(mode !== null);
|
|
116
|
+
});
|
|
117
|
+
}, [client]);
|
|
113
118
|
const wireRealtime = (0, import_react3.useCallback)(() => {
|
|
114
119
|
client.waitForRealtime().then(() => setIsRealtimeReady(true)).catch(() => {
|
|
115
120
|
});
|
|
@@ -199,7 +204,7 @@ function useTickets() {
|
|
|
199
204
|
try {
|
|
200
205
|
const result = await client.tickets.list();
|
|
201
206
|
if (mountedRef.current) {
|
|
202
|
-
setTickets(result.
|
|
207
|
+
setTickets(result.data);
|
|
203
208
|
setHasMore(result.hasMore);
|
|
204
209
|
}
|
|
205
210
|
} catch (err) {
|
|
@@ -241,36 +246,48 @@ function useMessages(ticketId) {
|
|
|
241
246
|
const [error, setError] = (0, import_react5.useState)(null);
|
|
242
247
|
const mountedRef = (0, import_react5.useRef)(true);
|
|
243
248
|
const seenIdsRef = (0, import_react5.useRef)(/* @__PURE__ */ new Set());
|
|
249
|
+
const requestRef = (0, import_react5.useRef)(0);
|
|
250
|
+
const scopeRef = (0, import_react5.useRef)(0);
|
|
251
|
+
const optimisticRef = (0, import_react5.useRef)(0);
|
|
244
252
|
const refresh = (0, import_react5.useCallback)(async () => {
|
|
245
253
|
if (!ticketId) return;
|
|
254
|
+
const request = ++requestRef.current;
|
|
246
255
|
setIsLoading(true);
|
|
247
256
|
setError(null);
|
|
248
257
|
try {
|
|
249
258
|
const msgs = await client.messages.list(ticketId);
|
|
250
|
-
if (mountedRef.current) {
|
|
259
|
+
if (mountedRef.current && request === requestRef.current) {
|
|
251
260
|
seenIdsRef.current = new Set(msgs.map((m) => m.id));
|
|
252
261
|
setMessages(msgs);
|
|
253
262
|
}
|
|
254
263
|
} catch (err) {
|
|
255
|
-
if (mountedRef.current) {
|
|
264
|
+
if (mountedRef.current && request === requestRef.current) {
|
|
256
265
|
setError(err instanceof Error ? err : new Error(String(err)));
|
|
257
266
|
}
|
|
258
267
|
} finally {
|
|
259
|
-
if (mountedRef.current) {
|
|
268
|
+
if (mountedRef.current && request === requestRef.current) {
|
|
260
269
|
setIsLoading(false);
|
|
261
270
|
}
|
|
262
271
|
}
|
|
263
272
|
}, [client, ticketId]);
|
|
264
273
|
(0, import_react5.useEffect)(() => {
|
|
265
274
|
mountedRef.current = true;
|
|
275
|
+
const scope = ++scopeRef.current;
|
|
276
|
+
seenIdsRef.current = /* @__PURE__ */ new Set();
|
|
277
|
+
setMessages([]);
|
|
278
|
+
setError(null);
|
|
266
279
|
if (!ticketId) {
|
|
267
280
|
setMessages([]);
|
|
268
281
|
setIsLoading(false);
|
|
269
|
-
return
|
|
282
|
+
return () => {
|
|
283
|
+
mountedRef.current = false;
|
|
284
|
+
requestRef.current++;
|
|
285
|
+
scopeRef.current++;
|
|
286
|
+
};
|
|
270
287
|
}
|
|
271
288
|
refresh();
|
|
272
289
|
const unsubscribe = client.onMessage(ticketId, (msg) => {
|
|
273
|
-
if (mountedRef.current && !seenIdsRef.current.has(msg.id)) {
|
|
290
|
+
if (mountedRef.current && scope === scopeRef.current && !seenIdsRef.current.has(msg.id)) {
|
|
274
291
|
seenIdsRef.current.add(msg.id);
|
|
275
292
|
setMessages((prev) => {
|
|
276
293
|
const optimisticIdx = prev.findIndex(
|
|
@@ -287,14 +304,17 @@ function useMessages(ticketId) {
|
|
|
287
304
|
});
|
|
288
305
|
return () => {
|
|
289
306
|
mountedRef.current = false;
|
|
307
|
+
requestRef.current++;
|
|
308
|
+
scopeRef.current++;
|
|
290
309
|
unsubscribe();
|
|
291
310
|
};
|
|
292
311
|
}, [client, ticketId, refresh]);
|
|
293
312
|
const send = (0, import_react5.useCallback)(
|
|
294
313
|
async (content) => {
|
|
295
314
|
if (!ticketId) throw new Error("No ticket selected");
|
|
315
|
+
const scope = scopeRef.current;
|
|
296
316
|
const optimistic = {
|
|
297
|
-
id: `optimistic-${
|
|
317
|
+
id: `optimistic-${++optimisticRef.current}`,
|
|
298
318
|
content,
|
|
299
319
|
sender: "customer",
|
|
300
320
|
sender_name: "",
|
|
@@ -305,11 +325,14 @@ function useMessages(ticketId) {
|
|
|
305
325
|
setMessages((prev) => [...prev, optimistic]);
|
|
306
326
|
try {
|
|
307
327
|
const msg = await client.messages.send(ticketId, content);
|
|
328
|
+
if (!mountedRef.current || scope !== scopeRef.current) return msg;
|
|
308
329
|
seenIdsRef.current.add(msg.id);
|
|
309
330
|
setMessages((prev) => prev.map((m) => m.id === optimistic.id ? msg : m));
|
|
310
331
|
return msg;
|
|
311
332
|
} catch (err) {
|
|
312
|
-
|
|
333
|
+
if (mountedRef.current && scope === scopeRef.current) {
|
|
334
|
+
setMessages((prev) => prev.filter((m) => m.id !== optimistic.id));
|
|
335
|
+
}
|
|
313
336
|
throw err;
|
|
314
337
|
}
|
|
315
338
|
},
|
|
@@ -727,20 +750,38 @@ function useCommunitySearch() {
|
|
|
727
750
|
|
|
728
751
|
// src/dashboard/CStarDashboardProvider.tsx
|
|
729
752
|
var import_react17 = require("react");
|
|
753
|
+
var import_js = require("@cstar.help/js");
|
|
730
754
|
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
731
755
|
var CStarDashboardContext = (0, import_react17.createContext)(null);
|
|
732
|
-
function CStarDashboardProvider({
|
|
733
|
-
|
|
756
|
+
function CStarDashboardProvider({
|
|
757
|
+
config,
|
|
758
|
+
client: providedClient,
|
|
759
|
+
children
|
|
760
|
+
}) {
|
|
761
|
+
if (!config && !providedClient) {
|
|
734
762
|
throw new Error(
|
|
735
|
-
"CStarDashboardProvider requires a `
|
|
763
|
+
"CStarDashboardProvider requires either a `config` prop (`{ apiKey, teamId }` \u2014 recommended) or a `client` prop (a pre-built CStarClient instance, deprecated). See https://cstar.help/developers/sdk/react#dashboard"
|
|
736
764
|
);
|
|
737
765
|
}
|
|
738
|
-
const
|
|
766
|
+
const client = (0, import_react17.useMemo)(() => {
|
|
767
|
+
if (providedClient) return providedClient;
|
|
768
|
+
return new import_js.CStarClient(config);
|
|
769
|
+
}, [
|
|
770
|
+
providedClient,
|
|
771
|
+
config?.apiKey,
|
|
772
|
+
config?.teamId,
|
|
773
|
+
config?.baseUrl,
|
|
774
|
+
config?.version,
|
|
775
|
+
config?.maxRetries,
|
|
776
|
+
config?.timeout
|
|
777
|
+
]);
|
|
778
|
+
const contextValue = (0, import_react17.useMemo)(() => ({ client }), [client]);
|
|
739
779
|
(0, import_react17.useEffect)(() => {
|
|
780
|
+
if (providedClient) return;
|
|
740
781
|
return () => {
|
|
741
782
|
client?.realtime?.disconnect?.();
|
|
742
783
|
};
|
|
743
|
-
}, [client]);
|
|
784
|
+
}, [client, providedClient]);
|
|
744
785
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CStarDashboardContext.Provider, { value: contextValue, children });
|
|
745
786
|
}
|
|
746
787
|
function useDashboardClient() {
|
|
@@ -753,6 +794,7 @@ function useDashboardClient() {
|
|
|
753
794
|
|
|
754
795
|
// src/dashboard/useCStarQuery.ts
|
|
755
796
|
var import_react18 = require("react");
|
|
797
|
+
var import_js2 = require("@cstar.help/js");
|
|
756
798
|
function resolveResource(client, path) {
|
|
757
799
|
const parts = path.split(".");
|
|
758
800
|
if (parts.length < 2 || parts[1] !== "list") return null;
|
|
@@ -764,6 +806,11 @@ function resolveResource(client, path) {
|
|
|
764
806
|
return { resource, basePath: `/${resourceName}` };
|
|
765
807
|
}
|
|
766
808
|
function useCStarQuery(resource, params) {
|
|
809
|
+
if (typeof resource !== "string") {
|
|
810
|
+
throw new import_js2.CStarValidationError(
|
|
811
|
+
`useCStarQuery: \`resource\` must be a string (e.g. 'tickets.list'). Received ${resource === null ? "null" : typeof resource}. The signature is useCStarQuery(resource: string, params?: object).`
|
|
812
|
+
);
|
|
813
|
+
}
|
|
767
814
|
const client = useDashboardClient();
|
|
768
815
|
const [data, setData] = (0, import_react18.useState)(null);
|
|
769
816
|
const [loading, setLoading] = (0, import_react18.useState)(true);
|
package/dist/index.d.cts
CHANGED
|
@@ -6,8 +6,7 @@ import { LibraryClient, LibraryConfig, Category, LibraryArticle, LibraryArticleL
|
|
|
6
6
|
export { Category, LibraryArticle, LibraryArticleListParams, LibraryConfig, LibrarySearchParams, LibrarySearchResult } from '@cstar.help/js/library';
|
|
7
7
|
import { CommunityClient, CommunityConfig, CommunityTopic, CommunityPost, CommunityPostListParams, CommunityPostDetail, CommunitySearchResult } from '@cstar.help/js/community';
|
|
8
8
|
export { CommunityComment, CommunityConfig, CommunityPost, CommunityPostDetail, CommunityPostListParams, CommunitySearchResult, CommunityTopic } from '@cstar.help/js/community';
|
|
9
|
-
import
|
|
10
|
-
import { CStarClient, RealtimeEvent, Boss, Player, LeaderboardEntry } from '@cstar.help/js';
|
|
9
|
+
import { CStarClientConfig, CStarClient, RealtimeEvent, Boss, Player, LeaderboardEntry } from '@cstar.help/js';
|
|
11
10
|
|
|
12
11
|
declare const CStarChatContext: react.Context<ChatClient | null>;
|
|
13
12
|
declare const CStarLibraryContext: react.Context<LibraryClient | null>;
|
|
@@ -41,7 +40,7 @@ interface CStarChatProviderProps extends ChatConfig {
|
|
|
41
40
|
* </CStarChatProvider>
|
|
42
41
|
* ```
|
|
43
42
|
*/
|
|
44
|
-
declare function CStarChatProvider({ children, ...config }: CStarChatProviderProps):
|
|
43
|
+
declare function CStarChatProvider({ children, ...config }: CStarChatProviderProps): react.JSX.Element;
|
|
45
44
|
|
|
46
45
|
interface UseChatReturn {
|
|
47
46
|
/** Identify a customer with HMAC signature. */
|
|
@@ -178,7 +177,7 @@ interface CStarLibraryProviderProps extends LibraryConfig {
|
|
|
178
177
|
* </CStarLibraryProvider>
|
|
179
178
|
* ```
|
|
180
179
|
*/
|
|
181
|
-
declare function CStarLibraryProvider({ children, ...config }: CStarLibraryProviderProps):
|
|
180
|
+
declare function CStarLibraryProvider({ children, ...config }: CStarLibraryProviderProps): react.JSX.Element;
|
|
182
181
|
|
|
183
182
|
interface UseCategoriesReturn {
|
|
184
183
|
/** All public categories. */
|
|
@@ -271,7 +270,7 @@ interface CStarCommunityProviderProps extends CommunityConfig {
|
|
|
271
270
|
* </CStarCommunityProvider>
|
|
272
271
|
* ```
|
|
273
272
|
*/
|
|
274
|
-
declare function CStarCommunityProvider({ children, ...config }: CStarCommunityProviderProps):
|
|
273
|
+
declare function CStarCommunityProvider({ children, ...config }: CStarCommunityProviderProps): react.JSX.Element;
|
|
275
274
|
|
|
276
275
|
interface UseTopicsReturn {
|
|
277
276
|
/** Community discussion topics. */
|
|
@@ -355,28 +354,48 @@ interface UseCommunitySearchReturn {
|
|
|
355
354
|
declare function useCommunitySearch(): UseCommunitySearchReturn;
|
|
356
355
|
|
|
357
356
|
interface CStarDashboardProviderProps {
|
|
358
|
-
/**
|
|
359
|
-
|
|
357
|
+
/**
|
|
358
|
+
* Serializable client config — `{ apiKey, teamId, baseUrl?, ... }`. The
|
|
359
|
+
* provider builds the `CStarClient` internally so this prop can be passed
|
|
360
|
+
* across the React Server Components boundary without tripping the
|
|
361
|
+
* "Classes or null prototypes are not supported" runtime error.
|
|
362
|
+
* Config is visible to the browser: use only a publishable key here.
|
|
363
|
+
*
|
|
364
|
+
* Either `config` or `client` is required.
|
|
365
|
+
*/
|
|
366
|
+
config?: CStarClientConfig;
|
|
367
|
+
/**
|
|
368
|
+
* A pre-built `CStarClient` instance.
|
|
369
|
+
*
|
|
370
|
+
* @deprecated since 0.9.0 — pass `config` instead. Class instances cannot
|
|
371
|
+
* cross the RSC boundary, which makes this prop unsafe to use from a
|
|
372
|
+
* Next.js App Router server component. Kept as an escape hatch for
|
|
373
|
+
* integrators who already wired it; will be removed in 1.0.
|
|
374
|
+
*/
|
|
375
|
+
client?: CStarClient;
|
|
360
376
|
children: react__default.ReactNode;
|
|
361
377
|
}
|
|
362
378
|
/**
|
|
363
|
-
* Provides a CStarClient to dashboard hooks.
|
|
379
|
+
* Provides a `CStarClient` to dashboard hooks.
|
|
364
380
|
*
|
|
365
|
-
*
|
|
366
|
-
*
|
|
367
|
-
*
|
|
381
|
+
* Provider props and bundled clients are visible to the browser. Pass only
|
|
382
|
+
* a publishable key; supported operations are limited by that key's permissions.
|
|
383
|
+
* Run privileged dashboard reads and mutations through your authenticated
|
|
384
|
+
* backend, where a secret-key `CStarClient` stays on the server.
|
|
368
385
|
*
|
|
369
386
|
* ```tsx
|
|
370
|
-
*
|
|
371
|
-
*
|
|
372
|
-
* <CStarDashboardProvider
|
|
387
|
+
* // app/providers.tsx
|
|
388
|
+
* 'use client';
|
|
389
|
+
* <CStarDashboardProvider
|
|
390
|
+
* config={{ apiKey: process.env.NEXT_PUBLIC_CSTAR_PUBLISHABLE_KEY!, teamId }}
|
|
391
|
+
* >
|
|
373
392
|
* <TicketList />
|
|
374
393
|
* </CStarDashboardProvider>
|
|
375
394
|
* ```
|
|
376
395
|
*/
|
|
377
|
-
declare function CStarDashboardProvider({ client, children }: CStarDashboardProviderProps):
|
|
396
|
+
declare function CStarDashboardProvider({ config, client: providedClient, children }: CStarDashboardProviderProps): react__default.JSX.Element;
|
|
378
397
|
/**
|
|
379
|
-
* Access the CStarClient from the nearest CStarDashboardProvider
|
|
398
|
+
* Access the `CStarClient` from the nearest `CStarDashboardProvider`.
|
|
380
399
|
* Throws if used outside a provider.
|
|
381
400
|
*/
|
|
382
401
|
declare function useDashboardClient(): CStarClient;
|
package/dist/index.d.ts
CHANGED
|
@@ -6,8 +6,7 @@ import { LibraryClient, LibraryConfig, Category, LibraryArticle, LibraryArticleL
|
|
|
6
6
|
export { Category, LibraryArticle, LibraryArticleListParams, LibraryConfig, LibrarySearchParams, LibrarySearchResult } from '@cstar.help/js/library';
|
|
7
7
|
import { CommunityClient, CommunityConfig, CommunityTopic, CommunityPost, CommunityPostListParams, CommunityPostDetail, CommunitySearchResult } from '@cstar.help/js/community';
|
|
8
8
|
export { CommunityComment, CommunityConfig, CommunityPost, CommunityPostDetail, CommunityPostListParams, CommunitySearchResult, CommunityTopic } from '@cstar.help/js/community';
|
|
9
|
-
import
|
|
10
|
-
import { CStarClient, RealtimeEvent, Boss, Player, LeaderboardEntry } from '@cstar.help/js';
|
|
9
|
+
import { CStarClientConfig, CStarClient, RealtimeEvent, Boss, Player, LeaderboardEntry } from '@cstar.help/js';
|
|
11
10
|
|
|
12
11
|
declare const CStarChatContext: react.Context<ChatClient | null>;
|
|
13
12
|
declare const CStarLibraryContext: react.Context<LibraryClient | null>;
|
|
@@ -41,7 +40,7 @@ interface CStarChatProviderProps extends ChatConfig {
|
|
|
41
40
|
* </CStarChatProvider>
|
|
42
41
|
* ```
|
|
43
42
|
*/
|
|
44
|
-
declare function CStarChatProvider({ children, ...config }: CStarChatProviderProps):
|
|
43
|
+
declare function CStarChatProvider({ children, ...config }: CStarChatProviderProps): react.JSX.Element;
|
|
45
44
|
|
|
46
45
|
interface UseChatReturn {
|
|
47
46
|
/** Identify a customer with HMAC signature. */
|
|
@@ -178,7 +177,7 @@ interface CStarLibraryProviderProps extends LibraryConfig {
|
|
|
178
177
|
* </CStarLibraryProvider>
|
|
179
178
|
* ```
|
|
180
179
|
*/
|
|
181
|
-
declare function CStarLibraryProvider({ children, ...config }: CStarLibraryProviderProps):
|
|
180
|
+
declare function CStarLibraryProvider({ children, ...config }: CStarLibraryProviderProps): react.JSX.Element;
|
|
182
181
|
|
|
183
182
|
interface UseCategoriesReturn {
|
|
184
183
|
/** All public categories. */
|
|
@@ -271,7 +270,7 @@ interface CStarCommunityProviderProps extends CommunityConfig {
|
|
|
271
270
|
* </CStarCommunityProvider>
|
|
272
271
|
* ```
|
|
273
272
|
*/
|
|
274
|
-
declare function CStarCommunityProvider({ children, ...config }: CStarCommunityProviderProps):
|
|
273
|
+
declare function CStarCommunityProvider({ children, ...config }: CStarCommunityProviderProps): react.JSX.Element;
|
|
275
274
|
|
|
276
275
|
interface UseTopicsReturn {
|
|
277
276
|
/** Community discussion topics. */
|
|
@@ -355,28 +354,48 @@ interface UseCommunitySearchReturn {
|
|
|
355
354
|
declare function useCommunitySearch(): UseCommunitySearchReturn;
|
|
356
355
|
|
|
357
356
|
interface CStarDashboardProviderProps {
|
|
358
|
-
/**
|
|
359
|
-
|
|
357
|
+
/**
|
|
358
|
+
* Serializable client config — `{ apiKey, teamId, baseUrl?, ... }`. The
|
|
359
|
+
* provider builds the `CStarClient` internally so this prop can be passed
|
|
360
|
+
* across the React Server Components boundary without tripping the
|
|
361
|
+
* "Classes or null prototypes are not supported" runtime error.
|
|
362
|
+
* Config is visible to the browser: use only a publishable key here.
|
|
363
|
+
*
|
|
364
|
+
* Either `config` or `client` is required.
|
|
365
|
+
*/
|
|
366
|
+
config?: CStarClientConfig;
|
|
367
|
+
/**
|
|
368
|
+
* A pre-built `CStarClient` instance.
|
|
369
|
+
*
|
|
370
|
+
* @deprecated since 0.9.0 — pass `config` instead. Class instances cannot
|
|
371
|
+
* cross the RSC boundary, which makes this prop unsafe to use from a
|
|
372
|
+
* Next.js App Router server component. Kept as an escape hatch for
|
|
373
|
+
* integrators who already wired it; will be removed in 1.0.
|
|
374
|
+
*/
|
|
375
|
+
client?: CStarClient;
|
|
360
376
|
children: react__default.ReactNode;
|
|
361
377
|
}
|
|
362
378
|
/**
|
|
363
|
-
* Provides a CStarClient to dashboard hooks.
|
|
379
|
+
* Provides a `CStarClient` to dashboard hooks.
|
|
364
380
|
*
|
|
365
|
-
*
|
|
366
|
-
*
|
|
367
|
-
*
|
|
381
|
+
* Provider props and bundled clients are visible to the browser. Pass only
|
|
382
|
+
* a publishable key; supported operations are limited by that key's permissions.
|
|
383
|
+
* Run privileged dashboard reads and mutations through your authenticated
|
|
384
|
+
* backend, where a secret-key `CStarClient` stays on the server.
|
|
368
385
|
*
|
|
369
386
|
* ```tsx
|
|
370
|
-
*
|
|
371
|
-
*
|
|
372
|
-
* <CStarDashboardProvider
|
|
387
|
+
* // app/providers.tsx
|
|
388
|
+
* 'use client';
|
|
389
|
+
* <CStarDashboardProvider
|
|
390
|
+
* config={{ apiKey: process.env.NEXT_PUBLIC_CSTAR_PUBLISHABLE_KEY!, teamId }}
|
|
391
|
+
* >
|
|
373
392
|
* <TicketList />
|
|
374
393
|
* </CStarDashboardProvider>
|
|
375
394
|
* ```
|
|
376
395
|
*/
|
|
377
|
-
declare function CStarDashboardProvider({ client, children }: CStarDashboardProviderProps):
|
|
396
|
+
declare function CStarDashboardProvider({ config, client: providedClient, children }: CStarDashboardProviderProps): react__default.JSX.Element;
|
|
378
397
|
/**
|
|
379
|
-
* Access the CStarClient from the nearest CStarDashboardProvider
|
|
398
|
+
* Access the `CStarClient` from the nearest `CStarDashboardProvider`.
|
|
380
399
|
* Throws if used outside a provider.
|
|
381
400
|
*/
|
|
382
401
|
declare function useDashboardClient(): CStarClient;
|
package/dist/index.js
CHANGED
|
@@ -52,9 +52,14 @@ function useChat() {
|
|
|
52
52
|
const [isRealtimeReady, setIsRealtimeReady] = useState(false);
|
|
53
53
|
const [error, setError] = useState(null);
|
|
54
54
|
useEffect2(() => {
|
|
55
|
-
const
|
|
56
|
-
if (
|
|
57
|
-
|
|
55
|
+
const subscribe = client.subscribe?.bind(client);
|
|
56
|
+
if (!subscribe) return;
|
|
57
|
+
return subscribe(() => {
|
|
58
|
+
const mode = client.getAuthMode();
|
|
59
|
+
setAuthMode(mode);
|
|
60
|
+
setIsAuthenticated(mode !== null);
|
|
61
|
+
});
|
|
62
|
+
}, [client]);
|
|
58
63
|
const wireRealtime = useCallback(() => {
|
|
59
64
|
client.waitForRealtime().then(() => setIsRealtimeReady(true)).catch(() => {
|
|
60
65
|
});
|
|
@@ -144,7 +149,7 @@ function useTickets() {
|
|
|
144
149
|
try {
|
|
145
150
|
const result = await client.tickets.list();
|
|
146
151
|
if (mountedRef.current) {
|
|
147
|
-
setTickets(result.
|
|
152
|
+
setTickets(result.data);
|
|
148
153
|
setHasMore(result.hasMore);
|
|
149
154
|
}
|
|
150
155
|
} catch (err) {
|
|
@@ -186,36 +191,48 @@ function useMessages(ticketId) {
|
|
|
186
191
|
const [error, setError] = useState3(null);
|
|
187
192
|
const mountedRef = useRef3(true);
|
|
188
193
|
const seenIdsRef = useRef3(/* @__PURE__ */ new Set());
|
|
194
|
+
const requestRef = useRef3(0);
|
|
195
|
+
const scopeRef = useRef3(0);
|
|
196
|
+
const optimisticRef = useRef3(0);
|
|
189
197
|
const refresh = useCallback3(async () => {
|
|
190
198
|
if (!ticketId) return;
|
|
199
|
+
const request = ++requestRef.current;
|
|
191
200
|
setIsLoading(true);
|
|
192
201
|
setError(null);
|
|
193
202
|
try {
|
|
194
203
|
const msgs = await client.messages.list(ticketId);
|
|
195
|
-
if (mountedRef.current) {
|
|
204
|
+
if (mountedRef.current && request === requestRef.current) {
|
|
196
205
|
seenIdsRef.current = new Set(msgs.map((m) => m.id));
|
|
197
206
|
setMessages(msgs);
|
|
198
207
|
}
|
|
199
208
|
} catch (err) {
|
|
200
|
-
if (mountedRef.current) {
|
|
209
|
+
if (mountedRef.current && request === requestRef.current) {
|
|
201
210
|
setError(err instanceof Error ? err : new Error(String(err)));
|
|
202
211
|
}
|
|
203
212
|
} finally {
|
|
204
|
-
if (mountedRef.current) {
|
|
213
|
+
if (mountedRef.current && request === requestRef.current) {
|
|
205
214
|
setIsLoading(false);
|
|
206
215
|
}
|
|
207
216
|
}
|
|
208
217
|
}, [client, ticketId]);
|
|
209
218
|
useEffect4(() => {
|
|
210
219
|
mountedRef.current = true;
|
|
220
|
+
const scope = ++scopeRef.current;
|
|
221
|
+
seenIdsRef.current = /* @__PURE__ */ new Set();
|
|
222
|
+
setMessages([]);
|
|
223
|
+
setError(null);
|
|
211
224
|
if (!ticketId) {
|
|
212
225
|
setMessages([]);
|
|
213
226
|
setIsLoading(false);
|
|
214
|
-
return
|
|
227
|
+
return () => {
|
|
228
|
+
mountedRef.current = false;
|
|
229
|
+
requestRef.current++;
|
|
230
|
+
scopeRef.current++;
|
|
231
|
+
};
|
|
215
232
|
}
|
|
216
233
|
refresh();
|
|
217
234
|
const unsubscribe = client.onMessage(ticketId, (msg) => {
|
|
218
|
-
if (mountedRef.current && !seenIdsRef.current.has(msg.id)) {
|
|
235
|
+
if (mountedRef.current && scope === scopeRef.current && !seenIdsRef.current.has(msg.id)) {
|
|
219
236
|
seenIdsRef.current.add(msg.id);
|
|
220
237
|
setMessages((prev) => {
|
|
221
238
|
const optimisticIdx = prev.findIndex(
|
|
@@ -232,14 +249,17 @@ function useMessages(ticketId) {
|
|
|
232
249
|
});
|
|
233
250
|
return () => {
|
|
234
251
|
mountedRef.current = false;
|
|
252
|
+
requestRef.current++;
|
|
253
|
+
scopeRef.current++;
|
|
235
254
|
unsubscribe();
|
|
236
255
|
};
|
|
237
256
|
}, [client, ticketId, refresh]);
|
|
238
257
|
const send = useCallback3(
|
|
239
258
|
async (content) => {
|
|
240
259
|
if (!ticketId) throw new Error("No ticket selected");
|
|
260
|
+
const scope = scopeRef.current;
|
|
241
261
|
const optimistic = {
|
|
242
|
-
id: `optimistic-${
|
|
262
|
+
id: `optimistic-${++optimisticRef.current}`,
|
|
243
263
|
content,
|
|
244
264
|
sender: "customer",
|
|
245
265
|
sender_name: "",
|
|
@@ -250,11 +270,14 @@ function useMessages(ticketId) {
|
|
|
250
270
|
setMessages((prev) => [...prev, optimistic]);
|
|
251
271
|
try {
|
|
252
272
|
const msg = await client.messages.send(ticketId, content);
|
|
273
|
+
if (!mountedRef.current || scope !== scopeRef.current) return msg;
|
|
253
274
|
seenIdsRef.current.add(msg.id);
|
|
254
275
|
setMessages((prev) => prev.map((m) => m.id === optimistic.id ? msg : m));
|
|
255
276
|
return msg;
|
|
256
277
|
} catch (err) {
|
|
257
|
-
|
|
278
|
+
if (mountedRef.current && scope === scopeRef.current) {
|
|
279
|
+
setMessages((prev) => prev.filter((m) => m.id !== optimistic.id));
|
|
280
|
+
}
|
|
258
281
|
throw err;
|
|
259
282
|
}
|
|
260
283
|
},
|
|
@@ -671,21 +694,39 @@ function useCommunitySearch() {
|
|
|
671
694
|
}
|
|
672
695
|
|
|
673
696
|
// src/dashboard/CStarDashboardProvider.tsx
|
|
674
|
-
import { createContext as createContext2, useContext as useContext2, useEffect as useEffect14,
|
|
697
|
+
import { createContext as createContext2, useContext as useContext2, useEffect as useEffect14, useMemo } from "react";
|
|
698
|
+
import { CStarClient } from "@cstar.help/js";
|
|
675
699
|
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
676
700
|
var CStarDashboardContext = createContext2(null);
|
|
677
|
-
function CStarDashboardProvider({
|
|
678
|
-
|
|
701
|
+
function CStarDashboardProvider({
|
|
702
|
+
config,
|
|
703
|
+
client: providedClient,
|
|
704
|
+
children
|
|
705
|
+
}) {
|
|
706
|
+
if (!config && !providedClient) {
|
|
679
707
|
throw new Error(
|
|
680
|
-
"CStarDashboardProvider requires a `
|
|
708
|
+
"CStarDashboardProvider requires either a `config` prop (`{ apiKey, teamId }` \u2014 recommended) or a `client` prop (a pre-built CStarClient instance, deprecated). See https://cstar.help/developers/sdk/react#dashboard"
|
|
681
709
|
);
|
|
682
710
|
}
|
|
683
|
-
const
|
|
711
|
+
const client = useMemo(() => {
|
|
712
|
+
if (providedClient) return providedClient;
|
|
713
|
+
return new CStarClient(config);
|
|
714
|
+
}, [
|
|
715
|
+
providedClient,
|
|
716
|
+
config?.apiKey,
|
|
717
|
+
config?.teamId,
|
|
718
|
+
config?.baseUrl,
|
|
719
|
+
config?.version,
|
|
720
|
+
config?.maxRetries,
|
|
721
|
+
config?.timeout
|
|
722
|
+
]);
|
|
723
|
+
const contextValue = useMemo(() => ({ client }), [client]);
|
|
684
724
|
useEffect14(() => {
|
|
725
|
+
if (providedClient) return;
|
|
685
726
|
return () => {
|
|
686
727
|
client?.realtime?.disconnect?.();
|
|
687
728
|
};
|
|
688
|
-
}, [client]);
|
|
729
|
+
}, [client, providedClient]);
|
|
689
730
|
return /* @__PURE__ */ jsx4(CStarDashboardContext.Provider, { value: contextValue, children });
|
|
690
731
|
}
|
|
691
732
|
function useDashboardClient() {
|
|
@@ -697,7 +738,8 @@ function useDashboardClient() {
|
|
|
697
738
|
}
|
|
698
739
|
|
|
699
740
|
// src/dashboard/useCStarQuery.ts
|
|
700
|
-
import { useState as useState13, useEffect as useEffect15, useRef as
|
|
741
|
+
import { useState as useState13, useEffect as useEffect15, useRef as useRef15, useCallback as useCallback12 } from "react";
|
|
742
|
+
import { CStarValidationError } from "@cstar.help/js";
|
|
701
743
|
function resolveResource(client, path) {
|
|
702
744
|
const parts = path.split(".");
|
|
703
745
|
if (parts.length < 2 || parts[1] !== "list") return null;
|
|
@@ -709,11 +751,16 @@ function resolveResource(client, path) {
|
|
|
709
751
|
return { resource, basePath: `/${resourceName}` };
|
|
710
752
|
}
|
|
711
753
|
function useCStarQuery(resource, params) {
|
|
754
|
+
if (typeof resource !== "string") {
|
|
755
|
+
throw new CStarValidationError(
|
|
756
|
+
`useCStarQuery: \`resource\` must be a string (e.g. 'tickets.list'). Received ${resource === null ? "null" : typeof resource}. The signature is useCStarQuery(resource: string, params?: object).`
|
|
757
|
+
);
|
|
758
|
+
}
|
|
712
759
|
const client = useDashboardClient();
|
|
713
760
|
const [data, setData] = useState13(null);
|
|
714
761
|
const [loading, setLoading] = useState13(true);
|
|
715
762
|
const [error, setError] = useState13(null);
|
|
716
|
-
const subscriptionRef =
|
|
763
|
+
const subscriptionRef = useRef15(null);
|
|
717
764
|
const paramsKey = params ? JSON.stringify(params) : "";
|
|
718
765
|
useEffect15(() => {
|
|
719
766
|
const resolved = resolveResource(client, resource);
|
|
@@ -841,13 +888,13 @@ function useCStarEvents(types) {
|
|
|
841
888
|
}
|
|
842
889
|
|
|
843
890
|
// src/dashboard/useBossBattle.ts
|
|
844
|
-
import { useState as useState16, useEffect as useEffect17, useRef as
|
|
891
|
+
import { useState as useState16, useEffect as useEffect17, useRef as useRef16, useCallback as useCallback14 } from "react";
|
|
845
892
|
function useBossBattle() {
|
|
846
893
|
const client = useDashboardClient();
|
|
847
894
|
const [boss, setBoss] = useState16(null);
|
|
848
895
|
const [loading, setLoading] = useState16(true);
|
|
849
896
|
const [error, setError] = useState16(null);
|
|
850
|
-
const subRef =
|
|
897
|
+
const subRef = useRef16(null);
|
|
851
898
|
useEffect17(() => {
|
|
852
899
|
const sub = client.game.subscribeBoss(client.realtime);
|
|
853
900
|
subRef.current = sub;
|
|
@@ -872,13 +919,13 @@ function useBossBattle() {
|
|
|
872
919
|
}
|
|
873
920
|
|
|
874
921
|
// src/dashboard/usePlayerStats.ts
|
|
875
|
-
import { useState as useState17, useEffect as useEffect18, useRef as
|
|
922
|
+
import { useState as useState17, useEffect as useEffect18, useRef as useRef17, useCallback as useCallback15 } from "react";
|
|
876
923
|
function usePlayerStats(memberId) {
|
|
877
924
|
const client = useDashboardClient();
|
|
878
925
|
const [player, setPlayer] = useState17(null);
|
|
879
926
|
const [loading, setLoading] = useState17(true);
|
|
880
927
|
const [error, setError] = useState17(null);
|
|
881
|
-
const subRef =
|
|
928
|
+
const subRef = useRef17(null);
|
|
882
929
|
useEffect18(() => {
|
|
883
930
|
const sub = client.game.subscribePlayer(client.realtime, memberId);
|
|
884
931
|
subRef.current = sub;
|
|
@@ -903,13 +950,13 @@ function usePlayerStats(memberId) {
|
|
|
903
950
|
}
|
|
904
951
|
|
|
905
952
|
// src/dashboard/useLeaderboard.ts
|
|
906
|
-
import { useState as useState18, useEffect as useEffect19, useRef as
|
|
953
|
+
import { useState as useState18, useEffect as useEffect19, useRef as useRef18, useCallback as useCallback16 } from "react";
|
|
907
954
|
function useLeaderboard(period) {
|
|
908
955
|
const client = useDashboardClient();
|
|
909
956
|
const [entries, setEntries] = useState18([]);
|
|
910
957
|
const [loading, setLoading] = useState18(true);
|
|
911
958
|
const [error, setError] = useState18(null);
|
|
912
|
-
const subRef =
|
|
959
|
+
const subRef = useRef18(null);
|
|
913
960
|
useEffect19(() => {
|
|
914
961
|
const sub = client.game.subscribeLeaderboard(client.realtime, period ? { period } : void 0);
|
|
915
962
|
subRef.current = sub;
|
|
@@ -934,13 +981,13 @@ function useLeaderboard(period) {
|
|
|
934
981
|
}
|
|
935
982
|
|
|
936
983
|
// src/dashboard/useGameActivity.ts
|
|
937
|
-
import { useState as useState19, useEffect as useEffect20, useRef as
|
|
984
|
+
import { useState as useState19, useEffect as useEffect20, useRef as useRef19, useCallback as useCallback17 } from "react";
|
|
938
985
|
function useGameActivity() {
|
|
939
986
|
const client = useDashboardClient();
|
|
940
987
|
const [events, setEvents] = useState19([]);
|
|
941
988
|
const [loading, setLoading] = useState19(true);
|
|
942
989
|
const [error, setError] = useState19(null);
|
|
943
|
-
const subRef =
|
|
990
|
+
const subRef = useRef19(null);
|
|
944
991
|
useEffect20(() => {
|
|
945
992
|
const sub = client.game.subscribeActivity(client.realtime);
|
|
946
993
|
subRef.current = sub;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cstar.help/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.1",
|
|
4
4
|
"description": "React hooks for the cStar customer support platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -30,19 +30,20 @@
|
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
32
32
|
"react": ">=18.0.0",
|
|
33
|
-
"@cstar.help/js": ">=0.
|
|
33
|
+
"@cstar.help/js": ">=0.15.0"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"react": "^18.3.1",
|
|
37
37
|
"react-dom": "^18.3.1",
|
|
38
|
-
"@cstar.help/js": "
|
|
38
|
+
"@cstar.help/js": "^0.17.0",
|
|
39
39
|
"@testing-library/react": "^16.1.0",
|
|
40
40
|
"@types/react": "^18.3.0",
|
|
41
41
|
"@types/react-dom": "^18.3.0",
|
|
42
42
|
"tsup": "^8.5.0",
|
|
43
43
|
"typescript": "^5.7.0",
|
|
44
44
|
"vitest": "^3.2.0",
|
|
45
|
-
"jsdom": "^25.0.0"
|
|
45
|
+
"jsdom": "^25.0.0",
|
|
46
|
+
"@testing-library/dom": "^10.4.1"
|
|
46
47
|
},
|
|
47
48
|
"license": "MIT",
|
|
48
49
|
"engines": {
|
|
@@ -51,11 +52,11 @@
|
|
|
51
52
|
"homepage": "https://cstar.help/developers/sdk/react",
|
|
52
53
|
"repository": {
|
|
53
54
|
"type": "git",
|
|
54
|
-
"url": "https://github.com/
|
|
55
|
+
"url": "https://github.com/msjoshlopez/cstar",
|
|
55
56
|
"directory": "packages/react"
|
|
56
57
|
},
|
|
57
58
|
"bugs": {
|
|
58
|
-
"url": "https://
|
|
59
|
+
"url": "https://www.cstar.help/support"
|
|
59
60
|
},
|
|
60
61
|
"keywords": [
|
|
61
62
|
"cstar",
|