@synap-core/client 0.1.0 → 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.
- package/dist/chunk-6MAWMLEW.js +66 -0
- package/dist/chunk-BVKLN5W3.js +118 -0
- package/dist/index.cjs +135 -1
- package/dist/index.d.ts +40 -1276
- package/dist/index.js +13 -1
- package/dist/react/provider.js +6 -60
- package/dist/react/usePresence.d.ts +29 -0
- package/dist/react/useYjs.d.ts +24 -0
- package/dist/react.cjs +109 -6
- package/dist/react.d.ts +10 -0
- package/dist/react.js +88 -1
- package/dist/realtime.cjs +109 -73
- package/dist/realtime.d.ts +59 -56
- package/dist/realtime.js +6 -90
- package/package.json +10 -3
package/dist/index.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createRealtimeClient
|
|
3
|
+
} from "./chunk-BVKLN5W3.js";
|
|
4
|
+
|
|
1
5
|
// src/index.ts
|
|
2
6
|
import { createTRPCClient, httpBatchLink } from "@trpc/client";
|
|
3
7
|
function createSynapClient(config) {
|
|
4
|
-
|
|
8
|
+
const trpcClient = createTRPCClient({
|
|
5
9
|
links: [
|
|
6
10
|
httpBatchLink({
|
|
7
11
|
url: `${config.url}/trpc`,
|
|
@@ -10,6 +14,14 @@ function createSynapClient(config) {
|
|
|
10
14
|
})
|
|
11
15
|
]
|
|
12
16
|
});
|
|
17
|
+
const realtimeClient = config.realtime ? createRealtimeClient({
|
|
18
|
+
url: config.url,
|
|
19
|
+
auth: config.realtime
|
|
20
|
+
}) : null;
|
|
21
|
+
return {
|
|
22
|
+
trpc: trpcClient,
|
|
23
|
+
realtime: realtimeClient
|
|
24
|
+
};
|
|
13
25
|
}
|
|
14
26
|
var index_default = createSynapClient;
|
|
15
27
|
export {
|
package/dist/react/provider.js
CHANGED
|
@@ -1,64 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
var trpc = createTRPCReact();
|
|
9
|
-
var SynapContext = createContext(null);
|
|
10
|
-
function SynapProvider({ url = "http://localhost:3000", getToken, headers, children }) {
|
|
11
|
-
const queryClient = useMemo(() => new QueryClient({
|
|
12
|
-
defaultOptions: {
|
|
13
|
-
queries: {
|
|
14
|
-
staleTime: 5 * 60 * 1e3,
|
|
15
|
-
// 5 minutes
|
|
16
|
-
retry: 1
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
}), []);
|
|
20
|
-
const trpcClient = useMemo(() => trpc.createClient({
|
|
21
|
-
links: [
|
|
22
|
-
httpBatchLink({
|
|
23
|
-
url: `${url}/trpc`,
|
|
24
|
-
async headers() {
|
|
25
|
-
const baseHeaders = {
|
|
26
|
-
...headers
|
|
27
|
-
};
|
|
28
|
-
if (getToken) {
|
|
29
|
-
const token = await getToken();
|
|
30
|
-
if (token) {
|
|
31
|
-
baseHeaders["Authorization"] = `Bearer ${token}`;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
return baseHeaders;
|
|
35
|
-
},
|
|
36
|
-
fetch(url2, options) {
|
|
37
|
-
return fetch(url2, {
|
|
38
|
-
...options,
|
|
39
|
-
credentials: "include"
|
|
40
|
-
// Send cookies
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
})
|
|
44
|
-
]
|
|
45
|
-
}), [url, getToken, headers]);
|
|
46
|
-
const contextValue = useMemo(() => ({
|
|
47
|
-
api: trpc,
|
|
48
|
-
url
|
|
49
|
-
}), [url]);
|
|
50
|
-
return /* @__PURE__ */ jsx(SynapContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(trpc.Provider, { client: trpcClient, queryClient, children: /* @__PURE__ */ jsx(QueryClientProvider, { client: queryClient, children }) }) });
|
|
51
|
-
}
|
|
52
|
-
function useSynap() {
|
|
53
|
-
const context = useContext(SynapContext);
|
|
54
|
-
if (!context) {
|
|
55
|
-
throw new Error("useSynap must be used within a SynapProvider");
|
|
56
|
-
}
|
|
57
|
-
return context;
|
|
58
|
-
}
|
|
59
|
-
function useSynapClient() {
|
|
60
|
-
return useSynap().api;
|
|
61
|
-
}
|
|
2
|
+
import {
|
|
3
|
+
SynapProvider,
|
|
4
|
+
trpc,
|
|
5
|
+
useSynap,
|
|
6
|
+
useSynapClient
|
|
7
|
+
} from "../chunk-6MAWMLEW.js";
|
|
62
8
|
export {
|
|
63
9
|
SynapProvider,
|
|
64
10
|
trpc,
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React Hook: usePresence
|
|
3
|
+
*
|
|
4
|
+
* Hook for real-time presence tracking in a view.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* function WhiteboardView({ viewId }: { viewId: string }) {
|
|
9
|
+
* const { client } = useSynapClient();
|
|
10
|
+
* const { users, moveCursor } = usePresence(client?.realtime, viewId);
|
|
11
|
+
*
|
|
12
|
+
* return (
|
|
13
|
+
* <div onMouseMove={(e) => moveCursor(e.clientX, e.clientY)}>
|
|
14
|
+
* {users.map(user => (
|
|
15
|
+
* <Cursor key={user.userId} {...user} />
|
|
16
|
+
* ))}
|
|
17
|
+
* </div>
|
|
18
|
+
* );
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
import type { RealtimeClient } from '../realtime.js';
|
|
23
|
+
import type { UserPresence } from '@synap-core/types/realtime';
|
|
24
|
+
export interface UsePresenceReturn {
|
|
25
|
+
users: UserPresence[];
|
|
26
|
+
moveCursor: (x: number, y: number) => void;
|
|
27
|
+
setTyping: (isTyping: boolean) => void;
|
|
28
|
+
}
|
|
29
|
+
export declare function usePresence(client: RealtimeClient | null, viewId: string, viewType?: string): UsePresenceReturn;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React Hook: useYjs
|
|
3
|
+
*
|
|
4
|
+
* Hook for Yjs CRDT synchronization in a view.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* function TldrawWhiteboard({ roomName }: { roomName: string }) {
|
|
9
|
+
* const { client } = useSynapClient();
|
|
10
|
+
* const { ydoc } = useYjs(client?.realtime, roomName);
|
|
11
|
+
*
|
|
12
|
+
* if (!ydoc) return <div>Connecting...</div>;
|
|
13
|
+
*
|
|
14
|
+
* return <TldrawEditor store={createTLStore({ doc: ydoc })} />;
|
|
15
|
+
* }
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
import type { RealtimeClient } from '../realtime.js';
|
|
19
|
+
import type { YDoc } from '@synap-core/types/realtime';
|
|
20
|
+
export interface UseYjsReturn {
|
|
21
|
+
ydoc: YDoc | null;
|
|
22
|
+
isConnected: boolean;
|
|
23
|
+
}
|
|
24
|
+
export declare function useYjs(client: RealtimeClient | null, roomName: string): UseYjsReturn;
|
package/dist/react.cjs
CHANGED
|
@@ -20,19 +20,122 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/react.ts
|
|
21
21
|
var react_exports = {};
|
|
22
22
|
__export(react_exports, {
|
|
23
|
-
SynapQueryClientProvider: () =>
|
|
23
|
+
SynapQueryClientProvider: () => import_react_query4.QueryClientProvider,
|
|
24
24
|
createSynapReact: () => createSynapReact,
|
|
25
|
-
default: () => react_default
|
|
25
|
+
default: () => react_default,
|
|
26
|
+
useEntities: () => useEntities,
|
|
27
|
+
useEvents: () => useEvents,
|
|
28
|
+
usePresence: () => usePresence,
|
|
29
|
+
useThreads: () => useThreads,
|
|
30
|
+
useYjs: () => useYjs
|
|
26
31
|
});
|
|
27
32
|
module.exports = __toCommonJS(react_exports);
|
|
28
|
-
var
|
|
29
|
-
|
|
33
|
+
var import_react_query3 = require("@trpc/react-query");
|
|
34
|
+
|
|
35
|
+
// src/react/provider.tsx
|
|
36
|
+
var import_react = require("react");
|
|
37
|
+
var import_react_query = require("@tanstack/react-query");
|
|
38
|
+
var import_react_query2 = require("@trpc/react-query");
|
|
39
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
40
|
+
var trpc = (0, import_react_query2.createTRPCReact)();
|
|
41
|
+
var SynapContext = (0, import_react.createContext)(null);
|
|
42
|
+
function useSynap() {
|
|
43
|
+
const context = (0, import_react.useContext)(SynapContext);
|
|
44
|
+
if (!context) {
|
|
45
|
+
throw new Error("useSynap must be used within a SynapProvider");
|
|
46
|
+
}
|
|
47
|
+
return context;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// src/react/useEntities.ts
|
|
51
|
+
function useEntities(options = {}) {
|
|
52
|
+
const { api } = useSynap();
|
|
53
|
+
return api.entities.list.useQuery({
|
|
54
|
+
type: options.type,
|
|
55
|
+
limit: options.limit || 50
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// src/react/useThreads.ts
|
|
60
|
+
function useThreads(_limit = 20) {
|
|
61
|
+
throw new Error("Chat router not yet implemented in API");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// src/react/useEvents.ts
|
|
65
|
+
function useEvents(options = {}) {
|
|
66
|
+
const { api } = useSynap();
|
|
67
|
+
return api.events.list.useQuery({
|
|
68
|
+
limit: options.limit || 100,
|
|
69
|
+
type: options.eventTypes ? options.eventTypes[0] : void 0
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// src/react/usePresence.ts
|
|
74
|
+
var import_react2 = require("react");
|
|
75
|
+
function usePresence(client, viewId, viewType) {
|
|
76
|
+
const [users, setUsers] = (0, import_react2.useState)([]);
|
|
77
|
+
(0, import_react2.useEffect)(() => {
|
|
78
|
+
if (!client) return;
|
|
79
|
+
client.connectPresence(viewId, viewType);
|
|
80
|
+
client.on("presence:init", (data) => {
|
|
81
|
+
setUsers(data.users);
|
|
82
|
+
});
|
|
83
|
+
client.on("user-joined", (user) => {
|
|
84
|
+
setUsers((prev) => [...prev, user]);
|
|
85
|
+
});
|
|
86
|
+
client.on("user-left", (data) => {
|
|
87
|
+
setUsers((prev) => prev.filter((u) => u.userId !== data.userId));
|
|
88
|
+
});
|
|
89
|
+
client.on("cursor-update", (data) => {
|
|
90
|
+
setUsers((prev) => prev.map(
|
|
91
|
+
(u) => u.userId === data.userId ? { ...u, cursor: { x: data.x, y: data.y } } : u
|
|
92
|
+
));
|
|
93
|
+
});
|
|
94
|
+
return () => {
|
|
95
|
+
client.disconnect();
|
|
96
|
+
};
|
|
97
|
+
}, [client, viewId, viewType]);
|
|
98
|
+
const moveCursor = (0, import_react2.useCallback)((x, y) => {
|
|
99
|
+
client?.moveCursor(x, y);
|
|
100
|
+
}, [client]);
|
|
101
|
+
const setTyping = (0, import_react2.useCallback)((isTyping) => {
|
|
102
|
+
client?.setTyping(isTyping);
|
|
103
|
+
}, [client]);
|
|
104
|
+
return { users, moveCursor, setTyping };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// src/react/useYjs.ts
|
|
108
|
+
var import_react3 = require("react");
|
|
109
|
+
function useYjs(client, roomName) {
|
|
110
|
+
const [ydoc, setYdoc] = (0, import_react3.useState)(null);
|
|
111
|
+
const [isConnected, setIsConnected] = (0, import_react3.useState)(false);
|
|
112
|
+
(0, import_react3.useEffect)(() => {
|
|
113
|
+
if (!client) return;
|
|
114
|
+
const doc = client.connectYjs(roomName);
|
|
115
|
+
setYdoc(doc);
|
|
116
|
+
setIsConnected(true);
|
|
117
|
+
return () => {
|
|
118
|
+
client.disconnect();
|
|
119
|
+
setYdoc(null);
|
|
120
|
+
setIsConnected(false);
|
|
121
|
+
};
|
|
122
|
+
}, [client, roomName]);
|
|
123
|
+
return { ydoc, isConnected };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// src/react.ts
|
|
127
|
+
var import_react_query4 = require("@tanstack/react-query");
|
|
30
128
|
function createSynapReact() {
|
|
31
|
-
return (0,
|
|
129
|
+
return (0, import_react_query3.createTRPCReact)();
|
|
32
130
|
}
|
|
33
131
|
var react_default = createSynapReact;
|
|
34
132
|
// Annotate the CommonJS export names for ESM import in node:
|
|
35
133
|
0 && (module.exports = {
|
|
36
134
|
SynapQueryClientProvider,
|
|
37
|
-
createSynapReact
|
|
135
|
+
createSynapReact,
|
|
136
|
+
useEntities,
|
|
137
|
+
useEvents,
|
|
138
|
+
usePresence,
|
|
139
|
+
useThreads,
|
|
140
|
+
useYjs
|
|
38
141
|
});
|
package/dist/react.d.ts
CHANGED
|
@@ -71,6 +71,16 @@ export declare function createSynapReact(): ReturnType<typeof createTRPCReact<Ap
|
|
|
71
71
|
* Type alias for the Synap tRPC React context
|
|
72
72
|
*/
|
|
73
73
|
export type SynapReactContext = ReturnType<typeof createSynapReact>;
|
|
74
|
+
/**
|
|
75
|
+
* @synap/client/react - React Integration
|
|
76
|
+
*
|
|
77
|
+
* Re-exports React hooks for use in React applications.
|
|
78
|
+
*/
|
|
79
|
+
export { useEntities } from './react/useEntities.js';
|
|
80
|
+
export { useThreads } from './react/useThreads.js';
|
|
81
|
+
export { useEvents } from './react/useEvents.js';
|
|
82
|
+
export { usePresence, type UsePresenceReturn } from './react/usePresence.js';
|
|
83
|
+
export { useYjs, type UseYjsReturn } from './react/useYjs.js';
|
|
74
84
|
export { QueryClientProvider as SynapQueryClientProvider } from '@tanstack/react-query';
|
|
75
85
|
export type { AppRouter } from '@synap/api';
|
|
76
86
|
export default createSynapReact;
|
package/dist/react.js
CHANGED
|
@@ -1,5 +1,87 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useSynap
|
|
3
|
+
} from "./chunk-6MAWMLEW.js";
|
|
4
|
+
|
|
1
5
|
// src/react.ts
|
|
2
6
|
import { createTRPCReact } from "@trpc/react-query";
|
|
7
|
+
|
|
8
|
+
// src/react/useEntities.ts
|
|
9
|
+
function useEntities(options = {}) {
|
|
10
|
+
const { api } = useSynap();
|
|
11
|
+
return api.entities.list.useQuery({
|
|
12
|
+
type: options.type,
|
|
13
|
+
limit: options.limit || 50
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// src/react/useThreads.ts
|
|
18
|
+
function useThreads(_limit = 20) {
|
|
19
|
+
throw new Error("Chat router not yet implemented in API");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// src/react/useEvents.ts
|
|
23
|
+
function useEvents(options = {}) {
|
|
24
|
+
const { api } = useSynap();
|
|
25
|
+
return api.events.list.useQuery({
|
|
26
|
+
limit: options.limit || 100,
|
|
27
|
+
type: options.eventTypes ? options.eventTypes[0] : void 0
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// src/react/usePresence.ts
|
|
32
|
+
import { useState, useEffect, useCallback } from "react";
|
|
33
|
+
function usePresence(client, viewId, viewType) {
|
|
34
|
+
const [users, setUsers] = useState([]);
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
if (!client) return;
|
|
37
|
+
client.connectPresence(viewId, viewType);
|
|
38
|
+
client.on("presence:init", (data) => {
|
|
39
|
+
setUsers(data.users);
|
|
40
|
+
});
|
|
41
|
+
client.on("user-joined", (user) => {
|
|
42
|
+
setUsers((prev) => [...prev, user]);
|
|
43
|
+
});
|
|
44
|
+
client.on("user-left", (data) => {
|
|
45
|
+
setUsers((prev) => prev.filter((u) => u.userId !== data.userId));
|
|
46
|
+
});
|
|
47
|
+
client.on("cursor-update", (data) => {
|
|
48
|
+
setUsers((prev) => prev.map(
|
|
49
|
+
(u) => u.userId === data.userId ? { ...u, cursor: { x: data.x, y: data.y } } : u
|
|
50
|
+
));
|
|
51
|
+
});
|
|
52
|
+
return () => {
|
|
53
|
+
client.disconnect();
|
|
54
|
+
};
|
|
55
|
+
}, [client, viewId, viewType]);
|
|
56
|
+
const moveCursor = useCallback((x, y) => {
|
|
57
|
+
client?.moveCursor(x, y);
|
|
58
|
+
}, [client]);
|
|
59
|
+
const setTyping = useCallback((isTyping) => {
|
|
60
|
+
client?.setTyping(isTyping);
|
|
61
|
+
}, [client]);
|
|
62
|
+
return { users, moveCursor, setTyping };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// src/react/useYjs.ts
|
|
66
|
+
import { useState as useState2, useEffect as useEffect2 } from "react";
|
|
67
|
+
function useYjs(client, roomName) {
|
|
68
|
+
const [ydoc, setYdoc] = useState2(null);
|
|
69
|
+
const [isConnected, setIsConnected] = useState2(false);
|
|
70
|
+
useEffect2(() => {
|
|
71
|
+
if (!client) return;
|
|
72
|
+
const doc = client.connectYjs(roomName);
|
|
73
|
+
setYdoc(doc);
|
|
74
|
+
setIsConnected(true);
|
|
75
|
+
return () => {
|
|
76
|
+
client.disconnect();
|
|
77
|
+
setYdoc(null);
|
|
78
|
+
setIsConnected(false);
|
|
79
|
+
};
|
|
80
|
+
}, [client, roomName]);
|
|
81
|
+
return { ydoc, isConnected };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// src/react.ts
|
|
3
85
|
import { QueryClientProvider } from "@tanstack/react-query";
|
|
4
86
|
function createSynapReact() {
|
|
5
87
|
return createTRPCReact();
|
|
@@ -8,5 +90,10 @@ var react_default = createSynapReact;
|
|
|
8
90
|
export {
|
|
9
91
|
QueryClientProvider as SynapQueryClientProvider,
|
|
10
92
|
createSynapReact,
|
|
11
|
-
react_default as default
|
|
93
|
+
react_default as default,
|
|
94
|
+
useEntities,
|
|
95
|
+
useEvents,
|
|
96
|
+
usePresence,
|
|
97
|
+
useThreads,
|
|
98
|
+
useYjs
|
|
12
99
|
};
|
package/dist/realtime.cjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,103 +17,137 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// src/realtime.ts
|
|
21
31
|
var realtime_exports = {};
|
|
22
32
|
__export(realtime_exports, {
|
|
23
|
-
|
|
33
|
+
RealtimeClient: () => RealtimeClient,
|
|
34
|
+
createRealtimeClient: () => createRealtimeClient
|
|
24
35
|
});
|
|
25
36
|
module.exports = __toCommonJS(realtime_exports);
|
|
26
|
-
var
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
reconnectAttempts = 0;
|
|
30
|
-
maxReconnectAttempts;
|
|
31
|
-
reconnectDelay;
|
|
32
|
-
reconnectTimer = null;
|
|
33
|
-
isIntentionallyDisconnected = false;
|
|
37
|
+
var import_socket = require("socket.io-client");
|
|
38
|
+
var Y = __toESM(require("yjs"), 1);
|
|
39
|
+
var RealtimeClient = class {
|
|
34
40
|
constructor(config) {
|
|
35
41
|
this.config = config;
|
|
36
|
-
this.maxReconnectAttempts = config.maxReconnectAttempts ?? 5;
|
|
37
|
-
this.reconnectDelay = config.reconnectDelay ?? 1e3;
|
|
38
42
|
}
|
|
43
|
+
presenceSocket = null;
|
|
44
|
+
yjsSocket = null;
|
|
45
|
+
listeners = /* @__PURE__ */ new Map();
|
|
39
46
|
/**
|
|
40
|
-
* Connect to
|
|
47
|
+
* Connect to presence namespace for real-time collaboration
|
|
41
48
|
*/
|
|
42
|
-
|
|
43
|
-
if (this.
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
this.isIntentionallyDisconnected = false;
|
|
47
|
-
const wsUrl = this.config.url;
|
|
48
|
-
try {
|
|
49
|
-
this.ws = new WebSocket(wsUrl);
|
|
50
|
-
this.ws.onopen = () => {
|
|
51
|
-
this.reconnectAttempts = 0;
|
|
52
|
-
this.config.onConnect?.();
|
|
53
|
-
};
|
|
54
|
-
this.ws.onmessage = (event) => {
|
|
55
|
-
try {
|
|
56
|
-
const message = JSON.parse(event.data);
|
|
57
|
-
this.config.onMessage?.(message);
|
|
58
|
-
} catch (error) {
|
|
59
|
-
console.error("Failed to parse WebSocket message:", error);
|
|
60
|
-
this.config.onError?.(
|
|
61
|
-
new Error(`Failed to parse message: ${error instanceof Error ? error.message : String(error)}`)
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
};
|
|
65
|
-
this.ws.onerror = () => {
|
|
66
|
-
this.config.onError?.(new Error("WebSocket error"));
|
|
67
|
-
};
|
|
68
|
-
this.ws.onclose = () => {
|
|
69
|
-
this.config.onDisconnect?.();
|
|
70
|
-
if (!this.isIntentionallyDisconnected) {
|
|
71
|
-
this.attemptReconnect();
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
} catch (error) {
|
|
75
|
-
this.config.onError?.(
|
|
76
|
-
error instanceof Error ? error : new Error(String(error))
|
|
77
|
-
);
|
|
49
|
+
connectPresence(viewId, viewType = "document") {
|
|
50
|
+
if (this.presenceSocket) {
|
|
51
|
+
this.presenceSocket.disconnect();
|
|
78
52
|
}
|
|
53
|
+
this.presenceSocket = (0, import_socket.io)(`${this.config.url}/presence`, {
|
|
54
|
+
auth: {
|
|
55
|
+
...this.config.auth,
|
|
56
|
+
viewId,
|
|
57
|
+
viewType
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
this.presenceSocket.on("presence:init", (data) => {
|
|
61
|
+
this.emit("presence:init", data);
|
|
62
|
+
});
|
|
63
|
+
this.presenceSocket.on("user:joined", (user) => {
|
|
64
|
+
this.emit("user-joined", user);
|
|
65
|
+
});
|
|
66
|
+
this.presenceSocket.on("user:left", (data) => {
|
|
67
|
+
this.emit("user-left", data);
|
|
68
|
+
});
|
|
69
|
+
this.presenceSocket.on("cursor:update", (data) => {
|
|
70
|
+
this.emit("cursor-update", data);
|
|
71
|
+
});
|
|
72
|
+
this.presenceSocket.on("typing", (data) => {
|
|
73
|
+
this.emit("typing", data);
|
|
74
|
+
});
|
|
79
75
|
}
|
|
80
76
|
/**
|
|
81
|
-
*
|
|
77
|
+
* Connect to Yjs namespace for CRDT synchronization
|
|
78
|
+
*
|
|
79
|
+
* @returns Yjs document that auto-syncs with server
|
|
82
80
|
*/
|
|
83
|
-
|
|
84
|
-
this.
|
|
85
|
-
|
|
86
|
-
clearTimeout(this.reconnectTimer);
|
|
87
|
-
this.reconnectTimer = null;
|
|
81
|
+
connectYjs(roomName) {
|
|
82
|
+
if (this.yjsSocket) {
|
|
83
|
+
this.yjsSocket.disconnect();
|
|
88
84
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
85
|
+
const ydoc = new Y.Doc();
|
|
86
|
+
this.yjsSocket = (0, import_socket.io)(`${this.config.url}/yjs`, {
|
|
87
|
+
query: { room: roomName }
|
|
88
|
+
});
|
|
89
|
+
this.yjsSocket.on("yjs:sync", (message) => {
|
|
90
|
+
Y.applyUpdate(ydoc, Uint8Array.from(message));
|
|
91
|
+
});
|
|
92
|
+
this.yjsSocket.on("yjs:update", (message) => {
|
|
93
|
+
Y.applyUpdate(ydoc, Uint8Array.from(message), this.yjsSocket);
|
|
94
|
+
});
|
|
95
|
+
ydoc.on("update", (update, origin) => {
|
|
96
|
+
if (origin !== this.yjsSocket) {
|
|
97
|
+
this.yjsSocket?.emit("yjs:update", Array.from(update));
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
return ydoc;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Subscribe to events
|
|
104
|
+
*/
|
|
105
|
+
on(event, callback) {
|
|
106
|
+
if (!this.listeners.has(event)) {
|
|
107
|
+
this.listeners.set(event, /* @__PURE__ */ new Set());
|
|
92
108
|
}
|
|
109
|
+
this.listeners.get(event).add(callback);
|
|
93
110
|
}
|
|
94
111
|
/**
|
|
95
|
-
*
|
|
112
|
+
* Unsubscribe from events
|
|
96
113
|
*/
|
|
97
|
-
|
|
98
|
-
|
|
114
|
+
off(event, callback) {
|
|
115
|
+
this.listeners.get(event)?.delete(callback);
|
|
99
116
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
117
|
+
/**
|
|
118
|
+
* Emit event to all listeners
|
|
119
|
+
*/
|
|
120
|
+
emit(event, data) {
|
|
121
|
+
this.listeners.get(event)?.forEach((cb) => cb(data));
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Move cursor (presence)
|
|
125
|
+
*/
|
|
126
|
+
moveCursor(x, y) {
|
|
127
|
+
this.presenceSocket?.emit("cursor:move", { x, y });
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Set typing indicator
|
|
131
|
+
*/
|
|
132
|
+
setTyping(isTyping) {
|
|
133
|
+
this.presenceSocket?.emit("typing", isTyping);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Disconnect all connections
|
|
137
|
+
*/
|
|
138
|
+
disconnect() {
|
|
139
|
+
this.presenceSocket?.disconnect();
|
|
140
|
+
this.yjsSocket?.disconnect();
|
|
141
|
+
this.presenceSocket = null;
|
|
142
|
+
this.yjsSocket = null;
|
|
143
|
+
this.listeners.clear();
|
|
112
144
|
}
|
|
113
145
|
};
|
|
146
|
+
function createRealtimeClient(config) {
|
|
147
|
+
return new RealtimeClient(config);
|
|
148
|
+
}
|
|
114
149
|
// Annotate the CommonJS export names for ESM import in node:
|
|
115
150
|
0 && (module.exports = {
|
|
116
|
-
|
|
151
|
+
RealtimeClient,
|
|
152
|
+
createRealtimeClient
|
|
117
153
|
});
|