UNPKG

@memori.ai/memori-react

Version:

[![npm version](https://img.shields.io/github/package-json/v/memori-ai/memori-react)](https://www.npmjs.com/package/@memori.ai/memori-react) ![Tests](https://github.com/memori-ai/memori-react/workflows/CI/badge.svg?branch=main) ![TypeScript Support](https

208 lines 8.78 kB
import { useEffect, useRef, useState } from 'react'; import { wsconnect } from '@nats-io/nats-core'; import { jetstream, DeliverPolicy, } from '@nats-io/jetstream'; function readString(raw, camelKey, snakeKey) { var _a; const value = (_a = raw[camelKey]) !== null && _a !== void 0 ? _a : raw[snakeKey]; return typeof value === 'string' ? value : undefined; } function readNumber(raw, camelKey, snakeKey) { var _a; const value = (_a = raw[camelKey]) !== null && _a !== void 0 ? _a : raw[snakeKey]; return typeof value === 'number' ? value : undefined; } function unwrapDialogState(value) { if (value && typeof value === 'object' && 'currentState' in value) { return value.currentState; } return value; } export function normalizeNatsEvent(raw) { var _a, _b, _c; const eventType = ((_a = readString(raw, 'eventType', 'event_type')) !== null && _a !== void 0 ? _a : 'unknown'); const correlationID = (_b = readString(raw, 'correlationID', 'correlation_id')) !== null && _b !== void 0 ? _b : readString(raw, 'correlationId', 'correlation_id'); if (eventType === 'progress') { return { eventType: 'progress', jobId: readString(raw, 'jobId', 'job_id'), currentStep: readNumber(raw, 'currentStep', 'current_step'), finalStep: readNumber(raw, 'finalStep', 'final_step'), message: typeof raw.message === 'string' ? raw.message : undefined, startingTime: readString(raw, 'startingTime', 'starting_time'), correlationID, }; } if (eventType === 'dialog_text_entered_response') { return { eventType: 'dialog_text_entered_response', requestID: typeof raw.requestID === 'string' ? raw.requestID : undefined, resultCode: typeof raw.resultCode === 'number' ? raw.resultCode : undefined, resultMessage: typeof raw.resultMessage === 'string' ? raw.resultMessage : undefined, currentState: unwrapDialogState(raw.currentState), correlationID, }; } if (eventType === 'error') { return { eventType: 'error', errorCode: ((_c = raw.errorCode) !== null && _c !== void 0 ? _c : raw.error_code), errorMessage: readString(raw, 'errorMessage', 'error_message'), backtrace: typeof raw.backtrace === 'string' ? raw.backtrace : undefined, correlationID, }; } return { eventType, correlationID, }; } function decodeMessage(subject, received, raw, onMessage) { const payload = normalizeNatsEvent(raw); console.debug(`[NATS] message #${received} on ${subject} (eventType=${payload.eventType})`, payload); onMessage(payload); } async function consumeCoreSubscription(nc, subject, onMessage) { const sub = nc.subscribe(subject); console.info('[NATS] subscribed to subject', subject); let received = 0; for await (const msg of sub) { received += 1; try { decodeMessage(subject, received, msg.json(), onMessage); } catch (err) { console.error('[NATS] message decode error', err); } } console.info(`[NATS] subscription on ${subject} closed (received ${received} messages)`); } async function consumeJetStream(nc, config, subject, mode, onMessage, setConsumerMessages) { const js = jetstream(nc); const consumer = config.consumer ? await js.consumers.get(config.stream, config.consumer) : await js.consumers.get(config.stream, { filter_subjects: [subject], deliver_policy: mode === 'catch-up' ? DeliverPolicy.LastPerSubject : DeliverPolicy.New, }); console.info('[NATS] JetStream consumer ready', config.consumer ? { stream: config.stream, consumer: config.consumer, subject, mode } : { stream: config.stream, subject, mode }); const messages = await consumer.consume(); setConsumerMessages(messages); let received = 0; for await (const msg of messages) { received += 1; try { decodeMessage(subject, received, msg.json(), onMessage); } catch (err) { console.error('[NATS] message decode error', err); } } console.info(`[NATS] JetStream consumer on ${subject} closed (received ${received} messages)`); } function closeNatsSession(consumerMessagesRef, connRef) { var _a, _b; (_a = consumerMessagesRef.current) === null || _a === void 0 ? void 0 : _a.stop(); consumerMessagesRef.current = null; (_b = connRef.current) === null || _b === void 0 ? void 0 : _b.close(); connRef.current = null; } export function useNatsSession(sessionId, config, onMessage) { const connRef = useRef(null); const consumerMessagesRef = useRef(null); const onMessageRef = useRef(onMessage); const wasHiddenRef = useRef(false); const [resumeGeneration, setResumeGeneration] = useState(0); useEffect(() => { onMessageRef.current = onMessage; }, [onMessage]); useEffect(() => { if (!sessionId || !(config === null || config === void 0 ? void 0 : config.stream)) { return; } const requestResume = () => { if (document.visibilityState !== 'visible') { return; } console.info('[NATS] resuming JetStream after visibility/network change'); setResumeGeneration(generation => generation + 1); }; const onVisibilityChange = () => { if (document.visibilityState === 'hidden') { wasHiddenRef.current = true; return; } if (wasHiddenRef.current) { wasHiddenRef.current = false; requestResume(); } }; const onPageShow = (event) => { if (event.persisted) { requestResume(); } }; document.addEventListener('visibilitychange', onVisibilityChange); window.addEventListener('online', requestResume); window.addEventListener('pageshow', onPageShow); return () => { document.removeEventListener('visibilitychange', onVisibilityChange); window.removeEventListener('online', requestResume); window.removeEventListener('pageshow', onPageShow); }; }, [sessionId, config === null || config === void 0 ? void 0 : config.stream]); useEffect(() => { var _a; if (!sessionId || !(config === null || config === void 0 ? void 0 : config.url) || !config.token) { console.debug('[NATS] subscription skipped (missing sessionId/url/token)', { sessionId, hasUrl: !!(config === null || config === void 0 ? void 0 : config.url), hasToken: !!(config === null || config === void 0 ? void 0 : config.token), }); return; } let closed = false; const subject = (_a = config.subject) !== null && _a !== void 0 ? _a : `sessions.${sessionId}`; const useJetStream = !!config.stream; const jetStreamMode = useJetStream && resumeGeneration > 0 ? 'catch-up' : 'live'; (async () => { console.info('[NATS] connecting to', config.url, 'for session', sessionId, useJetStream ? `(JetStream/${jetStreamMode})` : '(core)'); const nc = await wsconnect({ servers: [config.url], token: config.token, }); if (closed) { console.debug('[NATS] connection established after cleanup, closing immediately'); await nc.close(); return; } connRef.current = nc; console.info('[NATS] connected to', nc.getServer()); const dispatch = (event) => { onMessageRef.current(event); }; if (useJetStream) { await consumeJetStream(nc, config, subject, jetStreamMode, dispatch, messages => { consumerMessagesRef.current = messages; }); } else { await consumeCoreSubscription(nc, subject, dispatch); } })().catch(err => console.error('[NATS] connection error', err)); return () => { console.info('[NATS] cleanup: closing connection for subject', subject); closed = true; closeNatsSession(consumerMessagesRef, connRef); }; }, [sessionId, config, resumeGeneration]); useEffect(() => { setResumeGeneration(0); }, [sessionId]); } //# sourceMappingURL=useNatsSession.js.map