@memori.ai/memori-react 8.44.0 → 8.45.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/CHANGELOG.md +19 -0
- package/dist/components/MemoriWidget/MemoriWidget.d.ts +1 -0
- package/dist/components/MemoriWidget/MemoriWidget.js +59 -1
- package/dist/components/MemoriWidget/MemoriWidget.js.map +1 -1
- package/dist/helpers/nats/getNatsConfig.js.map +1 -1
- package/dist/helpers/nats/useNats.d.ts +2 -1
- package/dist/helpers/nats/useNats.js +87 -20
- package/dist/helpers/nats/useNats.js.map +1 -1
- package/dist/helpers/nats/useNatsSession.d.ts +6 -1
- package/dist/helpers/nats/useNatsSession.js +49 -8
- package/dist/helpers/nats/useNatsSession.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/esm/components/MemoriWidget/MemoriWidget.d.ts +1 -0
- package/esm/components/MemoriWidget/MemoriWidget.js +57 -1
- package/esm/components/MemoriWidget/MemoriWidget.js.map +1 -1
- package/esm/helpers/nats/getNatsConfig.js.map +1 -1
- package/esm/helpers/nats/useNats.d.ts +2 -1
- package/esm/helpers/nats/useNats.js +87 -20
- package/esm/helpers/nats/useNats.js.map +1 -1
- package/esm/helpers/nats/useNatsSession.d.ts +6 -1
- package/esm/helpers/nats/useNatsSession.js +48 -8
- package/esm/helpers/nats/useNatsSession.js.map +1 -1
- package/esm/version.d.ts +1 -1
- package/esm/version.js +1 -1
- package/package.json +1 -1
- package/src/components/MemoriWidget/MemoriWidget.tsx +65 -1
- package/src/helpers/nats/getNatsConfig.ts +4 -1
- package/src/helpers/nats/useNats.test.tsx +81 -0
- package/src/helpers/nats/useNats.ts +109 -29
- package/src/helpers/nats/useNatsSession.test.ts +16 -0
- package/src/helpers/nats/useNatsSession.ts +79 -13
- package/src/version.ts +1 -1
- package/dist/components/ChatHistoryDrawer/ChatResumeDrawer.css +0 -372
- package/dist/components/DrawerFooter/DrawerFooter.css +0 -62
- package/dist/components/IconButton/IconButton.css +0 -144
- package/dist/components/MobileSessionPanel/MobileSessionPanel.css +0 -459
- package/dist/components/PositionPopover/PositionPopover.css +0 -219
- package/dist/components/layouts/WebsiteAssistant/website-assistant.css +0 -806
- package/dist/components/layouts/fullpage.css +0 -522
- package/esm/components/ChatHistoryDrawer/ChatResumeDrawer.css +0 -372
- package/esm/components/ChatHistoryDrawer/ChatResumeDrawer.d.ts +0 -36
- package/esm/components/ChatHistoryDrawer/ChatResumeDrawer.js +0 -113
- package/esm/components/ChatHistoryDrawer/ChatResumeDrawer.js.map +0 -1
- package/esm/components/DrawerFooter/DrawerFooter.css +0 -62
- package/esm/components/DrawerFooter/DrawerFooter.d.ts +0 -12
- package/esm/components/DrawerFooter/DrawerFooter.js +0 -8
- package/esm/components/DrawerFooter/DrawerFooter.js.map +0 -1
- package/esm/components/IconButton/IconButton.css +0 -144
- package/esm/components/IconButton/IconButton.d.ts +0 -9
- package/esm/components/IconButton/IconButton.js +0 -33
- package/esm/components/IconButton/IconButton.js.map +0 -1
- package/esm/components/MobileSessionPanel/MobileSessionPanel.css +0 -459
- package/esm/components/MobileSessionPanel/MobileSessionPanel.d.ts +0 -65
- package/esm/components/MobileSessionPanel/MobileSessionPanel.js +0 -291
- package/esm/components/MobileSessionPanel/MobileSessionPanel.js.map +0 -1
- package/esm/components/PositionPopover/PositionPopover.css +0 -219
- package/esm/components/PositionPopover/PositionPopover.d.ts +0 -22
- package/esm/components/PositionPopover/PositionPopover.js +0 -190
- package/esm/components/PositionPopover/PositionPopover.js.map +0 -1
- package/esm/components/layouts/WebsiteAssistant/WebsiteAssistant.d.ts +0 -4
- package/esm/components/layouts/WebsiteAssistant/WebsiteAssistant.js +0 -61
- package/esm/components/layouts/WebsiteAssistant/WebsiteAssistant.js.map +0 -1
- package/esm/components/layouts/WebsiteAssistant/website-assistant.css +0 -806
- package/esm/components/layouts/fullpage.css +0 -522
- package/esm/context/widgetSurfaceContext.d.ts +0 -3
- package/esm/context/widgetSurfaceContext.js +0 -7
- package/esm/context/widgetSurfaceContext.js.map +0 -1
- package/esm/helpers/positionPopover.d.ts +0 -1
- package/esm/helpers/positionPopover.js +0 -4
- package/esm/helpers/positionPopover.js.map +0 -1
- package/esm/helpers/tts/muteSpeaker.d.ts +0 -13
- package/esm/helpers/tts/muteSpeaker.js +0 -21
- package/esm/helpers/tts/muteSpeaker.js.map +0 -1
- package/esm/helpers/usePressTooltip.d.ts +0 -13
- package/esm/helpers/usePressTooltip.js +0 -20
- package/esm/helpers/usePressTooltip.js.map +0 -1
- package/esm/icons/FacebookIcon.d.ts +0 -3
- package/esm/icons/FacebookIcon.js +0 -4
- package/esm/icons/FacebookIcon.js.map +0 -1
- package/esm/icons/LinkedinIcon.d.ts +0 -3
- package/esm/icons/LinkedinIcon.js +0 -4
- package/esm/icons/LinkedinIcon.js.map +0 -1
- package/esm/icons/MemoriIcon.d.ts +0 -3
- package/esm/icons/MemoriIcon.js +0 -6
- package/esm/icons/MemoriIcon.js.map +0 -1
- package/esm/icons/TelegramIcon.d.ts +0 -3
- package/esm/icons/TelegramIcon.js +0 -4
- package/esm/icons/TelegramIcon.js.map +0 -1
- package/esm/icons/TwitterIcon.d.ts +0 -3
- package/esm/icons/TwitterIcon.js +0 -4
- package/esm/icons/TwitterIcon.js.map +0 -1
- package/esm/icons/WhatsappIcon.d.ts +0 -3
- package/esm/icons/WhatsappIcon.js +0 -4
- package/esm/icons/WhatsappIcon.js.map +0 -1
- package/esm/icons/artifacts/DocumentIcon.d.ts +0 -3
- package/esm/icons/artifacts/DocumentIcon.js +0 -4
- package/esm/icons/artifacts/DocumentIcon.js.map +0 -1
- package/esm/icons/artifacts/HtmlIcon.d.ts +0 -3
- package/esm/icons/artifacts/HtmlIcon.js +0 -4
- package/esm/icons/artifacts/HtmlIcon.js.map +0 -1
- package/esm/icons/artifacts/JavascriptIcon.d.ts +0 -3
- package/esm/icons/artifacts/JavascriptIcon.js +0 -4
- package/esm/icons/artifacts/JavascriptIcon.js.map +0 -1
- package/esm/icons/artifacts/MarkdownIcon.d.ts +0 -3
- package/esm/icons/artifacts/MarkdownIcon.js +0 -4
- package/esm/icons/artifacts/MarkdownIcon.js.map +0 -1
- package/esm/icons/artifacts/PythonIcon.d.ts +0 -3
- package/esm/icons/artifacts/PythonIcon.js +0 -4
- package/esm/icons/artifacts/PythonIcon.js.map +0 -1
- package/esm/icons/artifacts/SvgIcon.d.ts +0 -3
- package/esm/icons/artifacts/SvgIcon.js +0 -4
- package/esm/icons/artifacts/SvgIcon.js.map +0 -1
- package/esm/icons/artifacts/XmlIcon.d.ts +0 -3
- package/esm/icons/artifacts/XmlIcon.js +0 -4
- package/esm/icons/artifacts/XmlIcon.js.map +0 -1
- package/esm/icons/artifacts/cssIcon.d.ts +0 -3
- package/esm/icons/artifacts/cssIcon.js +0 -4
- package/esm/icons/artifacts/cssIcon.js.map +0 -1
- package/esm/testUtils.d.ts +0 -5
- package/esm/testUtils.js +0 -15
- package/esm/testUtils.js.map +0 -1
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { useEffect, useRef, useState } from 'react';
|
|
2
|
-
import { wsconnect } from '@nats-io/nats-core';
|
|
2
|
+
import { wsconnect, jwtAuthenticator, AuthorizationError, UserAuthenticationExpiredError, } from '@nats-io/nats-core';
|
|
3
3
|
import { jetstream, DeliverPolicy, } from '@nats-io/jetstream';
|
|
4
|
+
export function isNatsAuthError(error) {
|
|
5
|
+
return (error instanceof AuthorizationError ||
|
|
6
|
+
error instanceof UserAuthenticationExpiredError ||
|
|
7
|
+
(error instanceof Error &&
|
|
8
|
+
/authorization violation|authentication expired|user authentication expired/i.test(error.message)));
|
|
9
|
+
}
|
|
4
10
|
function readString(raw, camelKey, snakeKey) {
|
|
5
11
|
var _a;
|
|
6
12
|
const value = (_a = raw[camelKey]) !== null && _a !== void 0 ? _a : raw[snakeKey];
|
|
@@ -63,9 +69,10 @@ function decodeMessage(subject, received, raw, onMessage) {
|
|
|
63
69
|
console.debug(`[NATS] message #${received} on ${subject} (eventType=${payload.eventType})`, payload);
|
|
64
70
|
onMessage(payload);
|
|
65
71
|
}
|
|
66
|
-
async function consumeCoreSubscription(nc, subject, onMessage) {
|
|
72
|
+
async function consumeCoreSubscription(nc, subject, onMessage, onReady) {
|
|
67
73
|
const sub = nc.subscribe(subject);
|
|
68
74
|
console.info('[NATS] subscribed to subject', subject);
|
|
75
|
+
onReady();
|
|
69
76
|
let received = 0;
|
|
70
77
|
for await (const msg of sub) {
|
|
71
78
|
received += 1;
|
|
@@ -78,7 +85,7 @@ async function consumeCoreSubscription(nc, subject, onMessage) {
|
|
|
78
85
|
}
|
|
79
86
|
console.info(`[NATS] subscription on ${subject} closed (received ${received} messages)`);
|
|
80
87
|
}
|
|
81
|
-
async function consumeJetStream(nc, config, subject, mode, onMessage, setConsumerMessages) {
|
|
88
|
+
async function consumeJetStream(nc, config, subject, mode, onMessage, setConsumerMessages, onReady) {
|
|
82
89
|
const js = jetstream(nc);
|
|
83
90
|
const consumer = config.consumer
|
|
84
91
|
? await js.consumers.get(config.stream, config.consumer)
|
|
@@ -93,6 +100,7 @@ async function consumeJetStream(nc, config, subject, mode, onMessage, setConsume
|
|
|
93
100
|
: { stream: config.stream, subject, mode });
|
|
94
101
|
const messages = await consumer.consume();
|
|
95
102
|
setConsumerMessages(messages);
|
|
103
|
+
onReady();
|
|
96
104
|
let received = 0;
|
|
97
105
|
for await (const msg of messages) {
|
|
98
106
|
received += 1;
|
|
@@ -112,15 +120,19 @@ function closeNatsSession(consumerMessagesRef, connRef) {
|
|
|
112
120
|
(_b = connRef.current) === null || _b === void 0 ? void 0 : _b.close();
|
|
113
121
|
connRef.current = null;
|
|
114
122
|
}
|
|
115
|
-
export function useNatsSession(sessionId, config, onMessage) {
|
|
123
|
+
export function useNatsSession(sessionId, config, onMessage, lifecycle = {}) {
|
|
116
124
|
const connRef = useRef(null);
|
|
117
125
|
const consumerMessagesRef = useRef(null);
|
|
118
126
|
const onMessageRef = useRef(onMessage);
|
|
127
|
+
const lifecycleRef = useRef(lifecycle);
|
|
119
128
|
const wasHiddenRef = useRef(false);
|
|
120
129
|
const [resumeGeneration, setResumeGeneration] = useState(0);
|
|
121
130
|
useEffect(() => {
|
|
122
131
|
onMessageRef.current = onMessage;
|
|
123
132
|
}, [onMessage]);
|
|
133
|
+
useEffect(() => {
|
|
134
|
+
lifecycleRef.current = lifecycle;
|
|
135
|
+
}, [lifecycle]);
|
|
124
136
|
useEffect(() => {
|
|
125
137
|
if (!sessionId || !(config === null || config === void 0 ? void 0 : config.stream)) {
|
|
126
138
|
return;
|
|
@@ -174,7 +186,7 @@ export function useNatsSession(sessionId, config, onMessage) {
|
|
|
174
186
|
console.info('[NATS] connecting to', config.url, 'for session', sessionId, useJetStream ? `(JetStream/${jetStreamMode})` : '(core)');
|
|
175
187
|
const nc = await wsconnect({
|
|
176
188
|
servers: [config.url],
|
|
177
|
-
|
|
189
|
+
authenticator: jwtAuthenticator(config.token),
|
|
178
190
|
});
|
|
179
191
|
if (closed) {
|
|
180
192
|
console.debug('[NATS] connection established after cleanup, closing immediately');
|
|
@@ -186,15 +198,43 @@ export function useNatsSession(sessionId, config, onMessage) {
|
|
|
186
198
|
const dispatch = (event) => {
|
|
187
199
|
onMessageRef.current(event);
|
|
188
200
|
};
|
|
201
|
+
const ready = () => { var _a, _b; return (_b = (_a = lifecycleRef.current).onConnected) === null || _b === void 0 ? void 0 : _b.call(_a); };
|
|
202
|
+
let authRefreshRequested = false;
|
|
203
|
+
const requestAuthRefresh = (error) => {
|
|
204
|
+
var _a, _b;
|
|
205
|
+
if (closed || authRefreshRequested)
|
|
206
|
+
return;
|
|
207
|
+
authRefreshRequested = true;
|
|
208
|
+
(_b = (_a = lifecycleRef.current).onAuthError) === null || _b === void 0 ? void 0 : _b.call(_a, error);
|
|
209
|
+
};
|
|
210
|
+
void (async () => {
|
|
211
|
+
for await (const status of nc.status()) {
|
|
212
|
+
if (status.type === 'error' && isNatsAuthError(status.error)) {
|
|
213
|
+
requestAuthRefresh(status.error);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
})();
|
|
218
|
+
void nc.closed().then(error => {
|
|
219
|
+
if (error && isNatsAuthError(error)) {
|
|
220
|
+
requestAuthRefresh(error);
|
|
221
|
+
}
|
|
222
|
+
});
|
|
189
223
|
if (useJetStream) {
|
|
190
224
|
await consumeJetStream(nc, config, subject, jetStreamMode, dispatch, messages => {
|
|
191
225
|
consumerMessagesRef.current = messages;
|
|
192
|
-
});
|
|
226
|
+
}, ready);
|
|
193
227
|
}
|
|
194
228
|
else {
|
|
195
|
-
await consumeCoreSubscription(nc, subject, dispatch);
|
|
229
|
+
await consumeCoreSubscription(nc, subject, dispatch, ready);
|
|
230
|
+
}
|
|
231
|
+
})().catch(err => {
|
|
232
|
+
var _a, _b;
|
|
233
|
+
console.error('[NATS] connection error', err);
|
|
234
|
+
if (!closed && isNatsAuthError(err)) {
|
|
235
|
+
(_b = (_a = lifecycleRef.current).onAuthError) === null || _b === void 0 ? void 0 : _b.call(_a, err);
|
|
196
236
|
}
|
|
197
|
-
})
|
|
237
|
+
});
|
|
198
238
|
return () => {
|
|
199
239
|
console.info('[NATS] cleanup: closing connection for subject', subject);
|
|
200
240
|
closed = true;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useNatsSession.js","sourceRoot":"","sources":["../../../src/helpers/nats/useNatsSession.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAyB,MAAM,OAAO,CAAC;AAC3E,OAAO,
|
|
1
|
+
{"version":3,"file":"useNatsSession.js","sourceRoot":"","sources":["../../../src/helpers/nats/useNatsSession.ts"],"names":[],"mappings":"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"}
|
package/esm/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "8.
|
|
1
|
+
export declare const version = "8.45.1";
|
package/esm/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const version = '8.
|
|
1
|
+
export const version = '8.45.1';
|
|
2
2
|
//# sourceMappingURL=version.js.map
|
package/package.json
CHANGED
|
@@ -159,6 +159,22 @@ function readCorrelationID(response: {
|
|
|
159
159
|
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
+
/** Stable engine-owned fields used to tell whether a pending turn completed. */
|
|
163
|
+
export function dialogStateFingerprint(state: DialogState | undefined): string {
|
|
164
|
+
if (!state) return '';
|
|
165
|
+
return JSON.stringify({
|
|
166
|
+
state: state.state,
|
|
167
|
+
previousState: state.previousState,
|
|
168
|
+
emission: state.emission,
|
|
169
|
+
emitter: state.emitter,
|
|
170
|
+
lastMatchedMemoryID: state.lastMatchedMemoryID,
|
|
171
|
+
currentDate: state.currentDate,
|
|
172
|
+
currentMemoryID: state.currentMemoryID,
|
|
173
|
+
contextVars: state.contextVars,
|
|
174
|
+
emittedMedia: state.emittedMedia,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
162
178
|
type MemoriTextEnteredEvent = CustomEvent<{
|
|
163
179
|
text: string;
|
|
164
180
|
waitForPrevious?: boolean;
|
|
@@ -724,6 +740,7 @@ const MemoriWidget = ({
|
|
|
724
740
|
|
|
725
741
|
type PendingEnterText = EnterTextRetryParams & {
|
|
726
742
|
msg?: string;
|
|
743
|
+
stateBeforeRequest?: string;
|
|
727
744
|
waitForResponse?: {
|
|
728
745
|
resolve: (event: NatsDialogResponseEvent) => void;
|
|
729
746
|
reject: (error: Error) => void;
|
|
@@ -1073,6 +1090,7 @@ const MemoriWidget = ({
|
|
|
1073
1090
|
if (response.resultCode === 0 && correlationID) {
|
|
1074
1091
|
registerPendingEnterText(correlationID, {
|
|
1075
1092
|
msg,
|
|
1093
|
+
stateBeforeRequest: dialogStateFingerprint(currentDialogState),
|
|
1076
1094
|
text,
|
|
1077
1095
|
media,
|
|
1078
1096
|
translate,
|
|
@@ -2363,6 +2381,7 @@ const MemoriWidget = ({
|
|
|
2363
2381
|
}, timeoutMs);
|
|
2364
2382
|
|
|
2365
2383
|
registerPendingEnterText(correlationID, {
|
|
2384
|
+
stateBeforeRequest: dialogStateFingerprint(currentDialogState),
|
|
2366
2385
|
waitForResponse: {
|
|
2367
2386
|
resolve: event => {
|
|
2368
2387
|
clearTimeout(timeoutId);
|
|
@@ -2376,9 +2395,53 @@ const MemoriWidget = ({
|
|
|
2376
2395
|
},
|
|
2377
2396
|
});
|
|
2378
2397
|
}),
|
|
2379
|
-
[registerPendingEnterText, clearEnterTextPending]
|
|
2398
|
+
[registerPendingEnterText, clearEnterTextPending, currentDialogState]
|
|
2380
2399
|
);
|
|
2381
2400
|
|
|
2401
|
+
const catchUpPendingEnterText = useCallback(async () => {
|
|
2402
|
+
if (!sessionId || pendingEnterTextRef.current.size === 0) return;
|
|
2403
|
+
|
|
2404
|
+
// Async turns are registered in send order. The current session state can
|
|
2405
|
+
// only represent the latest completed turn, so reconcile the latest one.
|
|
2406
|
+
const latest = Array.from(pendingEnterTextRef.current.entries()).pop();
|
|
2407
|
+
if (!latest) return;
|
|
2408
|
+
const [correlationID, pending] = latest;
|
|
2409
|
+
|
|
2410
|
+
try {
|
|
2411
|
+
const { currentState, resultCode, resultMessage, requestID } =
|
|
2412
|
+
await getSession(sessionId);
|
|
2413
|
+
|
|
2414
|
+
if (resultCode !== 0 || !currentState) {
|
|
2415
|
+
deliverEnterTextNatsResponse(correlationID, {
|
|
2416
|
+
eventType: 'dialog_text_entered_response',
|
|
2417
|
+
correlationID,
|
|
2418
|
+
requestID,
|
|
2419
|
+
resultCode,
|
|
2420
|
+
resultMessage,
|
|
2421
|
+
currentState,
|
|
2422
|
+
});
|
|
2423
|
+
return;
|
|
2424
|
+
}
|
|
2425
|
+
|
|
2426
|
+
if (dialogStateFingerprint(currentState) === pending.stateBeforeRequest) {
|
|
2427
|
+
console.info('[NATS] catch-up: pending turn has not completed yet');
|
|
2428
|
+
return;
|
|
2429
|
+
}
|
|
2430
|
+
|
|
2431
|
+
console.info('[NATS] catch-up: applying current engine session state');
|
|
2432
|
+
deliverEnterTextNatsResponse(correlationID, {
|
|
2433
|
+
eventType: 'dialog_text_entered_response',
|
|
2434
|
+
correlationID,
|
|
2435
|
+
requestID,
|
|
2436
|
+
resultCode,
|
|
2437
|
+
resultMessage,
|
|
2438
|
+
currentState,
|
|
2439
|
+
});
|
|
2440
|
+
} catch (error) {
|
|
2441
|
+
logWidgetError('NATS catch-up failed', error);
|
|
2442
|
+
}
|
|
2443
|
+
}, [sessionId, deliverEnterTextNatsResponse]);
|
|
2444
|
+
|
|
2382
2445
|
// NATS subscription: receives progress updates and the async enter-text response.
|
|
2383
2446
|
useNats({
|
|
2384
2447
|
baseUrl,
|
|
@@ -2403,6 +2466,7 @@ const MemoriWidget = ({
|
|
|
2403
2466
|
[deliverEnterTextNatsResponse]
|
|
2404
2467
|
),
|
|
2405
2468
|
onError: deliverEnterTextNatsError,
|
|
2469
|
+
onCatchUp: catchUpPendingEnterText,
|
|
2406
2470
|
});
|
|
2407
2471
|
|
|
2408
2472
|
const focusChatInput = () => {
|
|
@@ -7,7 +7,10 @@
|
|
|
7
7
|
export interface NatsConfig {
|
|
8
8
|
/** WebSocket URL of the NATS server (e.g. wss://nats.hz.slnode.net:8080). */
|
|
9
9
|
url: string;
|
|
10
|
-
/**
|
|
10
|
+
/**
|
|
11
|
+
* Per-session NATS user JWT. Presented with `jwtAuthenticator` (bearer:
|
|
12
|
+
* no nkey seed on the client).
|
|
13
|
+
*/
|
|
11
14
|
token: string;
|
|
12
15
|
/**
|
|
13
16
|
* JetStream stream that stores session events. When set, the client consumes
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { act, renderHook, waitFor } from '@testing-library/react';
|
|
2
|
+
import { getNatsConfig } from './getNatsConfig';
|
|
3
|
+
import { useNats } from './useNats';
|
|
4
|
+
import { NatsSessionLifecycle, useNatsSession } from './useNatsSession';
|
|
5
|
+
|
|
6
|
+
jest.mock('./getNatsConfig', () => ({
|
|
7
|
+
getNatsConfig: jest.fn(),
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
jest.mock('./useNatsSession', () => ({
|
|
11
|
+
useNatsSession: jest.fn(),
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
const mockGetNatsConfig = getNatsConfig as jest.MockedFunction<
|
|
15
|
+
typeof getNatsConfig
|
|
16
|
+
>;
|
|
17
|
+
const mockUseNatsSession = useNatsSession as jest.MockedFunction<
|
|
18
|
+
typeof useNatsSession
|
|
19
|
+
>;
|
|
20
|
+
|
|
21
|
+
function latestLifecycle(): NatsSessionLifecycle {
|
|
22
|
+
return mockUseNatsSession.mock.calls.at(-1)?.[3] ?? {};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe('useNats credential lifecycle', () => {
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
jest.clearAllMocks();
|
|
28
|
+
mockGetNatsConfig.mockResolvedValue({
|
|
29
|
+
url: 'wss://nats.example.test',
|
|
30
|
+
token: 'fresh-jwt',
|
|
31
|
+
});
|
|
32
|
+
Object.defineProperty(document, 'visibilityState', {
|
|
33
|
+
configurable: true,
|
|
34
|
+
value: 'visible',
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('refreshes the JWT on resume and catches up after reconnecting', async () => {
|
|
39
|
+
const onCatchUp = jest.fn();
|
|
40
|
+
renderHook(() =>
|
|
41
|
+
useNats({
|
|
42
|
+
baseUrl: 'https://dashboard.example.test',
|
|
43
|
+
sessionId: 'session-a',
|
|
44
|
+
onCatchUp,
|
|
45
|
+
})
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
await waitFor(() => expect(mockGetNatsConfig).toHaveBeenCalledTimes(1));
|
|
49
|
+
|
|
50
|
+
act(() => {
|
|
51
|
+
(document.dispatchEvent as (event: Event) => boolean)(
|
|
52
|
+
new Event('visibilitychange')
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
await waitFor(() => expect(mockGetNatsConfig).toHaveBeenCalledTimes(2));
|
|
56
|
+
|
|
57
|
+
act(() => latestLifecycle().onConnected?.());
|
|
58
|
+
expect(onCatchUp).toHaveBeenCalledTimes(1);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('refreshes the JWT after an authorization error', async () => {
|
|
62
|
+
const onCatchUp = jest.fn();
|
|
63
|
+
renderHook(() =>
|
|
64
|
+
useNats({
|
|
65
|
+
baseUrl: 'https://dashboard.example.test',
|
|
66
|
+
sessionId: 'session-a',
|
|
67
|
+
onCatchUp,
|
|
68
|
+
})
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
await waitFor(() => expect(mockGetNatsConfig).toHaveBeenCalledTimes(1));
|
|
72
|
+
|
|
73
|
+
act(() => {
|
|
74
|
+
latestLifecycle().onAuthError?.(new Error('Authorization Violation'));
|
|
75
|
+
});
|
|
76
|
+
await waitFor(() => expect(mockGetNatsConfig).toHaveBeenCalledTimes(2));
|
|
77
|
+
|
|
78
|
+
act(() => latestLifecycle().onConnected?.());
|
|
79
|
+
expect(onCatchUp).toHaveBeenCalledTimes(1);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
@@ -24,6 +24,11 @@ export interface UseNatsOptions {
|
|
|
24
24
|
onDialogResponse?: (event: NatsDialogResponseEvent) => void;
|
|
25
25
|
/** `error` events (logging / user notification). */
|
|
26
26
|
onError?: (event: NatsErrorEvent) => void;
|
|
27
|
+
/**
|
|
28
|
+
* Reconcile a pending turn from the engine after a resumed or reauthenticated
|
|
29
|
+
* subscription is installed.
|
|
30
|
+
*/
|
|
31
|
+
onCatchUp?: () => void | Promise<void>;
|
|
27
32
|
}
|
|
28
33
|
|
|
29
34
|
/**
|
|
@@ -38,6 +43,7 @@ export function useNats({
|
|
|
38
43
|
onProgress,
|
|
39
44
|
onDialogResponse,
|
|
40
45
|
onError,
|
|
46
|
+
onCatchUp,
|
|
41
47
|
}: UseNatsOptions) {
|
|
42
48
|
const [config, setConfig] = useState<NatsConfig | null>(null);
|
|
43
49
|
const [configError, setConfigError] = useState<Error | null>(null);
|
|
@@ -46,33 +52,42 @@ export function useNats({
|
|
|
46
52
|
const onProgressRef = useRef(onProgress);
|
|
47
53
|
const onDialogResponseRef = useRef(onDialogResponse);
|
|
48
54
|
const onErrorRef = useRef(onError);
|
|
55
|
+
const onCatchUpRef = useRef(onCatchUp);
|
|
56
|
+
const abortRef = useRef<AbortController | null>(null);
|
|
57
|
+
const catchUpAfterConnectRef = useRef(false);
|
|
58
|
+
const refreshInFlightRef = useRef(false);
|
|
59
|
+
const lastAuthRefreshRef = useRef(0);
|
|
49
60
|
useEffect(() => {
|
|
50
61
|
onProgressRef.current = onProgress;
|
|
51
62
|
onDialogResponseRef.current = onDialogResponse;
|
|
52
63
|
onErrorRef.current = onError;
|
|
53
|
-
|
|
64
|
+
onCatchUpRef.current = onCatchUp;
|
|
65
|
+
}, [onProgress, onDialogResponse, onError, onCatchUp]);
|
|
54
66
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
67
|
+
const refreshConfig = useCallback(
|
|
68
|
+
async (reason: 'initial' | 'resume' | 'auth') => {
|
|
69
|
+
if (!sessionId || refreshInFlightRef.current) return;
|
|
70
|
+
if (reason === 'auth' && Date.now() - lastAuthRefreshRef.current < 5000) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
63
73
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
refreshInFlightRef.current = true;
|
|
75
|
+
if (reason === 'auth') lastAuthRefreshRef.current = Date.now();
|
|
76
|
+
abortRef.current?.abort();
|
|
77
|
+
const controller = new AbortController();
|
|
78
|
+
abortRef.current = controller;
|
|
79
|
+
|
|
80
|
+
console.info(
|
|
81
|
+
'[NATS] fetching config from',
|
|
82
|
+
`${baseUrl}/api/nats`,
|
|
83
|
+
'for session',
|
|
84
|
+
sessionId,
|
|
85
|
+
`(${reason})`
|
|
86
|
+
);
|
|
87
|
+
try {
|
|
88
|
+
const cfg = await getNatsConfig(baseUrl, sessionId, controller.signal);
|
|
89
|
+
if (!controller.signal.aborted) {
|
|
90
|
+
catchUpAfterConnectRef.current = reason !== 'initial';
|
|
76
91
|
console.info('[NATS] config received', {
|
|
77
92
|
url: cfg.url,
|
|
78
93
|
jetStream: !!cfg.stream,
|
|
@@ -81,20 +96,65 @@ export function useNats({
|
|
|
81
96
|
setConfig(cfg);
|
|
82
97
|
setConfigError(null);
|
|
83
98
|
}
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
if (!cancelled && err?.name !== 'AbortError') {
|
|
99
|
+
} catch (err: any) {
|
|
100
|
+
if (!controller.signal.aborted && err?.name !== 'AbortError') {
|
|
87
101
|
console.error('[NATS] config error', err);
|
|
88
102
|
setConfig(null);
|
|
89
103
|
setConfigError(err instanceof Error ? err : new Error(String(err)));
|
|
90
104
|
}
|
|
91
|
-
}
|
|
105
|
+
} finally {
|
|
106
|
+
if (abortRef.current === controller) {
|
|
107
|
+
abortRef.current = null;
|
|
108
|
+
refreshInFlightRef.current = false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
[baseUrl, sessionId]
|
|
113
|
+
);
|
|
92
114
|
|
|
115
|
+
// Fetch connection config whenever the active session changes.
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
if (!sessionId) {
|
|
118
|
+
console.debug('[NATS] no sessionId, skipping config fetch');
|
|
119
|
+
setConfig(null);
|
|
120
|
+
setConfigError(null);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
void refreshConfig('initial');
|
|
93
125
|
return () => {
|
|
94
|
-
|
|
95
|
-
|
|
126
|
+
abortRef.current?.abort();
|
|
127
|
+
abortRef.current = null;
|
|
128
|
+
refreshInFlightRef.current = false;
|
|
96
129
|
};
|
|
97
|
-
}, [
|
|
130
|
+
}, [sessionId, refreshConfig]);
|
|
131
|
+
|
|
132
|
+
// A suspended browser may lose core NATS messages. Refresh the short-lived
|
|
133
|
+
// JWT, reconnect, then reconcile the pending turn from the engine.
|
|
134
|
+
useEffect(() => {
|
|
135
|
+
if (!sessionId) return;
|
|
136
|
+
|
|
137
|
+
const requestResume = () => {
|
|
138
|
+
if (document.visibilityState === 'visible') {
|
|
139
|
+
void refreshConfig('resume');
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
const onVisibilityChange = () => {
|
|
143
|
+
if (document.visibilityState === 'visible') requestResume();
|
|
144
|
+
};
|
|
145
|
+
const onPageShow = (event: PageTransitionEvent) => {
|
|
146
|
+
if (event.persisted) requestResume();
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
document.addEventListener('visibilitychange', onVisibilityChange);
|
|
150
|
+
window.addEventListener('online', requestResume);
|
|
151
|
+
window.addEventListener('pageshow', onPageShow);
|
|
152
|
+
return () => {
|
|
153
|
+
document.removeEventListener('visibilitychange', onVisibilityChange);
|
|
154
|
+
window.removeEventListener('online', requestResume);
|
|
155
|
+
window.removeEventListener('pageshow', onPageShow);
|
|
156
|
+
};
|
|
157
|
+
}, [sessionId, refreshConfig]);
|
|
98
158
|
|
|
99
159
|
const handleMessage = useCallback((event: NatsSessionEvent) => {
|
|
100
160
|
console.debug('[NATS] dispatching event', { eventType: event.eventType });
|
|
@@ -113,7 +173,27 @@ export function useNats({
|
|
|
113
173
|
}
|
|
114
174
|
}, []);
|
|
115
175
|
|
|
116
|
-
|
|
176
|
+
const handleConnected = useCallback(() => {
|
|
177
|
+
if (!catchUpAfterConnectRef.current) return;
|
|
178
|
+
catchUpAfterConnectRef.current = false;
|
|
179
|
+
void onCatchUpRef.current?.();
|
|
180
|
+
}, []);
|
|
181
|
+
|
|
182
|
+
const handleAuthError = useCallback(
|
|
183
|
+
(error: Error) => {
|
|
184
|
+
console.warn(
|
|
185
|
+
'[NATS] JWT rejected or expired; refreshing credentials',
|
|
186
|
+
error
|
|
187
|
+
);
|
|
188
|
+
void refreshConfig('auth');
|
|
189
|
+
},
|
|
190
|
+
[refreshConfig]
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
useNatsSession(sessionId, config ?? undefined, handleMessage, {
|
|
194
|
+
onConnected: handleConnected,
|
|
195
|
+
onAuthError: handleAuthError,
|
|
196
|
+
});
|
|
117
197
|
|
|
118
198
|
return {
|
|
119
199
|
/** True once connection config has been retrieved. */
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { AuthorizationError } from '@nats-io/nats-core';
|
|
2
|
+
import { isNatsAuthError } from './useNatsSession';
|
|
3
|
+
|
|
4
|
+
describe('isNatsAuthError', () => {
|
|
5
|
+
it('recognizes typed NATS authorization failures', () => {
|
|
6
|
+
expect(isNatsAuthError(new AuthorizationError('test'))).toBe(true);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('recognizes the server authorization violation message', () => {
|
|
10
|
+
expect(isNatsAuthError(new Error('Authorization Violation'))).toBe(true);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('does not classify unrelated connection failures as auth failures', () => {
|
|
14
|
+
expect(isNatsAuthError(new Error('connection refused'))).toBe(false);
|
|
15
|
+
});
|
|
16
|
+
});
|