@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 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+** — 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
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 — appears instantly
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 — no auth required.
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 — no auth required.
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} — {post.voteCount} votes
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 — and React Server Components can't serialize
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 — pass a plain config object.** The provider builds (and
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({ children }: { children: React.ReactNode }) {
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: process.env.NEXT_PUBLIC_CSTAR_PUBLISHABLE_KEY!,
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
- 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.
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 — pass a pre-built client (deprecated).** If you already have a
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: process.env.NEXT_PUBLIC_CSTAR_PUBLISHABLE_KEY!,
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 — only the provider boundary needs to be a client
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 18+ (for SSR)
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-${Date.now()}`,
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
- setMessages((prev) => prev.filter((m) => m.id !== optimistic.id));
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 }` \u2014 recommended) or a `client` prop (a pre-built CStarClient instance, deprecated). See https://cstar.help/developers/sdk/react#dashboard"
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): react_jsx_runtime.JSX.Element;
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): react_jsx_runtime.JSX.Element;
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): react_jsx_runtime.JSX.Element;
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
- * Unlike `CStarChatProvider` (which uses a publishable key for customer-facing
382
- * chat), this provider wraps a full admin client with a secret key for building
383
- * custom dashboards.
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
- * // Recommended: pass serializable config — works in any React tree, including
387
- * // App Router server components that hand the props to a client component.
388
- * <CStarDashboardProvider config={{ apiKey: process.env.CSTAR_API_KEY!, teamId }}>
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): react_jsx_runtime.JSX.Element;
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): react_jsx_runtime.JSX.Element;
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): react_jsx_runtime.JSX.Element;
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): react_jsx_runtime.JSX.Element;
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
- * Unlike `CStarChatProvider` (which uses a publishable key for customer-facing
382
- * chat), this provider wraps a full admin client with a secret key for building
383
- * custom dashboards.
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
- * // Recommended: pass serializable config — works in any React tree, including
387
- * // App Router server components that hand the props to a client component.
388
- * <CStarDashboardProvider config={{ apiKey: process.env.CSTAR_API_KEY!, teamId }}>
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): react_jsx_runtime.JSX.Element;
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-${Date.now()}`,
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
- setMessages((prev) => prev.filter((m) => m.id !== optimistic.id));
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 }` \u2014 recommended) or a `client` prop (a pre-built CStarClient instance, deprecated). See https://cstar.help/developers/sdk/react#dashboard"
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.0",
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": ">=0.15.0"
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": "file:../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/cstar-help/cstar",
55
+ "url": "https://github.com/msjoshlopez/cstar",
55
56
  "directory": "packages/react"
56
57
  },
57
58
  "bugs": {
58
- "url": "https://github.com/cstar-help/cstar/issues"
59
+ "url": "https://www.cstar.help/support"
59
60
  },
60
61
  "keywords": [
61
62
  "cstar",