@cossistant/react 0.0.18 → 0.0.19
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/hooks/private/use-rest-client.js +2 -2
- package/hooks/private/use-rest-client.js.map +1 -1
- package/package.json +3 -3
- package/primitives/avatar/image.d.ts +1 -1
- package/primitives/multimodal-input.d.ts +2 -2
- package/primitives/multimodal-input.d.ts.map +1 -1
- package/realtime/provider.js +1 -1
- package/realtime/provider.js.map +1 -1
package/README.md
CHANGED
|
@@ -48,7 +48,7 @@ import "@cossistant/react/support.css";
|
|
|
48
48
|
|
|
49
49
|
export function App() {
|
|
50
50
|
return (
|
|
51
|
-
<SupportProvider publicKey={process.env.
|
|
51
|
+
<SupportProvider publicKey={process.env.NEXT_PUBLIC_COSSISTANT_API_KEY}>
|
|
52
52
|
<Support />
|
|
53
53
|
</SupportProvider>
|
|
54
54
|
);
|
|
@@ -14,9 +14,9 @@ import { CossistantClient } from "@cossistant/core";
|
|
|
14
14
|
function useClient(publicKey, apiUrl = "https://api.cossistant.com/v1", wsUrl = "wss://api.cossistant.com/ws") {
|
|
15
15
|
return {
|
|
16
16
|
client: useMemo(() => {
|
|
17
|
-
const keyFromEnv = process.env.
|
|
17
|
+
const keyFromEnv = process.env.NEXT_PUBLIC_COSSISTANT_API_KEY || process.env.COSSISTANT_PUBLIC_KEY;
|
|
18
18
|
const keyToUse = publicKey ?? keyFromEnv;
|
|
19
|
-
if (!keyToUse) throw new Error("Public key is required. Please provide it as a prop or set
|
|
19
|
+
if (!keyToUse) throw new Error("Public key is required. Please provide it as a prop or set NEXT_PUBLIC_COSSISTANT_API_KEY environment variable.");
|
|
20
20
|
const config = {
|
|
21
21
|
apiUrl,
|
|
22
22
|
wsUrl,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-rest-client.js","names":["config: CossistantConfig","err: unknown"],"sources":["../../../src/hooks/private/use-rest-client.ts"],"sourcesContent":["\"use client\";\n\nimport { CossistantClient } from \"@cossistant/core\";\nimport type { CossistantConfig } from \"@cossistant/types\";\nimport { useMemo } from \"react\";\n\nexport type UseClientResult = {\n\tclient: CossistantClient;\n\terror: Error | null;\n};\n\n/**\n * Creates a memoised `CossistantClient` instance using the provided endpoints\n * and public key. When no key is passed the hook falls back to environment\n * variables and surfaces missing configuration errors through the returned\n * `error` field.\n */\nexport function useClient(\n\tpublicKey: string | undefined,\n\tapiUrl = \"https://api.cossistant.com/v1\",\n\twsUrl = \"wss://api.cossistant.com/ws\"\n): UseClientResult {\n\tconst client = useMemo(() => {\n\t\tconst keyFromEnv =\n\t\t\tprocess.env.
|
|
1
|
+
{"version":3,"file":"use-rest-client.js","names":["config: CossistantConfig","err: unknown"],"sources":["../../../src/hooks/private/use-rest-client.ts"],"sourcesContent":["\"use client\";\n\nimport { CossistantClient } from \"@cossistant/core\";\nimport type { CossistantConfig } from \"@cossistant/types\";\nimport { useMemo } from \"react\";\n\nexport type UseClientResult = {\n\tclient: CossistantClient;\n\terror: Error | null;\n};\n\n/**\n * Creates a memoised `CossistantClient` instance using the provided endpoints\n * and public key. When no key is passed the hook falls back to environment\n * variables and surfaces missing configuration errors through the returned\n * `error` field.\n */\nexport function useClient(\n\tpublicKey: string | undefined,\n\tapiUrl = \"https://api.cossistant.com/v1\",\n\twsUrl = \"wss://api.cossistant.com/ws\"\n): UseClientResult {\n\tconst client = useMemo(() => {\n\t\tconst keyFromEnv =\n\t\t\tprocess.env.NEXT_PUBLIC_COSSISTANT_API_KEY ||\n\t\t\tprocess.env.COSSISTANT_PUBLIC_KEY;\n\t\tconst keyToUse = publicKey ?? keyFromEnv;\n\n\t\tif (!keyToUse) {\n\t\t\tthrow new Error(\n\t\t\t\t\"Public key is required. Please provide it as a prop or set NEXT_PUBLIC_COSSISTANT_API_KEY environment variable.\"\n\t\t\t);\n\t\t}\n\n\t\tconst config: CossistantConfig = {\n\t\t\tapiUrl,\n\t\t\twsUrl,\n\t\t\tpublicKey: keyToUse,\n\t\t};\n\n\t\ttry {\n\t\t\treturn new CossistantClient(config);\n\t\t} catch (err: unknown) {\n\t\t\tthrow err instanceof Error\n\t\t\t\t? err\n\t\t\t\t: new Error(\"Failed to initialize Cossistant client\");\n\t\t}\n\t}, [publicKey, apiUrl, wsUrl]);\n\n\treturn { client, error: null };\n}\n"],"mappings":";;;;;;;;;;;;;AAiBA,SAAgB,UACf,WACA,SAAS,iCACT,QAAQ,+BACU;AA4BlB,QAAO;EAAE,QA3BM,cAAc;GAC5B,MAAM,aACL,QAAQ,IAAI,kCACZ,QAAQ,IAAI;GACb,MAAM,WAAW,aAAa;AAE9B,OAAI,CAAC,SACJ,OAAM,IAAI,MACT,kHACA;GAGF,MAAMA,SAA2B;IAChC;IACA;IACA,WAAW;IACX;AAED,OAAI;AACH,WAAO,IAAI,iBAAiB,OAAO;YAC3BC,KAAc;AACtB,UAAM,eAAe,QAClB,sBACA,IAAI,MAAM,yCAAyC;;KAErD;GAAC;GAAW;GAAQ;GAAM,CAAC;EAEb,OAAO;EAAM"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cossistant/react",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.19",
|
|
5
5
|
"private": false,
|
|
6
6
|
"author": "Cossistant team",
|
|
7
7
|
"description": "Headless React SDK for building AI-powered support/chat widgets. Hooks + primitives, WS-driven, TypeScript-first. Next.js-ready, Tailwind optional.",
|
|
@@ -88,8 +88,8 @@
|
|
|
88
88
|
"*.css"
|
|
89
89
|
],
|
|
90
90
|
"dependencies": {
|
|
91
|
-
"@cossistant/core": "0.0.
|
|
92
|
-
"@cossistant/types": "0.0.
|
|
91
|
+
"@cossistant/core": "0.0.19",
|
|
92
|
+
"@cossistant/types": "0.0.19",
|
|
93
93
|
"class-variance-authority": "^0.7.1",
|
|
94
94
|
"clsx": "^2.1.1",
|
|
95
95
|
"nanoid": "^5.1.5",
|
|
@@ -15,7 +15,7 @@ type AvatarImageProps = Omit<React$1.ImgHTMLAttributes<HTMLImageElement>, "src"
|
|
|
15
15
|
* Controlled `<img>` that syncs its loading status back to the avatar context
|
|
16
16
|
* so fallbacks know when to display.
|
|
17
17
|
*/
|
|
18
|
-
declare const AvatarImage: React$1.ForwardRefExoticComponent<Omit<React$1.ImgHTMLAttributes<HTMLImageElement>, "
|
|
18
|
+
declare const AvatarImage: React$1.ForwardRefExoticComponent<Omit<React$1.ImgHTMLAttributes<HTMLImageElement>, "alt" | "src"> & {
|
|
19
19
|
src: string;
|
|
20
20
|
alt?: string;
|
|
21
21
|
asChild?: boolean;
|
|
@@ -16,7 +16,7 @@ type MultimodalInputProps = Omit<React$1.TextareaHTMLAttributes<HTMLTextAreaElem
|
|
|
16
16
|
* clipboard uploads and auto-resizing while remaining composable via
|
|
17
17
|
* `asChild`.
|
|
18
18
|
*/
|
|
19
|
-
declare const MultimodalInput: React$1.ForwardRefExoticComponent<Omit<React$1.TextareaHTMLAttributes<HTMLTextAreaElement>, "
|
|
19
|
+
declare const MultimodalInput: React$1.ForwardRefExoticComponent<Omit<React$1.TextareaHTMLAttributes<HTMLTextAreaElement>, "onChange" | "value"> & {
|
|
20
20
|
value: string;
|
|
21
21
|
onChange: (value: string) => void;
|
|
22
22
|
onSubmit?: () => void;
|
|
@@ -38,7 +38,7 @@ declare const FileInput: React$1.ForwardRefExoticComponent<React$1.InputHTMLAttr
|
|
|
38
38
|
onFileSelect?: (files: File[]) => void;
|
|
39
39
|
asChild?: boolean;
|
|
40
40
|
} & React$1.RefAttributes<HTMLInputElement>>;
|
|
41
|
-
declare const SupportInput: React$1.ForwardRefExoticComponent<Omit<React$1.TextareaHTMLAttributes<HTMLTextAreaElement>, "
|
|
41
|
+
declare const SupportInput: React$1.ForwardRefExoticComponent<Omit<React$1.TextareaHTMLAttributes<HTMLTextAreaElement>, "onChange" | "value"> & {
|
|
42
42
|
value: string;
|
|
43
43
|
onChange: (value: string) => void;
|
|
44
44
|
onSubmit?: () => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"multimodal-input.d.ts","names":[],"sources":["../../src/primitives/multimodal-input.tsx"],"sourcesContent":[],"mappings":";;;KAGY,oBAAA,GAAuB,KAClC,OAAA,CAAM,uBAAuB;;EADlB,QAAA,EAAA,CAAA,KAAA,EAAA,MAAoB,EAAA,GAAA,IAAA;EACF,QAAA,CAAA,EAAA,GAAA,GAAA,IAAA;EAA7B,YAAM,CAAA,EAAA,CAAA,KAAA,EAMiB,IANjB,EAAA,EAAA,GAAA,IAAA;EAD4B,OAAA,CAAA,EAAA,OAAA;EAOX,SAAA,CAAA,EAAA,MAAA;EAGf,KAAA,CAAA,EAAA,KAAA,GAAA,IAAA;EAAK,QAAA,CAAA,EAAA,OAAA;AASd,CAAA;;;;;;cAAa,iBAAe,OAAA,CAAA,0BAAA,KAAA,OAAA,CAAA,uBAAA;;EAAA,QAAA,EAAA,CAAA,KAAA,EAAA,MAAA,EAAA,GAAA,IAAA;EAAA,QAAA,CAAA,EAAA,GAAA,GAAA,IAAA;EA4GhB,YAAA,CAAA,EAAA,CAAA,KAAc,EAxHF,IAwHE,EAAA,EAAA,GAAA,IAAA;EAA6B,OAAA,CAAA,EAAA,OAAA;EAA1B,SAAM,CAAA,EAAA,MAAA;EACX,KAAA,CAAA,EAtHf,KAsHe,GAAA,IAAA;EAAI,QAAA,CAAA,EAAA,OAAA;AAQ5B,CAAA,wBAkCI,oBAAA,CAAA,CAAA;AAlCkB,KATV,cAAA,GAAiB,OAAA,CAAM,mBASb,CATiC,gBASjC,CAAA,GAAA;EAAA,YAAA,CAAA,EAAA,CAAA,KAAA,EARE,IAQF,EAAA,EAAA,GAAA,IAAA;EARE,OAAA,CAAA,EAAA,OAAA;;;;;AA6CxB;AAAyB,cArCZ,SAqCY,EArCH,OAAA,CAAA,yBAqCG,CArCH,OAAA,CAAA,mBAqCG,CArCH,gBAqCG,CAAA,GAAA;EAAA,YAAA,CAAA,EAAA,CAAA,KAAA,EA7CD,IA6CC,EAAA,EAAA,GAAA,IAAA;EAAA,OAAA,CAAA,EAAA,OAAA;CAtKD,wBAAA,iBAAA,CAAA,CAAA;AAGf,cAmKI,YAnKJ,EAmKgB,OAAA,CAAA,yBAnKhB,CAmKgB,IAnKhB,CAmKgB,OAAA,CAAA,sBAnKhB,CAmKgB,mBAnKhB,CAAA,EAAA,
|
|
1
|
+
{"version":3,"file":"multimodal-input.d.ts","names":[],"sources":["../../src/primitives/multimodal-input.tsx"],"sourcesContent":[],"mappings":";;;KAGY,oBAAA,GAAuB,KAClC,OAAA,CAAM,uBAAuB;;EADlB,QAAA,EAAA,CAAA,KAAA,EAAA,MAAoB,EAAA,GAAA,IAAA;EACF,QAAA,CAAA,EAAA,GAAA,GAAA,IAAA;EAA7B,YAAM,CAAA,EAAA,CAAA,KAAA,EAMiB,IANjB,EAAA,EAAA,GAAA,IAAA;EAD4B,OAAA,CAAA,EAAA,OAAA;EAOX,SAAA,CAAA,EAAA,MAAA;EAGf,KAAA,CAAA,EAAA,KAAA,GAAA,IAAA;EAAK,QAAA,CAAA,EAAA,OAAA;AASd,CAAA;;;;;;cAAa,iBAAe,OAAA,CAAA,0BAAA,KAAA,OAAA,CAAA,uBAAA;;EAAA,QAAA,EAAA,CAAA,KAAA,EAAA,MAAA,EAAA,GAAA,IAAA;EAAA,QAAA,CAAA,EAAA,GAAA,GAAA,IAAA;EA4GhB,YAAA,CAAA,EAAA,CAAA,KAAc,EAxHF,IAwHE,EAAA,EAAA,GAAA,IAAA;EAA6B,OAAA,CAAA,EAAA,OAAA;EAA1B,SAAM,CAAA,EAAA,MAAA;EACX,KAAA,CAAA,EAtHf,KAsHe,GAAA,IAAA;EAAI,QAAA,CAAA,EAAA,OAAA;AAQ5B,CAAA,wBAkCI,oBAAA,CAAA,CAAA;AAlCkB,KATV,cAAA,GAAiB,OAAA,CAAM,mBASb,CATiC,gBASjC,CAAA,GAAA;EAAA,YAAA,CAAA,EAAA,CAAA,KAAA,EARE,IAQF,EAAA,EAAA,GAAA,IAAA;EARE,OAAA,CAAA,EAAA,OAAA;;;;;AA6CxB;AAAyB,cArCZ,SAqCY,EArCH,OAAA,CAAA,yBAqCG,CArCH,OAAA,CAAA,mBAqCG,CArCH,gBAqCG,CAAA,GAAA;EAAA,YAAA,CAAA,EAAA,CAAA,KAAA,EA7CD,IA6CC,EAAA,EAAA,GAAA,IAAA;EAAA,OAAA,CAAA,EAAA,OAAA;CAtKD,wBAAA,iBAAA,CAAA,CAAA;AAGf,cAmKI,YAnKJ,EAmKgB,OAAA,CAAA,yBAnKhB,CAmKgB,IAnKhB,CAmKgB,OAAA,CAAA,sBAnKhB,CAmKgB,mBAnKhB,CAAA,EAAA,UAAA,GAAA,OAAA,CAAA,GAAA;;;EAmKgB,QAAA,CAAA,EAAA,GAAA,GAAA,IAAA;EAAA,YAAA,CAAA,EAAA,CAAA,KAAA,EAtKD,IAsKC,EAAA,EAAA,GAAA,IAAA;;;UAnKhB"}
|
package/realtime/provider.js
CHANGED
|
@@ -134,7 +134,7 @@ function isHeartbeatTimedOut(lastHeartbeat, timeoutMs) {
|
|
|
134
134
|
function resolvePublicKey(explicit) {
|
|
135
135
|
const trimmed = explicit?.trim();
|
|
136
136
|
if (trimmed) return trimmed;
|
|
137
|
-
const normalized = (process.env.
|
|
137
|
+
const normalized = (process.env.NEXT_PUBLIC_COSSISTANT_API_KEY || process.env.COSSISTANT_PUBLIC_KEY || null)?.trim();
|
|
138
138
|
return normalized && normalized.length > 0 ? normalized : null;
|
|
139
139
|
}
|
|
140
140
|
function normalizeAuth(auth) {
|
package/realtime/provider.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.js","names":["payload: unknown"],"sources":["../../src/realtime/provider.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n\ttype AnyRealtimeEvent,\n\tisValidEventType,\n\ttype RealtimeEvent,\n\tvalidateRealtimeEvent,\n} from \"@cossistant/types/realtime-events\";\nimport type React from \"react\";\nimport {\n\tcreateContext,\n\tuseCallback,\n\tuseContext,\n\tuseEffect,\n\tuseMemo,\n\tuseRef,\n\tuseState,\n} from \"react\";\nimport useWebSocket, { ReadyState } from \"react-use-websocket\";\n\nconst DEFAULT_HEARTBEAT_INTERVAL_MS = 15_000;\nconst DEFAULT_HEARTBEAT_TIMEOUT_MS = 45_000;\n\ntype SubscribeHandler = (event: AnyRealtimeEvent) => void;\n\ntype MessageDecodeResult =\n\t| {\n\t\t\ttype: \"raw-text\";\n\t\t\tdata: string;\n\t }\n\t| {\n\t\t\ttype: \"unsupported\";\n\t };\n\ntype ParsedMessage =\n\t| {\n\t\t\ttype: \"pong\";\n\t }\n\t| {\n\t\t\ttype: \"connection-established\";\n\t\t\tconnectionId: string | null;\n\t }\n\t| {\n\t\t\ttype: \"error\";\n\t\t\tmessage: string;\n\t }\n\t| {\n\t\t\ttype: \"event\";\n\t\t\tevent: AnyRealtimeEvent;\n\t }\n\t| {\n\t\t\ttype: \"invalid\";\n\t };\n\ntype VisitorAuthConfig = {\n\tkind: \"visitor\";\n\tvisitorId: string | null;\n\twebsiteId?: string | null;\n\tpublicKey?: string | null;\n};\n\ntype SessionAuthConfig = {\n\tkind: \"session\";\n\tsessionToken: string | null;\n\twebsiteId?: string | null;\n\tuserId?: string | null;\n};\n\ntype RealtimeAuthConfig = VisitorAuthConfig | SessionAuthConfig;\n\ntype ResolvedAuthConfig = {\n\ttype: \"visitor\" | \"session\";\n\tvisitorId: string | null;\n\twebsiteId: string | null;\n\tuserId: string | null;\n\tsessionToken: string | null;\n\tpublicKey: string | null;\n};\n\ntype RealtimeProviderProps = {\n\tchildren: React.ReactNode;\n\twsUrl?: string;\n\tauth: RealtimeAuthConfig | null;\n\tautoConnect?: boolean;\n\tonConnect?: () => void;\n\tonDisconnect?: () => void;\n\tonError?: (error: Error) => void;\n};\n\ntype RealtimeConnectionState = {\n\tisConnected: boolean;\n\tisConnecting: boolean;\n\terror: Error | null;\n\tsend: (event: AnyRealtimeEvent) => void;\n\tsendRaw: (data: string) => void;\n\tsubscribe: (handler: SubscribeHandler) => () => void;\n\tlastEvent: AnyRealtimeEvent | null;\n\tconnectionId: string | null;\n\treconnect: () => void;\n};\n\ntype RealtimeContextValue = RealtimeConnectionState & {\n\tvisitorId: string | null;\n\twebsiteId: string | null;\n\tuserId: string | null;\n};\n\nconst DEFAULT_WS_URL = \"wss://api.cossistant.com/ws\";\n\nconst RealtimeContext = createContext<RealtimeContextValue | null>(null);\n\n/**\n * Decodes WebSocket message data into a string.\n * Handles string, ArrayBuffer, and ArrayBufferView formats.\n */\nfunction decodeMessageData(data: unknown): MessageDecodeResult {\n\tif (typeof data === \"string\") {\n\t\treturn { type: \"raw-text\", data };\n\t}\n\n\tif (data instanceof ArrayBuffer) {\n\t\ttry {\n\t\t\treturn { type: \"raw-text\", data: new TextDecoder().decode(data) };\n\t\t} catch {\n\t\t\treturn { type: \"unsupported\" };\n\t\t}\n\t}\n\n\tif (ArrayBuffer.isView(data)) {\n\t\ttry {\n\t\t\treturn { type: \"raw-text\", data: new TextDecoder().decode(data.buffer) };\n\t\t} catch {\n\t\t\treturn { type: \"unsupported\" };\n\t\t}\n\t}\n\n\treturn { type: \"unsupported\" };\n}\n\n/**\n * Safely parses JSON string, returning null if invalid.\n */\nfunction parseJson(raw: string): unknown {\n\ttry {\n\t\treturn JSON.parse(raw);\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Extracts a string field from an unknown object, with optional validation.\n */\nfunction extractStringField(\n\tobj: unknown,\n\tfield: string,\n\trequired = false\n): string | null {\n\tif (!obj || typeof obj !== \"object\" || !(field in obj)) {\n\t\treturn required ? null : null;\n\t}\n\tconst value = (obj as Record<string, unknown>)[field];\n\tif (typeof value === \"string\" && value.length > 0) {\n\t\treturn value;\n\t}\n\treturn required ? null : null;\n}\n\n/**\n * Parses a WebSocket message and determines its type and content.\n */\nfunction parseWebSocketMessage(rawText: string): ParsedMessage {\n\t// Handle pong heartbeat\n\tif (rawText === \"pong\") {\n\t\treturn { type: \"pong\" };\n\t}\n\n\t// Try to parse as JSON\n\tconst parsed = parseJson(rawText);\n\tif (!parsed || typeof parsed !== \"object\") {\n\t\treturn { type: \"invalid\" };\n\t}\n\n\tconst messageType = extractStringField(parsed, \"type\");\n\n\t// Handle CONNECTION_ESTABLISHED\n\tif (messageType === \"CONNECTION_ESTABLISHED\") {\n\t\tconst payload = (parsed as { payload?: unknown }).payload;\n\t\tconst connectionId = extractStringField(payload, \"connectionId\");\n\t\treturn { type: \"connection-established\", connectionId };\n\t}\n\n\t// Handle error messages\n\tif (\"error\" in parsed && \"message\" in parsed) {\n\t\tconst message =\n\t\t\textractStringField(parsed, \"message\") || \"Realtime connection error\";\n\t\treturn { type: \"error\", message };\n\t}\n\n\t// Handle realtime events\n\tif (messageType && isValidEventType(messageType)) {\n\t\ttry {\n\t\t\tconst event = constructRealtimeEvent(parsed);\n\t\t\tif (!event) {\n\t\t\t\treturn { type: \"invalid\" };\n\t\t\t}\n\t\t\treturn { type: \"event\", event };\n\t\t} catch (error) {\n\t\t\tconsole.error(\"[Realtime] Failed to construct event\", error);\n\t\t\treturn { type: \"invalid\" };\n\t\t}\n\t}\n\n\treturn { type: \"invalid\" };\n}\n\n/**\n * Constructs a RealtimeEvent from parsed JSON data.\n * Returns null if required fields are missing or validation fails.\n */\nfunction constructRealtimeEvent(parsed: unknown): AnyRealtimeEvent | null {\n\tif (!parsed || typeof parsed !== \"object\" || !(\"type\" in parsed)) {\n\t\treturn null;\n\t}\n\n\tconst type = (parsed as { type: unknown }).type;\n\tif (!isValidEventType(type)) {\n\t\treturn null;\n\t}\n\n\tconst eventType = type;\n\n\t// Extract payload directly\n\tconst payloadSource = (parsed as { payload?: unknown }).payload;\n\n\tlet payload: unknown;\n\ttry {\n\t\tpayload = validateRealtimeEvent(eventType, payloadSource);\n\t} catch (error) {\n\t\tconsole.error(\"[Realtime] Received invalid event payload\", error);\n\t\treturn null;\n\t}\n\n\tconst organizationId = extractStringField(\n\t\tpayloadSource,\n\t\t\"organizationId\",\n\t\ttrue\n\t);\n\tconst websiteId = extractStringField(payloadSource, \"websiteId\", true);\n\n\tif (!organizationId) {\n\t\tconsole.error(\"[Realtime] Received event without organizationId\", parsed);\n\t\treturn null;\n\t}\n\n\tif (!websiteId) {\n\t\tconsole.error(\"[Realtime] Received event without websiteId\", parsed);\n\t\treturn null;\n\t}\n\n\tconst visitorId = extractStringField(parsed, \"visitorId\");\n\n\treturn {\n\t\ttype: eventType,\n\t\tpayload,\n\t\torganizationId,\n\t\twebsiteId,\n\t\tvisitorId,\n\t} as AnyRealtimeEvent;\n}\n\n/**\n * Checks if heartbeat has timed out.\n */\nfunction isHeartbeatTimedOut(\n\tlastHeartbeat: number,\n\ttimeoutMs: number\n): boolean {\n\tconst elapsed = Date.now() - lastHeartbeat;\n\treturn elapsed > timeoutMs;\n}\n\nfunction resolvePublicKey(explicit?: string | null): string | null {\n\tconst trimmed = explicit?.trim();\n\tif (trimmed) {\n\t\treturn trimmed;\n\t}\n\n\tconst fromEnv =\n\t\tprocess.env.NEXT_PUBLIC_COSSISTANT_KEY ||\n\t\tprocess.env.COSSISTANT_PUBLIC_KEY ||\n\t\tnull;\n\n\tconst normalized = fromEnv?.trim();\n\treturn normalized && normalized.length > 0 ? normalized : null;\n}\n\nfunction normalizeAuth(\n\tauth: RealtimeAuthConfig | null\n): ResolvedAuthConfig | null {\n\tif (!auth) {\n\t\treturn null;\n\t}\n\n\tif (auth.kind === \"visitor\") {\n\t\tconst visitorId = auth.visitorId?.trim() || null;\n\n\t\tif (!visitorId) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn {\n\t\t\ttype: \"visitor\",\n\t\t\tvisitorId,\n\t\t\twebsiteId: auth.websiteId?.trim() || null,\n\t\t\tuserId: null,\n\t\t\tsessionToken: null,\n\t\t\tpublicKey: resolvePublicKey(auth.publicKey ?? null),\n\t\t} satisfies ResolvedAuthConfig;\n\t}\n\n\tconst sessionToken = auth.sessionToken?.trim() || null;\n\n\tif (!sessionToken) {\n\t\treturn null;\n\t}\n\n\treturn {\n\t\ttype: \"session\",\n\t\tvisitorId: null,\n\t\twebsiteId: auth.websiteId?.trim() || null,\n\t\tuserId: auth.userId?.trim() || null,\n\t\tsessionToken,\n\t\tpublicKey: null,\n\t} satisfies ResolvedAuthConfig;\n}\n\nfunction buildSocketUrl(\n\tbaseUrl: string,\n\tauth: ResolvedAuthConfig | null\n): string | null {\n\tif (!auth) {\n\t\treturn null;\n\t}\n\n\ttry {\n\t\tconst url = new URL(baseUrl);\n\n\t\tif (auth.type === \"visitor\") {\n\t\t\turl.searchParams.set(\"visitorId\", auth.visitorId ?? \"\");\n\t\t\tconst publicKey = auth.publicKey;\n\t\t\tif (publicKey) {\n\t\t\t\turl.searchParams.set(\"publicKey\", publicKey);\n\t\t\t}\n\t\t} else {\n\t\t\turl.searchParams.set(\"sessionToken\", auth.sessionToken ?? \"\");\n\t\t\tif (auth.websiteId) {\n\t\t\t\turl.searchParams.set(\"websiteId\", auth.websiteId);\n\t\t\t}\n\t\t}\n\n\t\treturn url.toString();\n\t} catch (error) {\n\t\tconsole.error(\"[Realtime] Failed to build WebSocket URL\", error);\n\t\treturn null;\n\t}\n}\n\n/**\n * Provides websocket connectivity and heartbeating logic for realtime events.\n */\nexport function RealtimeProvider({\n\tchildren,\n\twsUrl = DEFAULT_WS_URL,\n\tauth,\n\tautoConnect = true,\n\tonConnect,\n\tonDisconnect,\n\tonError,\n}: RealtimeProviderProps): React.ReactElement {\n\tconst normalizedAuth = normalizeAuth(auth);\n\n\tconst socketUrl = buildSocketUrl(wsUrl, normalizedAuth);\n\tconst eventHandlersRef = useRef<Set<SubscribeHandler>>(new Set());\n\tconst lastHeartbeatRef = useRef<number>(Date.now());\n\tconst hasOpenedRef = useRef(false);\n\tconst previousUrlRef = useRef<string | null>(null);\n\tconst [connectionError, setConnectionError] = useState<Error | null>(null);\n\tconst [lastEvent, setLastEvent] = useState<AnyRealtimeEvent | null>(null);\n\tconst [connectionId, setConnectionId] = useState<string | null>(null);\n\n\tconst heartbeatIntervalMs = DEFAULT_HEARTBEAT_INTERVAL_MS;\n\tconst heartbeatTimeoutMs = DEFAULT_HEARTBEAT_TIMEOUT_MS;\n\n\tconst canConnect = Boolean(autoConnect && socketUrl);\n\tconst connectionUrl = canConnect ? socketUrl : null;\n\n\t// Track URL changes to detect when connection is being replaced\n\tuseEffect(() => {\n\t\tif (connectionUrl !== previousUrlRef.current) {\n\t\t\tpreviousUrlRef.current = connectionUrl;\n\t\t\t// Reset hasOpenedRef when URL changes so we know a new connection is starting\n\t\t\thasOpenedRef.current = false;\n\t\t}\n\t}, [connectionUrl]);\n\n\tconst {\n\t\tsendMessage,\n\t\tsendJsonMessage,\n\t\tlastMessage,\n\t\treadyState,\n\t\tgetWebSocket,\n\t} = useWebSocket(\n\t\tconnectionUrl,\n\t\t{\n\t\t\tshouldReconnect: (closeEvent) => {\n\t\t\t\tif (!canConnect) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tif (closeEvent.code === 1008 || closeEvent.code === 1011) {\n\t\t\t\t\tconst err = new Error(\n\t\t\t\t\t\tcloseEvent.reason ||\n\t\t\t\t\t\t\t\"Realtime connection closed by server. Please check your credentials.\"\n\t\t\t\t\t);\n\t\t\t\t\tsetConnectionError(err);\n\t\t\t\t\tonError?.(err);\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\treturn true;\n\t\t\t},\n\t\t\treconnectAttempts: autoConnect ? undefined : 0,\n\t\t\treconnectInterval: (attempt) => {\n\t\t\t\tconst base = 500 * 2 ** attempt;\n\t\t\t\treturn Math.min(base, 30_000);\n\t\t\t},\n\t\t\tretryOnError: false,\n\t\t\tonOpen: () => {\n\t\t\t\thasOpenedRef.current = true;\n\t\t\t\tsetConnectionError(null);\n\t\t\t\tlastHeartbeatRef.current = Date.now();\n\t\t\t\tonConnect?.();\n\t\t\t},\n\t\t\tonClose: () => {\n\t\t\t\tsetConnectionId(null);\n\t\t\t\tonDisconnect?.();\n\t\t\t},\n\t\t\tonError: (event) => {\n\t\t\t\tif (!canConnect) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst socketLike = event.target;\n\t\t\t\tconst currentSocket = getWebSocket();\n\t\t\t\tconst isBrowserSocket =\n\t\t\t\t\ttypeof WebSocket !== \"undefined\" && socketLike instanceof WebSocket;\n\t\t\t\tconst socketState = isBrowserSocket ? socketLike.readyState : undefined;\n\n\t\t\t\t// Only suppress errors for THIS provider's socket, not other nested providers\n\t\t\t\t// Check if the errored socket belongs to this provider instance\n\t\t\t\tconst isThisProvidersSocket = currentSocket === socketLike;\n\n\t\t\t\t// Suppress errors if:\n\t\t\t\t// 1. This socket was replaced (URL changed while connecting) - only for this provider\n\t\t\t\t// 2. Connection URL is null (component unmounting or disabled)\n\t\t\t\t// 3. Socket is in CLOSING/CLOSED state and hasn't opened (cleanup/unmount) - only for this provider\n\t\t\t\tif (\n\t\t\t\t\t(!isThisProvidersSocket && currentSocket) ||\n\t\t\t\t\t!connectionUrl ||\n\t\t\t\t\t(isThisProvidersSocket &&\n\t\t\t\t\t\t!hasOpenedRef.current &&\n\t\t\t\t\t\t(socketState === WebSocket.CLOSING ||\n\t\t\t\t\t\t\tsocketState === WebSocket.CLOSED))\n\t\t\t\t) {\n\t\t\t\t\t// Suppress these expected errors during connection replacement or cleanup\n\t\t\t\t\t// But only if it's THIS provider's socket being replaced\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// For errors that occur during CONNECTING state, check if URL changed\n\t\t\t\t// Only suppress if it's this provider's socket\n\t\t\t\tif (\n\t\t\t\t\tisThisProvidersSocket &&\n\t\t\t\t\t!hasOpenedRef.current &&\n\t\t\t\t\tsocketState === WebSocket.CONNECTING &&\n\t\t\t\t\tconnectionUrl !== previousUrlRef.current\n\t\t\t\t) {\n\t\t\t\t\t// URL changed while connecting, suppress error\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst err = new Error(`WebSocket error: ${event.type}`);\n\t\t\t\tsetConnectionError(err);\n\t\t\t\tonError?.(err);\n\t\t\t},\n\t\t},\n\t\tcanConnect\n\t);\n\n\tuseEffect(() => {\n\t\tif (!lastMessage) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Decode message data from various formats\n\t\tconst decoded = decodeMessageData(lastMessage.data);\n\t\tif (decoded.type === \"unsupported\") {\n\t\t\treturn;\n\t\t}\n\n\t\t// Parse the message and determine its type\n\t\tconst message = parseWebSocketMessage(decoded.data);\n\n\t\t// Handle different message types\n\t\tswitch (message.type) {\n\t\t\tcase \"pong\":\n\t\t\t\tlastHeartbeatRef.current = Date.now();\n\t\t\t\tbreak;\n\n\t\t\tcase \"connection-established\":\n\t\t\t\tsetConnectionId(message.connectionId);\n\t\t\t\tlastHeartbeatRef.current = Date.now();\n\t\t\t\tbreak;\n\n\t\t\tcase \"error\": {\n\t\t\t\tconst err = new Error(message.message);\n\t\t\t\tsetConnectionError(err);\n\t\t\t\tonError?.(err);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"event\":\n\t\t\t\tlastHeartbeatRef.current = Date.now();\n\t\t\t\tsetLastEvent(message.event);\n\t\t\t\tfor (const handler of eventHandlersRef.current) {\n\t\t\t\t\tPromise.resolve(handler(message.event)).catch((error) => {\n\t\t\t\t\t\tconst err =\n\t\t\t\t\t\t\terror instanceof Error\n\t\t\t\t\t\t\t\t? error\n\t\t\t\t\t\t\t\t: new Error(`Subscriber threw an exception: ${String(error)}`);\n\t\t\t\t\t\tonError?.(err);\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tdefault:\n\t\t\t\t// Silently ignore invalid or unknown messages\n\t\t\t\tbreak;\n\t\t}\n\t}, [lastMessage, onError]);\n\n\tuseEffect(() => {\n\t\tif (!canConnect) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst interval = window.setInterval(() => {\n\t\t\tif (readyState !== ReadyState.OPEN) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Check if heartbeat has timed out\n\t\t\tif (isHeartbeatTimedOut(lastHeartbeatRef.current, heartbeatTimeoutMs)) {\n\t\t\t\tconst socket = getWebSocket();\n\t\t\t\tsocket?.close(4000, \"Heartbeat timeout\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Send ping to keep connection alive\n\t\t\ttry {\n\t\t\t\tsendMessage(\"ping\");\n\t\t\t} catch {\n\t\t\t\t// Ignore send failures; reconnect logic will handle it\n\t\t\t}\n\t\t}, heartbeatIntervalMs);\n\n\t\treturn () => {\n\t\t\twindow.clearInterval(interval);\n\t\t};\n\t}, [\n\t\tcanConnect,\n\t\theartbeatIntervalMs,\n\t\theartbeatTimeoutMs,\n\t\treadyState,\n\t\tsendMessage,\n\t\tgetWebSocket,\n\t]);\n\n\tconst send = useCallback(\n\t\t(event: AnyRealtimeEvent) => {\n\t\t\tif (!connectionUrl) {\n\t\t\t\tthrow new Error(\"Realtime connection is disabled\");\n\t\t\t}\n\n\t\t\tif (readyState !== ReadyState.OPEN) {\n\t\t\t\tthrow new Error(\"Realtime connection is not established\");\n\t\t\t}\n\n\t\t\tsendJsonMessage(event);\n\t\t},\n\t\t[connectionUrl, readyState, sendJsonMessage]\n\t);\n\n\tconst sendRaw = useCallback(\n\t\t(data: string) => {\n\t\t\tif (!connectionUrl) {\n\t\t\t\tthrow new Error(\"Realtime connection is disabled\");\n\t\t\t}\n\n\t\t\tif (readyState !== ReadyState.OPEN) {\n\t\t\t\tthrow new Error(\"Realtime connection is not established\");\n\t\t\t}\n\n\t\t\tsendMessage(data);\n\t\t},\n\t\t[connectionUrl, readyState, sendMessage]\n\t);\n\n\tconst subscribe = useCallback((handler: SubscribeHandler) => {\n\t\teventHandlersRef.current.add(handler);\n\t\treturn () => {\n\t\t\teventHandlersRef.current.delete(handler);\n\t\t};\n\t}, []);\n\n\tconst reconnect = useCallback(() => {\n\t\tconst socket = getWebSocket();\n\t\tsocket?.close();\n\t}, [getWebSocket]);\n\n\tconst connection = useMemo<RealtimeConnectionState>(\n\t\t() => ({\n\t\t\tisConnected: readyState === ReadyState.OPEN,\n\t\t\tisConnecting: readyState === ReadyState.CONNECTING,\n\t\t\terror: connectionError,\n\t\t\tsend,\n\t\t\tsendRaw,\n\t\t\tsubscribe,\n\t\t\tlastEvent,\n\t\t\tconnectionId,\n\t\t\treconnect,\n\t\t}),\n\t\t[\n\t\t\treadyState,\n\t\t\tconnectionError,\n\t\t\tsend,\n\t\t\tsendRaw,\n\t\t\tsubscribe,\n\t\t\tlastEvent,\n\t\t\tconnectionId,\n\t\t\treconnect,\n\t\t]\n\t);\n\n\tconst value = useMemo<RealtimeContextValue>(\n\t\t() => ({\n\t\t\t...connection,\n\t\t\tvisitorId: normalizedAuth?.visitorId ?? null,\n\t\t\twebsiteId: normalizedAuth?.websiteId ?? null,\n\t\t\tuserId: normalizedAuth?.userId ?? null,\n\t\t}),\n\t\t[\n\t\t\tconnection,\n\t\t\tnormalizedAuth?.visitorId,\n\t\t\tnormalizedAuth?.websiteId,\n\t\t\tnormalizedAuth?.userId,\n\t\t]\n\t);\n\n\treturn (\n\t\t<RealtimeContext.Provider value={value}>\n\t\t\t{children}\n\t\t</RealtimeContext.Provider>\n\t);\n}\n\n/**\n * Returns the realtime connection context.\n */\nexport function useRealtimeConnection(): RealtimeContextValue {\n\tconst context = useContext(RealtimeContext);\n\tif (!context) {\n\t\tthrow new Error(\n\t\t\t\"useRealtimeConnection must be used within RealtimeProvider\"\n\t\t);\n\t}\n\n\treturn context;\n}\n\nexport type { RealtimeContextValue };\nexport type { RealtimeAuthConfig };\nexport type { RealtimeProviderProps };\nexport type { RealtimeEvent } from \"@cossistant/types/realtime-events\";\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"provider.js","names":["payload: unknown"],"sources":["../../src/realtime/provider.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n\ttype AnyRealtimeEvent,\n\tisValidEventType,\n\ttype RealtimeEvent,\n\tvalidateRealtimeEvent,\n} from \"@cossistant/types/realtime-events\";\nimport type React from \"react\";\nimport {\n\tcreateContext,\n\tuseCallback,\n\tuseContext,\n\tuseEffect,\n\tuseMemo,\n\tuseRef,\n\tuseState,\n} from \"react\";\nimport useWebSocket, { ReadyState } from \"react-use-websocket\";\n\nconst DEFAULT_HEARTBEAT_INTERVAL_MS = 15_000;\nconst DEFAULT_HEARTBEAT_TIMEOUT_MS = 45_000;\n\ntype SubscribeHandler = (event: AnyRealtimeEvent) => void;\n\ntype MessageDecodeResult =\n\t| {\n\t\t\ttype: \"raw-text\";\n\t\t\tdata: string;\n\t }\n\t| {\n\t\t\ttype: \"unsupported\";\n\t };\n\ntype ParsedMessage =\n\t| {\n\t\t\ttype: \"pong\";\n\t }\n\t| {\n\t\t\ttype: \"connection-established\";\n\t\t\tconnectionId: string | null;\n\t }\n\t| {\n\t\t\ttype: \"error\";\n\t\t\tmessage: string;\n\t }\n\t| {\n\t\t\ttype: \"event\";\n\t\t\tevent: AnyRealtimeEvent;\n\t }\n\t| {\n\t\t\ttype: \"invalid\";\n\t };\n\ntype VisitorAuthConfig = {\n\tkind: \"visitor\";\n\tvisitorId: string | null;\n\twebsiteId?: string | null;\n\tpublicKey?: string | null;\n};\n\ntype SessionAuthConfig = {\n\tkind: \"session\";\n\tsessionToken: string | null;\n\twebsiteId?: string | null;\n\tuserId?: string | null;\n};\n\ntype RealtimeAuthConfig = VisitorAuthConfig | SessionAuthConfig;\n\ntype ResolvedAuthConfig = {\n\ttype: \"visitor\" | \"session\";\n\tvisitorId: string | null;\n\twebsiteId: string | null;\n\tuserId: string | null;\n\tsessionToken: string | null;\n\tpublicKey: string | null;\n};\n\ntype RealtimeProviderProps = {\n\tchildren: React.ReactNode;\n\twsUrl?: string;\n\tauth: RealtimeAuthConfig | null;\n\tautoConnect?: boolean;\n\tonConnect?: () => void;\n\tonDisconnect?: () => void;\n\tonError?: (error: Error) => void;\n};\n\ntype RealtimeConnectionState = {\n\tisConnected: boolean;\n\tisConnecting: boolean;\n\terror: Error | null;\n\tsend: (event: AnyRealtimeEvent) => void;\n\tsendRaw: (data: string) => void;\n\tsubscribe: (handler: SubscribeHandler) => () => void;\n\tlastEvent: AnyRealtimeEvent | null;\n\tconnectionId: string | null;\n\treconnect: () => void;\n};\n\ntype RealtimeContextValue = RealtimeConnectionState & {\n\tvisitorId: string | null;\n\twebsiteId: string | null;\n\tuserId: string | null;\n};\n\nconst DEFAULT_WS_URL = \"wss://api.cossistant.com/ws\";\n\nconst RealtimeContext = createContext<RealtimeContextValue | null>(null);\n\n/**\n * Decodes WebSocket message data into a string.\n * Handles string, ArrayBuffer, and ArrayBufferView formats.\n */\nfunction decodeMessageData(data: unknown): MessageDecodeResult {\n\tif (typeof data === \"string\") {\n\t\treturn { type: \"raw-text\", data };\n\t}\n\n\tif (data instanceof ArrayBuffer) {\n\t\ttry {\n\t\t\treturn { type: \"raw-text\", data: new TextDecoder().decode(data) };\n\t\t} catch {\n\t\t\treturn { type: \"unsupported\" };\n\t\t}\n\t}\n\n\tif (ArrayBuffer.isView(data)) {\n\t\ttry {\n\t\t\treturn { type: \"raw-text\", data: new TextDecoder().decode(data.buffer) };\n\t\t} catch {\n\t\t\treturn { type: \"unsupported\" };\n\t\t}\n\t}\n\n\treturn { type: \"unsupported\" };\n}\n\n/**\n * Safely parses JSON string, returning null if invalid.\n */\nfunction parseJson(raw: string): unknown {\n\ttry {\n\t\treturn JSON.parse(raw);\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Extracts a string field from an unknown object, with optional validation.\n */\nfunction extractStringField(\n\tobj: unknown,\n\tfield: string,\n\trequired = false\n): string | null {\n\tif (!obj || typeof obj !== \"object\" || !(field in obj)) {\n\t\treturn required ? null : null;\n\t}\n\tconst value = (obj as Record<string, unknown>)[field];\n\tif (typeof value === \"string\" && value.length > 0) {\n\t\treturn value;\n\t}\n\treturn required ? null : null;\n}\n\n/**\n * Parses a WebSocket message and determines its type and content.\n */\nfunction parseWebSocketMessage(rawText: string): ParsedMessage {\n\t// Handle pong heartbeat\n\tif (rawText === \"pong\") {\n\t\treturn { type: \"pong\" };\n\t}\n\n\t// Try to parse as JSON\n\tconst parsed = parseJson(rawText);\n\tif (!parsed || typeof parsed !== \"object\") {\n\t\treturn { type: \"invalid\" };\n\t}\n\n\tconst messageType = extractStringField(parsed, \"type\");\n\n\t// Handle CONNECTION_ESTABLISHED\n\tif (messageType === \"CONNECTION_ESTABLISHED\") {\n\t\tconst payload = (parsed as { payload?: unknown }).payload;\n\t\tconst connectionId = extractStringField(payload, \"connectionId\");\n\t\treturn { type: \"connection-established\", connectionId };\n\t}\n\n\t// Handle error messages\n\tif (\"error\" in parsed && \"message\" in parsed) {\n\t\tconst message =\n\t\t\textractStringField(parsed, \"message\") || \"Realtime connection error\";\n\t\treturn { type: \"error\", message };\n\t}\n\n\t// Handle realtime events\n\tif (messageType && isValidEventType(messageType)) {\n\t\ttry {\n\t\t\tconst event = constructRealtimeEvent(parsed);\n\t\t\tif (!event) {\n\t\t\t\treturn { type: \"invalid\" };\n\t\t\t}\n\t\t\treturn { type: \"event\", event };\n\t\t} catch (error) {\n\t\t\tconsole.error(\"[Realtime] Failed to construct event\", error);\n\t\t\treturn { type: \"invalid\" };\n\t\t}\n\t}\n\n\treturn { type: \"invalid\" };\n}\n\n/**\n * Constructs a RealtimeEvent from parsed JSON data.\n * Returns null if required fields are missing or validation fails.\n */\nfunction constructRealtimeEvent(parsed: unknown): AnyRealtimeEvent | null {\n\tif (!parsed || typeof parsed !== \"object\" || !(\"type\" in parsed)) {\n\t\treturn null;\n\t}\n\n\tconst type = (parsed as { type: unknown }).type;\n\tif (!isValidEventType(type)) {\n\t\treturn null;\n\t}\n\n\tconst eventType = type;\n\n\t// Extract payload directly\n\tconst payloadSource = (parsed as { payload?: unknown }).payload;\n\n\tlet payload: unknown;\n\ttry {\n\t\tpayload = validateRealtimeEvent(eventType, payloadSource);\n\t} catch (error) {\n\t\tconsole.error(\"[Realtime] Received invalid event payload\", error);\n\t\treturn null;\n\t}\n\n\tconst organizationId = extractStringField(\n\t\tpayloadSource,\n\t\t\"organizationId\",\n\t\ttrue\n\t);\n\tconst websiteId = extractStringField(payloadSource, \"websiteId\", true);\n\n\tif (!organizationId) {\n\t\tconsole.error(\"[Realtime] Received event without organizationId\", parsed);\n\t\treturn null;\n\t}\n\n\tif (!websiteId) {\n\t\tconsole.error(\"[Realtime] Received event without websiteId\", parsed);\n\t\treturn null;\n\t}\n\n\tconst visitorId = extractStringField(parsed, \"visitorId\");\n\n\treturn {\n\t\ttype: eventType,\n\t\tpayload,\n\t\torganizationId,\n\t\twebsiteId,\n\t\tvisitorId,\n\t} as AnyRealtimeEvent;\n}\n\n/**\n * Checks if heartbeat has timed out.\n */\nfunction isHeartbeatTimedOut(\n\tlastHeartbeat: number,\n\ttimeoutMs: number\n): boolean {\n\tconst elapsed = Date.now() - lastHeartbeat;\n\treturn elapsed > timeoutMs;\n}\n\nfunction resolvePublicKey(explicit?: string | null): string | null {\n\tconst trimmed = explicit?.trim();\n\tif (trimmed) {\n\t\treturn trimmed;\n\t}\n\n\tconst fromEnv =\n\t\tprocess.env.NEXT_PUBLIC_COSSISTANT_API_KEY ||\n\t\tprocess.env.COSSISTANT_PUBLIC_KEY ||\n\t\tnull;\n\n\tconst normalized = fromEnv?.trim();\n\treturn normalized && normalized.length > 0 ? normalized : null;\n}\n\nfunction normalizeAuth(\n\tauth: RealtimeAuthConfig | null\n): ResolvedAuthConfig | null {\n\tif (!auth) {\n\t\treturn null;\n\t}\n\n\tif (auth.kind === \"visitor\") {\n\t\tconst visitorId = auth.visitorId?.trim() || null;\n\n\t\tif (!visitorId) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn {\n\t\t\ttype: \"visitor\",\n\t\t\tvisitorId,\n\t\t\twebsiteId: auth.websiteId?.trim() || null,\n\t\t\tuserId: null,\n\t\t\tsessionToken: null,\n\t\t\tpublicKey: resolvePublicKey(auth.publicKey ?? null),\n\t\t} satisfies ResolvedAuthConfig;\n\t}\n\n\tconst sessionToken = auth.sessionToken?.trim() || null;\n\n\tif (!sessionToken) {\n\t\treturn null;\n\t}\n\n\treturn {\n\t\ttype: \"session\",\n\t\tvisitorId: null,\n\t\twebsiteId: auth.websiteId?.trim() || null,\n\t\tuserId: auth.userId?.trim() || null,\n\t\tsessionToken,\n\t\tpublicKey: null,\n\t} satisfies ResolvedAuthConfig;\n}\n\nfunction buildSocketUrl(\n\tbaseUrl: string,\n\tauth: ResolvedAuthConfig | null\n): string | null {\n\tif (!auth) {\n\t\treturn null;\n\t}\n\n\ttry {\n\t\tconst url = new URL(baseUrl);\n\n\t\tif (auth.type === \"visitor\") {\n\t\t\turl.searchParams.set(\"visitorId\", auth.visitorId ?? \"\");\n\t\t\tconst publicKey = auth.publicKey;\n\t\t\tif (publicKey) {\n\t\t\t\turl.searchParams.set(\"publicKey\", publicKey);\n\t\t\t}\n\t\t} else {\n\t\t\turl.searchParams.set(\"sessionToken\", auth.sessionToken ?? \"\");\n\t\t\tif (auth.websiteId) {\n\t\t\t\turl.searchParams.set(\"websiteId\", auth.websiteId);\n\t\t\t}\n\t\t}\n\n\t\treturn url.toString();\n\t} catch (error) {\n\t\tconsole.error(\"[Realtime] Failed to build WebSocket URL\", error);\n\t\treturn null;\n\t}\n}\n\n/**\n * Provides websocket connectivity and heartbeating logic for realtime events.\n */\nexport function RealtimeProvider({\n\tchildren,\n\twsUrl = DEFAULT_WS_URL,\n\tauth,\n\tautoConnect = true,\n\tonConnect,\n\tonDisconnect,\n\tonError,\n}: RealtimeProviderProps): React.ReactElement {\n\tconst normalizedAuth = normalizeAuth(auth);\n\n\tconst socketUrl = buildSocketUrl(wsUrl, normalizedAuth);\n\tconst eventHandlersRef = useRef<Set<SubscribeHandler>>(new Set());\n\tconst lastHeartbeatRef = useRef<number>(Date.now());\n\tconst hasOpenedRef = useRef(false);\n\tconst previousUrlRef = useRef<string | null>(null);\n\tconst [connectionError, setConnectionError] = useState<Error | null>(null);\n\tconst [lastEvent, setLastEvent] = useState<AnyRealtimeEvent | null>(null);\n\tconst [connectionId, setConnectionId] = useState<string | null>(null);\n\n\tconst heartbeatIntervalMs = DEFAULT_HEARTBEAT_INTERVAL_MS;\n\tconst heartbeatTimeoutMs = DEFAULT_HEARTBEAT_TIMEOUT_MS;\n\n\tconst canConnect = Boolean(autoConnect && socketUrl);\n\tconst connectionUrl = canConnect ? socketUrl : null;\n\n\t// Track URL changes to detect when connection is being replaced\n\tuseEffect(() => {\n\t\tif (connectionUrl !== previousUrlRef.current) {\n\t\t\tpreviousUrlRef.current = connectionUrl;\n\t\t\t// Reset hasOpenedRef when URL changes so we know a new connection is starting\n\t\t\thasOpenedRef.current = false;\n\t\t}\n\t}, [connectionUrl]);\n\n\tconst {\n\t\tsendMessage,\n\t\tsendJsonMessage,\n\t\tlastMessage,\n\t\treadyState,\n\t\tgetWebSocket,\n\t} = useWebSocket(\n\t\tconnectionUrl,\n\t\t{\n\t\t\tshouldReconnect: (closeEvent) => {\n\t\t\t\tif (!canConnect) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tif (closeEvent.code === 1008 || closeEvent.code === 1011) {\n\t\t\t\t\tconst err = new Error(\n\t\t\t\t\t\tcloseEvent.reason ||\n\t\t\t\t\t\t\t\"Realtime connection closed by server. Please check your credentials.\"\n\t\t\t\t\t);\n\t\t\t\t\tsetConnectionError(err);\n\t\t\t\t\tonError?.(err);\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\treturn true;\n\t\t\t},\n\t\t\treconnectAttempts: autoConnect ? undefined : 0,\n\t\t\treconnectInterval: (attempt) => {\n\t\t\t\tconst base = 500 * 2 ** attempt;\n\t\t\t\treturn Math.min(base, 30_000);\n\t\t\t},\n\t\t\tretryOnError: false,\n\t\t\tonOpen: () => {\n\t\t\t\thasOpenedRef.current = true;\n\t\t\t\tsetConnectionError(null);\n\t\t\t\tlastHeartbeatRef.current = Date.now();\n\t\t\t\tonConnect?.();\n\t\t\t},\n\t\t\tonClose: () => {\n\t\t\t\tsetConnectionId(null);\n\t\t\t\tonDisconnect?.();\n\t\t\t},\n\t\t\tonError: (event) => {\n\t\t\t\tif (!canConnect) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst socketLike = event.target;\n\t\t\t\tconst currentSocket = getWebSocket();\n\t\t\t\tconst isBrowserSocket =\n\t\t\t\t\ttypeof WebSocket !== \"undefined\" && socketLike instanceof WebSocket;\n\t\t\t\tconst socketState = isBrowserSocket ? socketLike.readyState : undefined;\n\n\t\t\t\t// Only suppress errors for THIS provider's socket, not other nested providers\n\t\t\t\t// Check if the errored socket belongs to this provider instance\n\t\t\t\tconst isThisProvidersSocket = currentSocket === socketLike;\n\n\t\t\t\t// Suppress errors if:\n\t\t\t\t// 1. This socket was replaced (URL changed while connecting) - only for this provider\n\t\t\t\t// 2. Connection URL is null (component unmounting or disabled)\n\t\t\t\t// 3. Socket is in CLOSING/CLOSED state and hasn't opened (cleanup/unmount) - only for this provider\n\t\t\t\tif (\n\t\t\t\t\t(!isThisProvidersSocket && currentSocket) ||\n\t\t\t\t\t!connectionUrl ||\n\t\t\t\t\t(isThisProvidersSocket &&\n\t\t\t\t\t\t!hasOpenedRef.current &&\n\t\t\t\t\t\t(socketState === WebSocket.CLOSING ||\n\t\t\t\t\t\t\tsocketState === WebSocket.CLOSED))\n\t\t\t\t) {\n\t\t\t\t\t// Suppress these expected errors during connection replacement or cleanup\n\t\t\t\t\t// But only if it's THIS provider's socket being replaced\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// For errors that occur during CONNECTING state, check if URL changed\n\t\t\t\t// Only suppress if it's this provider's socket\n\t\t\t\tif (\n\t\t\t\t\tisThisProvidersSocket &&\n\t\t\t\t\t!hasOpenedRef.current &&\n\t\t\t\t\tsocketState === WebSocket.CONNECTING &&\n\t\t\t\t\tconnectionUrl !== previousUrlRef.current\n\t\t\t\t) {\n\t\t\t\t\t// URL changed while connecting, suppress error\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst err = new Error(`WebSocket error: ${event.type}`);\n\t\t\t\tsetConnectionError(err);\n\t\t\t\tonError?.(err);\n\t\t\t},\n\t\t},\n\t\tcanConnect\n\t);\n\n\tuseEffect(() => {\n\t\tif (!lastMessage) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Decode message data from various formats\n\t\tconst decoded = decodeMessageData(lastMessage.data);\n\t\tif (decoded.type === \"unsupported\") {\n\t\t\treturn;\n\t\t}\n\n\t\t// Parse the message and determine its type\n\t\tconst message = parseWebSocketMessage(decoded.data);\n\n\t\t// Handle different message types\n\t\tswitch (message.type) {\n\t\t\tcase \"pong\":\n\t\t\t\tlastHeartbeatRef.current = Date.now();\n\t\t\t\tbreak;\n\n\t\t\tcase \"connection-established\":\n\t\t\t\tsetConnectionId(message.connectionId);\n\t\t\t\tlastHeartbeatRef.current = Date.now();\n\t\t\t\tbreak;\n\n\t\t\tcase \"error\": {\n\t\t\t\tconst err = new Error(message.message);\n\t\t\t\tsetConnectionError(err);\n\t\t\t\tonError?.(err);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"event\":\n\t\t\t\tlastHeartbeatRef.current = Date.now();\n\t\t\t\tsetLastEvent(message.event);\n\t\t\t\tfor (const handler of eventHandlersRef.current) {\n\t\t\t\t\tPromise.resolve(handler(message.event)).catch((error) => {\n\t\t\t\t\t\tconst err =\n\t\t\t\t\t\t\terror instanceof Error\n\t\t\t\t\t\t\t\t? error\n\t\t\t\t\t\t\t\t: new Error(`Subscriber threw an exception: ${String(error)}`);\n\t\t\t\t\t\tonError?.(err);\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tdefault:\n\t\t\t\t// Silently ignore invalid or unknown messages\n\t\t\t\tbreak;\n\t\t}\n\t}, [lastMessage, onError]);\n\n\tuseEffect(() => {\n\t\tif (!canConnect) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst interval = window.setInterval(() => {\n\t\t\tif (readyState !== ReadyState.OPEN) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Check if heartbeat has timed out\n\t\t\tif (isHeartbeatTimedOut(lastHeartbeatRef.current, heartbeatTimeoutMs)) {\n\t\t\t\tconst socket = getWebSocket();\n\t\t\t\tsocket?.close(4000, \"Heartbeat timeout\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Send ping to keep connection alive\n\t\t\ttry {\n\t\t\t\tsendMessage(\"ping\");\n\t\t\t} catch {\n\t\t\t\t// Ignore send failures; reconnect logic will handle it\n\t\t\t}\n\t\t}, heartbeatIntervalMs);\n\n\t\treturn () => {\n\t\t\twindow.clearInterval(interval);\n\t\t};\n\t}, [\n\t\tcanConnect,\n\t\theartbeatIntervalMs,\n\t\theartbeatTimeoutMs,\n\t\treadyState,\n\t\tsendMessage,\n\t\tgetWebSocket,\n\t]);\n\n\tconst send = useCallback(\n\t\t(event: AnyRealtimeEvent) => {\n\t\t\tif (!connectionUrl) {\n\t\t\t\tthrow new Error(\"Realtime connection is disabled\");\n\t\t\t}\n\n\t\t\tif (readyState !== ReadyState.OPEN) {\n\t\t\t\tthrow new Error(\"Realtime connection is not established\");\n\t\t\t}\n\n\t\t\tsendJsonMessage(event);\n\t\t},\n\t\t[connectionUrl, readyState, sendJsonMessage]\n\t);\n\n\tconst sendRaw = useCallback(\n\t\t(data: string) => {\n\t\t\tif (!connectionUrl) {\n\t\t\t\tthrow new Error(\"Realtime connection is disabled\");\n\t\t\t}\n\n\t\t\tif (readyState !== ReadyState.OPEN) {\n\t\t\t\tthrow new Error(\"Realtime connection is not established\");\n\t\t\t}\n\n\t\t\tsendMessage(data);\n\t\t},\n\t\t[connectionUrl, readyState, sendMessage]\n\t);\n\n\tconst subscribe = useCallback((handler: SubscribeHandler) => {\n\t\teventHandlersRef.current.add(handler);\n\t\treturn () => {\n\t\t\teventHandlersRef.current.delete(handler);\n\t\t};\n\t}, []);\n\n\tconst reconnect = useCallback(() => {\n\t\tconst socket = getWebSocket();\n\t\tsocket?.close();\n\t}, [getWebSocket]);\n\n\tconst connection = useMemo<RealtimeConnectionState>(\n\t\t() => ({\n\t\t\tisConnected: readyState === ReadyState.OPEN,\n\t\t\tisConnecting: readyState === ReadyState.CONNECTING,\n\t\t\terror: connectionError,\n\t\t\tsend,\n\t\t\tsendRaw,\n\t\t\tsubscribe,\n\t\t\tlastEvent,\n\t\t\tconnectionId,\n\t\t\treconnect,\n\t\t}),\n\t\t[\n\t\t\treadyState,\n\t\t\tconnectionError,\n\t\t\tsend,\n\t\t\tsendRaw,\n\t\t\tsubscribe,\n\t\t\tlastEvent,\n\t\t\tconnectionId,\n\t\t\treconnect,\n\t\t]\n\t);\n\n\tconst value = useMemo<RealtimeContextValue>(\n\t\t() => ({\n\t\t\t...connection,\n\t\t\tvisitorId: normalizedAuth?.visitorId ?? null,\n\t\t\twebsiteId: normalizedAuth?.websiteId ?? null,\n\t\t\tuserId: normalizedAuth?.userId ?? null,\n\t\t}),\n\t\t[\n\t\t\tconnection,\n\t\t\tnormalizedAuth?.visitorId,\n\t\t\tnormalizedAuth?.websiteId,\n\t\t\tnormalizedAuth?.userId,\n\t\t]\n\t);\n\n\treturn (\n\t\t<RealtimeContext.Provider value={value}>\n\t\t\t{children}\n\t\t</RealtimeContext.Provider>\n\t);\n}\n\n/**\n * Returns the realtime connection context.\n */\nexport function useRealtimeConnection(): RealtimeContextValue {\n\tconst context = useContext(RealtimeContext);\n\tif (!context) {\n\t\tthrow new Error(\n\t\t\t\"useRealtimeConnection must be used within RealtimeProvider\"\n\t\t);\n\t}\n\n\treturn context;\n}\n\nexport type { RealtimeContextValue };\nexport type { RealtimeAuthConfig };\nexport type { RealtimeProviderProps };\nexport type { RealtimeEvent } from \"@cossistant/types/realtime-events\";\n"],"mappings":"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"}
|