@cstar.help/react 0.10.0 → 0.10.2
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 +32 -22
- package/dist/index.cjs +26 -8
- package/dist/index.d.cts +19 -11
- package/dist/index.d.ts +19 -11
- package/dist/index.js +26 -8
- package/package.json +7 -6
package/README.md
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
React hooks for the [cStar](https://cstar.help) customer support platform. Build custom chat widgets and knowledge base UIs with type-safe, SSR-compatible hooks.
|
|
4
4
|
|
|
5
|
-
- **React 18
|
|
6
|
-
- **Three modules
|
|
7
|
-
- **TypeScript-first
|
|
8
|
-
- **Tiny
|
|
5
|
+
- **React 18+**: hooks with automatic loading states and error handling
|
|
6
|
+
- **Three modules**: Chat (real-time messaging), Library (knowledge base), and Community (forum)
|
|
7
|
+
- **TypeScript-first**: full type definitions included
|
|
8
|
+
- **Tiny**: ~18 KB, tree-shakeable, only peer deps
|
|
9
9
|
|
|
10
10
|
## Install
|
|
11
11
|
|
|
@@ -82,7 +82,7 @@ function ChatWindow({ ticketId }) {
|
|
|
82
82
|
const [text, setText] = useState('');
|
|
83
83
|
|
|
84
84
|
const handleSend = async () => {
|
|
85
|
-
await send(text); // optimistic
|
|
85
|
+
await send(text); // optimistic: appears instantly
|
|
86
86
|
setText('');
|
|
87
87
|
};
|
|
88
88
|
|
|
@@ -109,7 +109,7 @@ function ChatWindow({ ticketId }) {
|
|
|
109
109
|
|
|
110
110
|
## Knowledge Base
|
|
111
111
|
|
|
112
|
-
Wrap in `<CStarLibraryProvider>` for public knowledge base access
|
|
112
|
+
Wrap in `<CStarLibraryProvider>` for public knowledge base access. No auth required.
|
|
113
113
|
|
|
114
114
|
### Setup
|
|
115
115
|
|
|
@@ -184,7 +184,7 @@ function SearchBar() {
|
|
|
184
184
|
|
|
185
185
|
## Community
|
|
186
186
|
|
|
187
|
-
Wrap in `<CStarCommunityProvider>` for public community forum access
|
|
187
|
+
Wrap in `<CStarCommunityProvider>` for public community forum access. No auth required.
|
|
188
188
|
|
|
189
189
|
### Setup
|
|
190
190
|
|
|
@@ -220,7 +220,7 @@ function CommunityForum() {
|
|
|
220
220
|
<ul>
|
|
221
221
|
{posts.map((post) => (
|
|
222
222
|
<li key={post.id}>
|
|
223
|
-
{post.title}
|
|
223
|
+
{post.title} ({post.voteCount} votes)
|
|
224
224
|
</li>
|
|
225
225
|
))}
|
|
226
226
|
</ul>
|
|
@@ -303,11 +303,11 @@ function CommunitySearch() {
|
|
|
303
303
|
## Using with Next.js App Router (RSC)
|
|
304
304
|
|
|
305
305
|
The dashboard provider (`CStarDashboardProvider`) wraps a `CStarClient`,
|
|
306
|
-
which is a class instance
|
|
306
|
+
which is a class instance, and React Server Components can't serialize
|
|
307
307
|
class instances across the server/client boundary. The provider therefore
|
|
308
308
|
takes one of two prop shapes:
|
|
309
309
|
|
|
310
|
-
**Recommended
|
|
310
|
+
**Recommended: pass a plain config object.** The provider builds (and
|
|
311
311
|
disposes) the client inside the `'use client'` boundary, so nothing
|
|
312
312
|
class-shaped crosses the RSC boundary:
|
|
313
313
|
|
|
@@ -316,11 +316,17 @@ class-shaped crosses the RSC boundary:
|
|
|
316
316
|
'use client';
|
|
317
317
|
import { CStarDashboardProvider } from '@cstar.help/react';
|
|
318
318
|
|
|
319
|
-
export function Providers({
|
|
319
|
+
export function Providers({
|
|
320
|
+
children,
|
|
321
|
+
secretKey
|
|
322
|
+
}: {
|
|
323
|
+
children: React.ReactNode;
|
|
324
|
+
secretKey: string;
|
|
325
|
+
}) {
|
|
320
326
|
return (
|
|
321
327
|
<CStarDashboardProvider
|
|
322
328
|
config={{
|
|
323
|
-
apiKey:
|
|
329
|
+
apiKey: secretKey, // passed down from your server, see below
|
|
324
330
|
teamId: process.env.NEXT_PUBLIC_CSTAR_TEAM_ID!
|
|
325
331
|
}}
|
|
326
332
|
>
|
|
@@ -330,12 +336,16 @@ export function Providers({ children }: { children: React.ReactNode }) {
|
|
|
330
336
|
}
|
|
331
337
|
```
|
|
332
338
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
339
|
+
Every dashboard hook calls team endpoints, and those need a secret key
|
|
340
|
+
(`sk_live_*` / `sk_test_*`): a publishable key gets 403
|
|
341
|
+
`publishable_key_forbidden` on reads and `publishable_key_write` on writes.
|
|
342
|
+
Whatever you pass to the provider reaches the browser, so use it only in a
|
|
343
|
+
trusted environment, such as an internal tool behind your own login where
|
|
344
|
+
everyone who can open the page may hold that key. Use a key scoped to what
|
|
345
|
+
the dashboard needs. For a public site, have the app call your own backend
|
|
346
|
+
(a route handler or server action) that holds a secret-key `CStarClient`.
|
|
337
347
|
|
|
338
|
-
**Legacy
|
|
348
|
+
**Legacy: pass a pre-built client (deprecated).** If you already have a
|
|
339
349
|
client instance you want to share, the `client` prop still works, but you
|
|
340
350
|
have to construct it inside `'use client'` and you take on responsibility
|
|
341
351
|
for tearing down the realtime subscription:
|
|
@@ -346,28 +356,28 @@ import { useMemo } from 'react';
|
|
|
346
356
|
import { CStarClient } from '@cstar.help/js';
|
|
347
357
|
import { CStarDashboardProvider } from '@cstar.help/react';
|
|
348
358
|
|
|
349
|
-
export function Providers({ children }) {
|
|
359
|
+
export function Providers({ children, secretKey }) {
|
|
350
360
|
const client = useMemo(
|
|
351
361
|
() =>
|
|
352
362
|
new CStarClient({
|
|
353
|
-
apiKey:
|
|
363
|
+
apiKey: secretKey, // trusted environments only, as above
|
|
354
364
|
teamId: process.env.NEXT_PUBLIC_CSTAR_TEAM_ID!
|
|
355
365
|
}),
|
|
356
|
-
[]
|
|
366
|
+
[secretKey]
|
|
357
367
|
);
|
|
358
368
|
return <CStarDashboardProvider client={client}>{children}</CStarDashboardProvider>;
|
|
359
369
|
}
|
|
360
370
|
```
|
|
361
371
|
|
|
362
372
|
Either way, mark the file `'use client'`. Server components can sit above
|
|
363
|
-
the provider in the tree
|
|
373
|
+
the provider in the tree. Only the provider boundary needs to be a client
|
|
364
374
|
component.
|
|
365
375
|
|
|
366
376
|
## Requirements
|
|
367
377
|
|
|
368
378
|
- React 18+
|
|
369
379
|
- @cstar.help/js 0.1.0+
|
|
370
|
-
- Node.js
|
|
380
|
+
- Node.js 22+ for SSR with `@cstar.help/js` 0.17
|
|
371
381
|
|
|
372
382
|
## License
|
|
373
383
|
|
package/dist/index.cjs
CHANGED
|
@@ -246,36 +246,48 @@ function useMessages(ticketId) {
|
|
|
246
246
|
const [error, setError] = (0, import_react5.useState)(null);
|
|
247
247
|
const mountedRef = (0, import_react5.useRef)(true);
|
|
248
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);
|
|
249
252
|
const refresh = (0, import_react5.useCallback)(async () => {
|
|
250
253
|
if (!ticketId) return;
|
|
254
|
+
const request = ++requestRef.current;
|
|
251
255
|
setIsLoading(true);
|
|
252
256
|
setError(null);
|
|
253
257
|
try {
|
|
254
258
|
const msgs = await client.messages.list(ticketId);
|
|
255
|
-
if (mountedRef.current) {
|
|
259
|
+
if (mountedRef.current && request === requestRef.current) {
|
|
256
260
|
seenIdsRef.current = new Set(msgs.map((m) => m.id));
|
|
257
261
|
setMessages(msgs);
|
|
258
262
|
}
|
|
259
263
|
} catch (err) {
|
|
260
|
-
if (mountedRef.current) {
|
|
264
|
+
if (mountedRef.current && request === requestRef.current) {
|
|
261
265
|
setError(err instanceof Error ? err : new Error(String(err)));
|
|
262
266
|
}
|
|
263
267
|
} finally {
|
|
264
|
-
if (mountedRef.current) {
|
|
268
|
+
if (mountedRef.current && request === requestRef.current) {
|
|
265
269
|
setIsLoading(false);
|
|
266
270
|
}
|
|
267
271
|
}
|
|
268
272
|
}, [client, ticketId]);
|
|
269
273
|
(0, import_react5.useEffect)(() => {
|
|
270
274
|
mountedRef.current = true;
|
|
275
|
+
const scope = ++scopeRef.current;
|
|
276
|
+
seenIdsRef.current = /* @__PURE__ */ new Set();
|
|
277
|
+
setMessages([]);
|
|
278
|
+
setError(null);
|
|
271
279
|
if (!ticketId) {
|
|
272
280
|
setMessages([]);
|
|
273
281
|
setIsLoading(false);
|
|
274
|
-
return
|
|
282
|
+
return () => {
|
|
283
|
+
mountedRef.current = false;
|
|
284
|
+
requestRef.current++;
|
|
285
|
+
scopeRef.current++;
|
|
286
|
+
};
|
|
275
287
|
}
|
|
276
288
|
refresh();
|
|
277
289
|
const unsubscribe = client.onMessage(ticketId, (msg) => {
|
|
278
|
-
if (mountedRef.current && !seenIdsRef.current.has(msg.id)) {
|
|
290
|
+
if (mountedRef.current && scope === scopeRef.current && !seenIdsRef.current.has(msg.id)) {
|
|
279
291
|
seenIdsRef.current.add(msg.id);
|
|
280
292
|
setMessages((prev) => {
|
|
281
293
|
const optimisticIdx = prev.findIndex(
|
|
@@ -292,14 +304,17 @@ function useMessages(ticketId) {
|
|
|
292
304
|
});
|
|
293
305
|
return () => {
|
|
294
306
|
mountedRef.current = false;
|
|
307
|
+
requestRef.current++;
|
|
308
|
+
scopeRef.current++;
|
|
295
309
|
unsubscribe();
|
|
296
310
|
};
|
|
297
311
|
}, [client, ticketId, refresh]);
|
|
298
312
|
const send = (0, import_react5.useCallback)(
|
|
299
313
|
async (content) => {
|
|
300
314
|
if (!ticketId) throw new Error("No ticket selected");
|
|
315
|
+
const scope = scopeRef.current;
|
|
301
316
|
const optimistic = {
|
|
302
|
-
id: `optimistic-${
|
|
317
|
+
id: `optimistic-${++optimisticRef.current}`,
|
|
303
318
|
content,
|
|
304
319
|
sender: "customer",
|
|
305
320
|
sender_name: "",
|
|
@@ -310,11 +325,14 @@ function useMessages(ticketId) {
|
|
|
310
325
|
setMessages((prev) => [...prev, optimistic]);
|
|
311
326
|
try {
|
|
312
327
|
const msg = await client.messages.send(ticketId, content);
|
|
328
|
+
if (!mountedRef.current || scope !== scopeRef.current) return msg;
|
|
313
329
|
seenIdsRef.current.add(msg.id);
|
|
314
330
|
setMessages((prev) => prev.map((m) => m.id === optimistic.id ? msg : m));
|
|
315
331
|
return msg;
|
|
316
332
|
} catch (err) {
|
|
317
|
-
|
|
333
|
+
if (mountedRef.current && scope === scopeRef.current) {
|
|
334
|
+
setMessages((prev) => prev.filter((m) => m.id !== optimistic.id));
|
|
335
|
+
}
|
|
318
336
|
throw err;
|
|
319
337
|
}
|
|
320
338
|
},
|
|
@@ -742,7 +760,7 @@ function CStarDashboardProvider({
|
|
|
742
760
|
}) {
|
|
743
761
|
if (!config && !providedClient) {
|
|
744
762
|
throw new Error(
|
|
745
|
-
"CStarDashboardProvider requires either a `config` prop (`{ apiKey, teamId }
|
|
763
|
+
"CStarDashboardProvider requires either a `config` prop (`{ apiKey, teamId }`, recommended) or a `client` prop (a pre-built CStarClient instance, deprecated). See https://cstar.help/developers/sdk/react#dashboard"
|
|
746
764
|
);
|
|
747
765
|
}
|
|
748
766
|
const client = (0, import_react17.useMemo)(() => {
|
package/dist/index.d.cts
CHANGED
|
@@ -6,7 +6,6 @@ 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 * as react_jsx_runtime from 'react/jsx-runtime';
|
|
10
9
|
import { CStarClientConfig, CStarClient, RealtimeEvent, Boss, Player, LeaderboardEntry } from '@cstar.help/js';
|
|
11
10
|
|
|
12
11
|
declare const CStarChatContext: react.Context<ChatClient | 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. */
|
|
@@ -360,6 +359,11 @@ interface CStarDashboardProviderProps {
|
|
|
360
359
|
* provider builds the `CStarClient` internally so this prop can be passed
|
|
361
360
|
* across the React Server Components boundary without tripping the
|
|
362
361
|
* "Classes or null prototypes are not supported" runtime error.
|
|
362
|
+
* Every dashboard hook calls team endpoints, which need a secret key
|
|
363
|
+
* (a publishable key gets 403 publishable_key_forbidden). Config reaches
|
|
364
|
+
* the browser, so use the provider only where everyone who loads the page
|
|
365
|
+
* may hold that key (an internal tool behind your own login, a desktop
|
|
366
|
+
* app). On a public site, call your own backend instead.
|
|
363
367
|
*
|
|
364
368
|
* Either `config` or `client` is required.
|
|
365
369
|
*/
|
|
@@ -378,19 +382,23 @@ interface CStarDashboardProviderProps {
|
|
|
378
382
|
/**
|
|
379
383
|
* Provides a `CStarClient` to dashboard hooks.
|
|
380
384
|
*
|
|
381
|
-
*
|
|
382
|
-
*
|
|
383
|
-
*
|
|
385
|
+
* Every hook calls team endpoints, which need a secret key: publishable keys
|
|
386
|
+
* get 403 publishable_key_forbidden on reads and publishable_key_write on
|
|
387
|
+
* writes. Provider props and bundled clients are visible to the browser, so
|
|
388
|
+
* use the provider only in a trusted environment (an internal tool behind
|
|
389
|
+
* your own login, a desktop app), with a secret key scoped to what the
|
|
390
|
+
* dashboard needs. For anything public, have the app call your own backend,
|
|
391
|
+
* where a secret-key `CStarClient` stays on the server.
|
|
384
392
|
*
|
|
385
393
|
* ```tsx
|
|
386
|
-
* //
|
|
387
|
-
*
|
|
388
|
-
* <CStarDashboardProvider config={{ apiKey:
|
|
394
|
+
* // app/providers.tsx (an internal tool behind your own login)
|
|
395
|
+
* 'use client';
|
|
396
|
+
* <CStarDashboardProvider config={{ apiKey: secretKeyFromYourServer, teamId }}>
|
|
389
397
|
* <TicketList />
|
|
390
398
|
* </CStarDashboardProvider>
|
|
391
399
|
* ```
|
|
392
400
|
*/
|
|
393
|
-
declare function CStarDashboardProvider({ config, client: providedClient, children }: CStarDashboardProviderProps):
|
|
401
|
+
declare function CStarDashboardProvider({ config, client: providedClient, children }: CStarDashboardProviderProps): react__default.JSX.Element;
|
|
394
402
|
/**
|
|
395
403
|
* Access the `CStarClient` from the nearest `CStarDashboardProvider`.
|
|
396
404
|
* Throws if used outside a provider.
|
package/dist/index.d.ts
CHANGED
|
@@ -6,7 +6,6 @@ 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 * as react_jsx_runtime from 'react/jsx-runtime';
|
|
10
9
|
import { CStarClientConfig, CStarClient, RealtimeEvent, Boss, Player, LeaderboardEntry } from '@cstar.help/js';
|
|
11
10
|
|
|
12
11
|
declare const CStarChatContext: react.Context<ChatClient | 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. */
|
|
@@ -360,6 +359,11 @@ interface CStarDashboardProviderProps {
|
|
|
360
359
|
* provider builds the `CStarClient` internally so this prop can be passed
|
|
361
360
|
* across the React Server Components boundary without tripping the
|
|
362
361
|
* "Classes or null prototypes are not supported" runtime error.
|
|
362
|
+
* Every dashboard hook calls team endpoints, which need a secret key
|
|
363
|
+
* (a publishable key gets 403 publishable_key_forbidden). Config reaches
|
|
364
|
+
* the browser, so use the provider only where everyone who loads the page
|
|
365
|
+
* may hold that key (an internal tool behind your own login, a desktop
|
|
366
|
+
* app). On a public site, call your own backend instead.
|
|
363
367
|
*
|
|
364
368
|
* Either `config` or `client` is required.
|
|
365
369
|
*/
|
|
@@ -378,19 +382,23 @@ interface CStarDashboardProviderProps {
|
|
|
378
382
|
/**
|
|
379
383
|
* Provides a `CStarClient` to dashboard hooks.
|
|
380
384
|
*
|
|
381
|
-
*
|
|
382
|
-
*
|
|
383
|
-
*
|
|
385
|
+
* Every hook calls team endpoints, which need a secret key: publishable keys
|
|
386
|
+
* get 403 publishable_key_forbidden on reads and publishable_key_write on
|
|
387
|
+
* writes. Provider props and bundled clients are visible to the browser, so
|
|
388
|
+
* use the provider only in a trusted environment (an internal tool behind
|
|
389
|
+
* your own login, a desktop app), with a secret key scoped to what the
|
|
390
|
+
* dashboard needs. For anything public, have the app call your own backend,
|
|
391
|
+
* where a secret-key `CStarClient` stays on the server.
|
|
384
392
|
*
|
|
385
393
|
* ```tsx
|
|
386
|
-
* //
|
|
387
|
-
*
|
|
388
|
-
* <CStarDashboardProvider config={{ apiKey:
|
|
394
|
+
* // app/providers.tsx (an internal tool behind your own login)
|
|
395
|
+
* 'use client';
|
|
396
|
+
* <CStarDashboardProvider config={{ apiKey: secretKeyFromYourServer, teamId }}>
|
|
389
397
|
* <TicketList />
|
|
390
398
|
* </CStarDashboardProvider>
|
|
391
399
|
* ```
|
|
392
400
|
*/
|
|
393
|
-
declare function CStarDashboardProvider({ config, client: providedClient, children }: CStarDashboardProviderProps):
|
|
401
|
+
declare function CStarDashboardProvider({ config, client: providedClient, children }: CStarDashboardProviderProps): react__default.JSX.Element;
|
|
394
402
|
/**
|
|
395
403
|
* Access the `CStarClient` from the nearest `CStarDashboardProvider`.
|
|
396
404
|
* Throws if used outside a provider.
|
package/dist/index.js
CHANGED
|
@@ -191,36 +191,48 @@ function useMessages(ticketId) {
|
|
|
191
191
|
const [error, setError] = useState3(null);
|
|
192
192
|
const mountedRef = useRef3(true);
|
|
193
193
|
const seenIdsRef = useRef3(/* @__PURE__ */ new Set());
|
|
194
|
+
const requestRef = useRef3(0);
|
|
195
|
+
const scopeRef = useRef3(0);
|
|
196
|
+
const optimisticRef = useRef3(0);
|
|
194
197
|
const refresh = useCallback3(async () => {
|
|
195
198
|
if (!ticketId) return;
|
|
199
|
+
const request = ++requestRef.current;
|
|
196
200
|
setIsLoading(true);
|
|
197
201
|
setError(null);
|
|
198
202
|
try {
|
|
199
203
|
const msgs = await client.messages.list(ticketId);
|
|
200
|
-
if (mountedRef.current) {
|
|
204
|
+
if (mountedRef.current && request === requestRef.current) {
|
|
201
205
|
seenIdsRef.current = new Set(msgs.map((m) => m.id));
|
|
202
206
|
setMessages(msgs);
|
|
203
207
|
}
|
|
204
208
|
} catch (err) {
|
|
205
|
-
if (mountedRef.current) {
|
|
209
|
+
if (mountedRef.current && request === requestRef.current) {
|
|
206
210
|
setError(err instanceof Error ? err : new Error(String(err)));
|
|
207
211
|
}
|
|
208
212
|
} finally {
|
|
209
|
-
if (mountedRef.current) {
|
|
213
|
+
if (mountedRef.current && request === requestRef.current) {
|
|
210
214
|
setIsLoading(false);
|
|
211
215
|
}
|
|
212
216
|
}
|
|
213
217
|
}, [client, ticketId]);
|
|
214
218
|
useEffect4(() => {
|
|
215
219
|
mountedRef.current = true;
|
|
220
|
+
const scope = ++scopeRef.current;
|
|
221
|
+
seenIdsRef.current = /* @__PURE__ */ new Set();
|
|
222
|
+
setMessages([]);
|
|
223
|
+
setError(null);
|
|
216
224
|
if (!ticketId) {
|
|
217
225
|
setMessages([]);
|
|
218
226
|
setIsLoading(false);
|
|
219
|
-
return
|
|
227
|
+
return () => {
|
|
228
|
+
mountedRef.current = false;
|
|
229
|
+
requestRef.current++;
|
|
230
|
+
scopeRef.current++;
|
|
231
|
+
};
|
|
220
232
|
}
|
|
221
233
|
refresh();
|
|
222
234
|
const unsubscribe = client.onMessage(ticketId, (msg) => {
|
|
223
|
-
if (mountedRef.current && !seenIdsRef.current.has(msg.id)) {
|
|
235
|
+
if (mountedRef.current && scope === scopeRef.current && !seenIdsRef.current.has(msg.id)) {
|
|
224
236
|
seenIdsRef.current.add(msg.id);
|
|
225
237
|
setMessages((prev) => {
|
|
226
238
|
const optimisticIdx = prev.findIndex(
|
|
@@ -237,14 +249,17 @@ function useMessages(ticketId) {
|
|
|
237
249
|
});
|
|
238
250
|
return () => {
|
|
239
251
|
mountedRef.current = false;
|
|
252
|
+
requestRef.current++;
|
|
253
|
+
scopeRef.current++;
|
|
240
254
|
unsubscribe();
|
|
241
255
|
};
|
|
242
256
|
}, [client, ticketId, refresh]);
|
|
243
257
|
const send = useCallback3(
|
|
244
258
|
async (content) => {
|
|
245
259
|
if (!ticketId) throw new Error("No ticket selected");
|
|
260
|
+
const scope = scopeRef.current;
|
|
246
261
|
const optimistic = {
|
|
247
|
-
id: `optimistic-${
|
|
262
|
+
id: `optimistic-${++optimisticRef.current}`,
|
|
248
263
|
content,
|
|
249
264
|
sender: "customer",
|
|
250
265
|
sender_name: "",
|
|
@@ -255,11 +270,14 @@ function useMessages(ticketId) {
|
|
|
255
270
|
setMessages((prev) => [...prev, optimistic]);
|
|
256
271
|
try {
|
|
257
272
|
const msg = await client.messages.send(ticketId, content);
|
|
273
|
+
if (!mountedRef.current || scope !== scopeRef.current) return msg;
|
|
258
274
|
seenIdsRef.current.add(msg.id);
|
|
259
275
|
setMessages((prev) => prev.map((m) => m.id === optimistic.id ? msg : m));
|
|
260
276
|
return msg;
|
|
261
277
|
} catch (err) {
|
|
262
|
-
|
|
278
|
+
if (mountedRef.current && scope === scopeRef.current) {
|
|
279
|
+
setMessages((prev) => prev.filter((m) => m.id !== optimistic.id));
|
|
280
|
+
}
|
|
263
281
|
throw err;
|
|
264
282
|
}
|
|
265
283
|
},
|
|
@@ -687,7 +705,7 @@ function CStarDashboardProvider({
|
|
|
687
705
|
}) {
|
|
688
706
|
if (!config && !providedClient) {
|
|
689
707
|
throw new Error(
|
|
690
|
-
"CStarDashboardProvider requires either a `config` prop (`{ apiKey, teamId }
|
|
708
|
+
"CStarDashboardProvider requires either a `config` prop (`{ apiKey, teamId }`, recommended) or a `client` prop (a pre-built CStarClient instance, deprecated). See https://cstar.help/developers/sdk/react#dashboard"
|
|
691
709
|
);
|
|
692
710
|
}
|
|
693
711
|
const client = useMemo(() => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cstar.help/react",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.2",
|
|
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": "
|
|
33
|
+
"@cstar.help/js": "^0.18.2"
|
|
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.18.2",
|
|
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",
|