@cstar.help/react 0.10.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 +1 -1
- package/dist/index.cjs +25 -7
- package/dist/index.d.cts +14 -11
- package/dist/index.d.ts +14 -11
- package/dist/index.js +25 -7
- package/package.json +6 -5
package/README.md
CHANGED
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
|
},
|
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,7 @@ 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
|
+
* Config is visible to the browser: use only a publishable key here.
|
|
363
363
|
*
|
|
364
364
|
* Either `config` or `client` is required.
|
|
365
365
|
*/
|
|
@@ -378,19 +378,22 @@ interface CStarDashboardProviderProps {
|
|
|
378
378
|
/**
|
|
379
379
|
* Provides a `CStarClient` to dashboard hooks.
|
|
380
380
|
*
|
|
381
|
-
*
|
|
382
|
-
*
|
|
383
|
-
*
|
|
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.
|
|
384
385
|
*
|
|
385
386
|
* ```tsx
|
|
386
|
-
* //
|
|
387
|
-
*
|
|
388
|
-
* <CStarDashboardProvider
|
|
387
|
+
* // app/providers.tsx
|
|
388
|
+
* 'use client';
|
|
389
|
+
* <CStarDashboardProvider
|
|
390
|
+
* config={{ apiKey: process.env.NEXT_PUBLIC_CSTAR_PUBLISHABLE_KEY!, teamId }}
|
|
391
|
+
* >
|
|
389
392
|
* <TicketList />
|
|
390
393
|
* </CStarDashboardProvider>
|
|
391
394
|
* ```
|
|
392
395
|
*/
|
|
393
|
-
declare function CStarDashboardProvider({ config, client: providedClient, children }: CStarDashboardProviderProps):
|
|
396
|
+
declare function CStarDashboardProvider({ config, client: providedClient, children }: CStarDashboardProviderProps): react__default.JSX.Element;
|
|
394
397
|
/**
|
|
395
398
|
* Access the `CStarClient` from the nearest `CStarDashboardProvider`.
|
|
396
399
|
* 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,7 @@ 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
|
+
* Config is visible to the browser: use only a publishable key here.
|
|
363
363
|
*
|
|
364
364
|
* Either `config` or `client` is required.
|
|
365
365
|
*/
|
|
@@ -378,19 +378,22 @@ interface CStarDashboardProviderProps {
|
|
|
378
378
|
/**
|
|
379
379
|
* Provides a `CStarClient` to dashboard hooks.
|
|
380
380
|
*
|
|
381
|
-
*
|
|
382
|
-
*
|
|
383
|
-
*
|
|
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.
|
|
384
385
|
*
|
|
385
386
|
* ```tsx
|
|
386
|
-
* //
|
|
387
|
-
*
|
|
388
|
-
* <CStarDashboardProvider
|
|
387
|
+
* // app/providers.tsx
|
|
388
|
+
* 'use client';
|
|
389
|
+
* <CStarDashboardProvider
|
|
390
|
+
* config={{ apiKey: process.env.NEXT_PUBLIC_CSTAR_PUBLISHABLE_KEY!, teamId }}
|
|
391
|
+
* >
|
|
389
392
|
* <TicketList />
|
|
390
393
|
* </CStarDashboardProvider>
|
|
391
394
|
* ```
|
|
392
395
|
*/
|
|
393
|
-
declare function CStarDashboardProvider({ config, client: providedClient, children }: CStarDashboardProviderProps):
|
|
396
|
+
declare function CStarDashboardProvider({ config, client: providedClient, children }: CStarDashboardProviderProps): react__default.JSX.Element;
|
|
394
397
|
/**
|
|
395
398
|
* Access the `CStarClient` from the nearest `CStarDashboardProvider`.
|
|
396
399
|
* 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
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cstar.help/react",
|
|
3
|
-
"version": "0.10.
|
|
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",
|
|
@@ -35,14 +35,15 @@
|
|
|
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",
|