@dotwire/react 0.0.0-stage → 0.2.0

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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/provider.tsx","../src/hooks.ts","../src/reducers.ts","../src/subscription.ts"],"sourcesContent":["import { createContext, useEffect, useState, type ReactNode } from 'react';\nimport { DotwireClient, type DotwireClientOptions } from '@dotwire/client';\n\nexport const DotwireContext = createContext<DotwireClient | null>(null);\n\nexport interface DotwireProviderProps {\n children?: ReactNode;\n /**\n * Options to construct a `DotwireClient` internally. Ignored if `client` is also given.\n * Construct this once (e.g. with `useMemo`/`useState`, or a module-level constant) — a new\n * object identity on every render tears down and rebuilds the client.\n */\n options?: DotwireClientOptions;\n /**\n * A pre-built `DotwireClient` instance. When given, the provider never constructs or\n * disposes a client itself — the caller owns that instance's lifecycle.\n */\n client?: DotwireClient;\n}\n\n/**\n * Provides a `DotwireClient` to `useDotwireClient`/`useConnectionState`/`useRoom`/`useTyping`/\n * `usePresence` (spec §7).\n *\n * - Construction happens in an effect, never during render, so this component is safe to\n * render on the server (`react-dom/server`) — `useDotwireClient()` simply returns `null`\n * until the effect runs on the client. See `test/ssr.test.tsx`.\n * - In React 18 StrictMode, effects run an extra setup+cleanup pass in development; the\n * cleanup below disposes whatever instance that pass constructed before the next one runs,\n * so double-invocation is safe.\n * - A client built from `options` is disposed on unmount (or when `options` changes). A\n * client passed via the `client` prop is never disposed here — the caller owns it.\n */\nexport function DotwireProvider(props: DotwireProviderProps): ReactNode {\n const { children, options, client: providedClient } = props;\n const [managedClient, setManagedClient] = useState<DotwireClient | null>(null);\n\n useEffect(() => {\n if (providedClient || !options) {\n return undefined;\n }\n\n const instance = new DotwireClient(options);\n setManagedClient(instance);\n\n return () => {\n instance.dispose();\n setManagedClient((current) => (current === instance ? null : current));\n };\n // `options` is compared by reference deliberately: a new object rebuilds the client.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [providedClient, options]);\n\n const client = providedClient ?? managedClient;\n\n return <DotwireContext.Provider value={client}>{children}</DotwireContext.Provider>;\n}\n","import { useCallback, useContext, useEffect, useReducer, useRef, useState } from 'react';\nimport type { ConnectionState, DotwireClient, RoomMessage, SendAck } from '@dotwire/client';\nimport { DotwireContext } from './provider.js';\nimport {\n initialPresenceState,\n initialRoomMessagesState,\n initialTypingState,\n presenceReducer,\n roomMessagesReducer,\n typingReducer,\n typingUsersOf,\n type RoomMessagesState,\n type RoomStatus\n} from './reducers.js';\nimport { releaseRoom, retainRoom } from './subscription.js';\n\n/** The `DotwireClient` from the nearest `DotwireProvider`, or `null` before it has mounted. */\nexport function useDotwireClient(): DotwireClient | null {\n return useContext(DotwireContext);\n}\n\n/** The client's live `ConnectionState`, re-rendering on every `onStateChange` event. */\nexport function useConnectionState(): ConnectionState {\n const client = useDotwireClient();\n const [state, setState] = useState<ConnectionState>(() => client?.getConnectionState() ?? 'disconnected');\n\n useEffect(() => {\n if (!client) {\n setState('disconnected');\n return undefined;\n }\n setState(client.getConnectionState());\n return client.onStateChange(setState);\n }, [client]);\n\n return state;\n}\n\nexport interface UseRoomOptions {\n /** How many of the latest messages to load on subscribe. Default 50. */\n history?: number;\n}\n\nexport interface UseRoomResult {\n messages: RoomMessage[];\n status: RoomStatus;\n error: unknown;\n hasMore: boolean;\n loadOlder: () => Promise<void>;\n send: (content: string) => Promise<SendAck>;\n}\n\n/**\n * Subscribes to `roomId` for as long as the component is mounted: loads the initial history\n * page, applies live messages/retractions/revocation, and exposes `loadOlder`/`send` (spec\n * §7). All state transitions are delegated to `roomMessagesReducer` (`src/reducers.ts`).\n */\nexport function useRoom(roomId: string, options: UseRoomOptions = {}): UseRoomResult {\n const history = options.history ?? 50;\n const client = useDotwireClient();\n const [state, dispatch] = useReducer(roomMessagesReducer, initialRoomMessagesState);\n const stateRef = useRef<RoomMessagesState>(state);\n stateRef.current = state;\n const scopeRef = useRef<object | null>(null);\n\n useEffect(() => {\n if (!client) return undefined;\n\n const scope = {};\n scopeRef.current = scope;\n dispatch({ type: 'reset' });\n const room = client.room(roomId);\n retainRoom(client, roomId);\n let cancelled = false;\n\n const unsubscribers = [\n room.onMessage((message) => dispatch({ type: 'message', message })),\n room.onMessageRetracted(({ seq }) => dispatch({ type: 'retracted', seq })),\n room.onMembershipRevoked(() => dispatch({ type: 'revoked' }))\n ];\n\n room\n .subscribe({ history })\n .then((page) => {\n if (cancelled) return;\n dispatch({ type: 'subscribed', page: page ?? { messages: [], hasMore: false } });\n })\n .catch((error: unknown) => {\n if (!cancelled) dispatch({ type: 'error', error });\n });\n\n return () => {\n cancelled = true;\n if (scopeRef.current === scope) scopeRef.current = null;\n for (const unsub of unsubscribers) unsub();\n room.release();\n // Only the last interested hook for this (client, roomId) actually leaves the hub\n // group — see `src/subscription.ts`. We deliberately do NOT call `room.dispose()`\n // here: it would unconditionally unsubscribe, which is exactly what the refcount\n // guards against when `useTyping`/`usePresence` share the same room.\n if (releaseRoom(client, roomId)) {\n client.unsubscribe(roomId).catch(() => {});\n }\n };\n }, [client, roomId, history]);\n\n const loadOlder = useCallback(async () => {\n if (!client) return;\n const oldest = stateRef.current.messages[0];\n if (!oldest) return;\n const scope = scopeRef.current;\n try {\n const page = await client.getHistory(roomId, { beforeSeq: oldest.seq, limit: history });\n if (scopeRef.current === scope) dispatch({ type: 'olderLoaded', page });\n } catch (error) {\n if (scopeRef.current === scope) dispatch({ type: 'error', error });\n }\n }, [client, roomId, history]);\n\n const send = useCallback(\n async (content: string): Promise<SendAck> => {\n if (!client) {\n throw new Error('Dotwire client is not ready yet.');\n }\n try {\n return await client.sendMessage(roomId, content);\n } catch (error) {\n dispatch({ type: 'error', error });\n throw error;\n }\n },\n [client, roomId]\n );\n\n return {\n messages: state.messages,\n status: state.status,\n error: state.error,\n hasMore: state.hasMore,\n loadOlder,\n send\n };\n}\n\nexport interface UseTypingResult {\n /** User ids currently considered typing (expired entries are removed automatically). */\n typingUsers: string[];\n notifyTyping: () => Promise<void>;\n stopTyping: () => Promise<void>;\n}\n\n/** How long a typing notification stays valid absent a follow-up (spec §7: \"with expiry\"). */\nconst TYPING_EXPIRY_MS = 3500;\nconst TYPING_EXPIRY_CHECK_INTERVAL_MS = 1000;\n\n/**\n * Tracks who is typing in `roomId`. Expiry is time-based (a user who goes silent without an\n * explicit stop notification drops out after `TYPING_EXPIRY_MS`) and driven by `typingReducer`,\n * which takes an explicit `now` so it is unit-testable without timers.\n */\nexport function useTyping(roomId: string): UseTypingResult {\n const client = useDotwireClient();\n const [state, dispatch] = useReducer(typingReducer, initialTypingState);\n const roomHandleRef = useRef<ReturnType<DotwireClient['room']> | null>(null);\n\n useEffect(() => {\n if (!client) return undefined;\n\n const room = client.room(roomId);\n roomHandleRef.current = room;\n retainRoom(client, roomId);\n // Typing/presence delivery requires hub group membership, same as message delivery.\n room.subscribe().catch(() => {\n // Best-effort: if this never resolves, no typing events will arrive for this room;\n // the caller can observe that via useConnectionState/useRoom's own error state.\n });\n\n const unsub = room.onTyping((notification) => {\n dispatch({ type: 'notification', userId: notification.userId, isTyping: notification.isTyping, now: Date.now() });\n });\n\n const interval = setInterval(() => {\n dispatch({ type: 'expire', now: Date.now(), ttlMs: TYPING_EXPIRY_MS });\n }, TYPING_EXPIRY_CHECK_INTERVAL_MS);\n\n return () => {\n unsub();\n clearInterval(interval);\n roomHandleRef.current = null;\n room.release();\n if (releaseRoom(client, roomId)) {\n client.unsubscribe(roomId).catch(() => {});\n }\n };\n }, [client, roomId]);\n\n const notifyTyping = useCallback(async () => {\n await roomHandleRef.current?.notifyTyping();\n }, []);\n\n const stopTyping = useCallback(async () => {\n await roomHandleRef.current?.stopTyping();\n }, []);\n\n return { typingUsers: typingUsersOf(state), notifyTyping, stopTyping };\n}\n\nexport interface UsePresenceResult {\n /** User ids present, accumulated from deltas since this hook mounted. */\n present: string[];\n}\n\n/**\n * Accumulates presence deltas for `roomId` since mount (spec §7). There is no presence\n * snapshot endpoint on the server, so a component that mounts this hook after other members\n * already joined will not see them in `present` until the next `PresenceUpdated` delta for\n * this room (e.g. the next member to join or leave).\n */\nexport function usePresence(roomId: string): UsePresenceResult {\n const client = useDotwireClient();\n const [state, dispatch] = useReducer(presenceReducer, initialPresenceState);\n\n useEffect(() => {\n if (!client) return undefined;\n\n const room = client.room(roomId);\n retainRoom(client, roomId);\n room.subscribe().catch(() => {\n // Best-effort, same rationale as useTyping.\n });\n\n const unsub = room.onPresence((delta) => {\n dispatch({ type: 'delta', joined: delta.joined, left: delta.left });\n });\n\n return () => {\n unsub();\n room.release();\n if (releaseRoom(client, roomId)) {\n client.unsubscribe(roomId).catch(() => {});\n }\n };\n }, [client, roomId]);\n\n return { present: state };\n}\n","/**\n * Pure state-transition functions backing `useRoom`, `useTyping`, and `usePresence`\n * (spec §7). None of these import React; each is unit-tested directly with Node's\n * test runner in `test/reducers.test.ts`. The hooks in `src/hooks.ts` are thin\n * `useReducer` wrappers around these.\n */\nimport type { HistoryPage, RoomMessage } from '@dotwire/client';\n\n// ---------------------------------------------------------------------------\n// Room messages\n// ---------------------------------------------------------------------------\n\nexport type RoomStatus = 'connecting' | 'ready' | 'revoked' | 'error';\n\nexport interface RoomMessagesState {\n messages: RoomMessage[];\n status: RoomStatus;\n error: unknown;\n hasMore: boolean;\n}\n\nexport type RoomMessagesAction =\n | { type: 'reset' }\n | { type: 'subscribed'; page: HistoryPage }\n | { type: 'message'; message: RoomMessage }\n | { type: 'retracted'; seq: number }\n | { type: 'revoked' }\n | { type: 'error'; error: unknown }\n | { type: 'olderLoaded'; page: HistoryPage };\n\nexport const initialRoomMessagesState: RoomMessagesState = {\n messages: [],\n status: 'connecting',\n error: null,\n hasMore: false\n};\n\n/**\n * Merges `incoming` into `existing`, deduping by `seq` (last write for a given seq\n * wins) and returning the result sorted ascending by `seq` (spec §6, §7: \"consumers\n * order by seq themselves\").\n */\nfunction mergeMessages(existing: RoomMessage[], incoming: RoomMessage[]): RoomMessage[] {\n if (incoming.length === 0) return existing;\n const last = existing[existing.length - 1];\n if (incoming.length === 1 && (last === undefined || incoming[0].seq > last.seq)) return [...existing, incoming[0]];\n const bySeq = new Map<number, RoomMessage>();\n for (const message of existing) bySeq.set(message.seq, message);\n for (const message of incoming) bySeq.set(message.seq, message);\n return Array.from(bySeq.values()).sort((a, b) => a.seq - b.seq);\n}\n\nexport function roomMessagesReducer(\n state: RoomMessagesState,\n action: RoomMessagesAction\n): RoomMessagesState {\n switch (action.type) {\n case 'reset':\n return initialRoomMessagesState;\n case 'subscribed':\n return {\n messages: mergeMessages(state.messages, action.page.messages),\n status: 'ready',\n error: null,\n hasMore: action.page.hasMore\n };\n case 'message':\n return { ...state, messages: mergeMessages(state.messages, [action.message]) };\n case 'retracted':\n return { ...state, messages: state.messages.filter((m) => m.seq !== action.seq) };\n case 'revoked':\n return { ...state, status: 'revoked' };\n case 'error':\n return { ...state, status: 'error', error: action.error };\n case 'olderLoaded':\n return {\n ...state,\n messages: mergeMessages(state.messages, action.page.messages),\n hasMore: action.page.hasMore\n };\n default:\n return state;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Typing\n// ---------------------------------------------------------------------------\n\nexport interface TypingEntry {\n updatedAt: number;\n}\n\n/** Keyed by userId; a key is present only while that user is considered typing. */\nexport type TypingState = Record<string, TypingEntry>;\n\nexport type TypingAction =\n | { type: 'notification'; userId: string; isTyping: boolean; now: number }\n | { type: 'expire'; now: number; ttlMs: number };\n\nexport const initialTypingState: TypingState = {};\n\nexport function typingReducer(state: TypingState, action: TypingAction): TypingState {\n switch (action.type) {\n case 'notification': {\n if (action.isTyping) {\n return { ...state, [action.userId]: { updatedAt: action.now } };\n }\n if (!(action.userId in state)) return state;\n const next = { ...state };\n delete next[action.userId];\n return next;\n }\n case 'expire': {\n let changed = false;\n const next: TypingState = {};\n for (const [userId, entry] of Object.entries(state)) {\n if (action.now - entry.updatedAt < action.ttlMs) {\n next[userId] = entry;\n } else {\n changed = true;\n }\n }\n return changed ? next : state;\n }\n default:\n return state;\n }\n}\n\nexport function typingUsersOf(state: TypingState): string[] {\n return Object.keys(state);\n}\n\n// ---------------------------------------------------------------------------\n// Presence\n// ---------------------------------------------------------------------------\n\n/** Sorted, deduped user ids accumulated from deltas since mount (spec §7: no snapshot endpoint). */\nexport type PresenceState = string[];\n\nexport type PresenceAction = { type: 'delta'; joined: string[]; left: string[] };\n\nexport const initialPresenceState: PresenceState = [];\n\nexport function presenceReducer(state: PresenceState, action: PresenceAction): PresenceState {\n const set = new Set(state);\n for (const id of action.left) set.delete(id);\n for (const id of action.joined) set.add(id);\n return Array.from(set).sort();\n}\n","import type { DotwireClient } from '@dotwire/client';\n\n/**\n * `DotwireClient.subscribe`/`unsubscribe` (and `DotwireRoomHandle.subscribe`/`unsubscribe`)\n * operate on a single, client-wide \"am I in this room's hub group\" flag per `roomId` — it is\n * not refcounted. If two hooks (e.g. `useRoom` and `useTyping`) are both mounted against the\n * same room, calling `subscribe` from each is harmless (redundant hub invokes), but the FIRST\n * hook to unmount would call `unsubscribe` and silently drop the OTHER hook's live delivery.\n *\n * This module refcounts unsubscribe intent per `(client, roomId)` pair so the underlying hub\n * group is only actually left once every hook interested in that room has released it. It does\n * not wrap `subscribe` itself, since redundant `Subscribe` hub invokes are cheap and safe.\n */\nconst refCounts = new WeakMap<DotwireClient, Map<string, number>>();\n\nexport function retainRoom(client: DotwireClient, roomId: string): void {\n let rooms = refCounts.get(client);\n if (!rooms) {\n rooms = new Map();\n refCounts.set(client, rooms);\n }\n rooms.set(roomId, (rooms.get(roomId) ?? 0) + 1);\n}\n\n/**\n * Releases one hook's interest in `roomId`. Returns `true` when this was the last interested\n * hook, meaning the caller should now call `client.unsubscribe(roomId)`.\n */\nexport function releaseRoom(client: DotwireClient, roomId: string): boolean {\n const rooms = refCounts.get(client);\n if (!rooms) return true;\n const next = (rooms.get(roomId) ?? 1) - 1;\n if (next <= 0) {\n rooms.delete(roomId);\n return true;\n }\n rooms.set(roomId, next);\n return false;\n}\n"],"mappings":"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","names":["useEffect","useState","useState","useEffect"]}
package/package.json CHANGED
@@ -1,6 +1,54 @@
1
1
  {
2
2
  "name": "@dotwire/react",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.0",
4
+ "description": "React hooks and provider for @dotwire/client: DotwireProvider, useRoom, useTyping, usePresence, useConnectionState.",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/pasztor-mark/dotwire.git",
8
+ "directory": "packages/react"
9
+ },
10
+ "main": "./dist/index.cjs",
11
+ "module": "./dist/index.js",
12
+ "types": "./dist/index.d.ts",
13
+ "exports": {
14
+ ".": {
15
+ "types": "./dist/index.d.ts",
16
+ "import": "./dist/index.js",
17
+ "require": "./dist/index.cjs"
18
+ }
19
+ },
20
+ "files": ["dist", "README.md"],
21
+ "sideEffects": false,
22
+ "engines": {
23
+ "node": ">=20"
24
+ },
25
+ "scripts": {
26
+ "build": "tsup",
27
+ "dev": "tsup --watch",
28
+ "test": "tsc -p tsconfig.test.json && node --test dist-test/test/**/*.test.js"
29
+ },
30
+ "keywords": [
31
+ "dotwire",
32
+ "chat",
33
+ "sdk",
34
+ "react",
35
+ "hooks",
36
+ "signalr"
37
+ ],
38
+ "license": "Apache-2.0",
39
+ "type": "module",
40
+ "peerDependencies": {
41
+ "react": ">=18",
42
+ "@dotwire/client": "^0.2.0"
43
+ },
44
+ "devDependencies": {
45
+ "@dotwire/client": "*",
46
+ "@types/node": "^22.0.0",
47
+ "@types/react": "^18.3.0",
48
+ "@types/react-dom": "^18.3.0",
49
+ "react": "^18.3.0",
50
+ "react-dom": "^18.3.0",
51
+ "tsup": "^8.5.1",
52
+ "typescript": "^5.8.0"
53
+ }
54
+ }