@coderegtech/jsonify-ws 1.0.5
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 +226 -0
- package/dist/index.d.mts +211 -0
- package/dist/index.d.ts +211 -0
- package/dist/index.js +542 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +505 -0
- package/dist/index.mjs.map +1 -0
- package/dist/server.d.mts +12 -0
- package/dist/server.d.ts +12 -0
- package/dist/server.js +65 -0
- package/dist/server.js.map +1 -0
- package/dist/server.mjs +47 -0
- package/dist/server.mjs.map +1 -0
- package/package.json +65 -0
package/README.md
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# jsonify-ws
|
|
2
|
+
|
|
3
|
+
Real-time JSON synchronization over WebSocket using Socket.IO. Provides React hooks for the client, a `data-copy` attribute system for inline content editing, and a ready-to-run sync server.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install jsonify-ws socket.io-client
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Environment Variable
|
|
12
|
+
|
|
13
|
+
Set `WSL_URL` to your WebSocket server URL:
|
|
14
|
+
|
|
15
|
+
```env
|
|
16
|
+
# .env (Vite)
|
|
17
|
+
VITE_WSL_URL=http://localhost:4000
|
|
18
|
+
|
|
19
|
+
# .env (CRA / Next.js)
|
|
20
|
+
REACT_APP_WSL_URL=http://localhost:4000
|
|
21
|
+
|
|
22
|
+
# Or set directly
|
|
23
|
+
WSL_URL=http://localhost:4000
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
> If not set, defaults to `http://localhost:4000`.
|
|
27
|
+
|
|
28
|
+
## Quick Start
|
|
29
|
+
|
|
30
|
+
### 1. Start the server
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# Option A: CLI
|
|
34
|
+
npx jsonify-ws-server
|
|
35
|
+
|
|
36
|
+
# Option B: Programmatic
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import { createJsonifyServer } from "jsonify-ws/server";
|
|
41
|
+
|
|
42
|
+
createJsonifyServer(4000); // port, cors origin
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### 2. Use the `useJsonify()` hook
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
import { useJsonify } from "jsonify-ws";
|
|
49
|
+
|
|
50
|
+
function App() {
|
|
51
|
+
const j = useJsonify({
|
|
52
|
+
autoConnect: true,
|
|
53
|
+
initialData: { home: { title: "Hello World", subtitle: "Edit me!" } },
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<div>
|
|
58
|
+
<p>Status: {j.status}</p>
|
|
59
|
+
<button onClick={j.toggleEditMode}>
|
|
60
|
+
{j.editMode ? "✅ Editing" : "✏️ Edit Mode"}
|
|
61
|
+
</button>
|
|
62
|
+
<h1 data-copy="home.title">{j.data.home?.title}</h1>
|
|
63
|
+
<p data-copy="home.subtitle">{j.data.home?.subtitle}</p>
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## API
|
|
70
|
+
|
|
71
|
+
### `useJsonify(options?)`
|
|
72
|
+
|
|
73
|
+
The primary hook — combines WebSocket sync with `data-copy` attribute scanning and contentEditable.
|
|
74
|
+
|
|
75
|
+
| Option | Type | Default | Description |
|
|
76
|
+
| --------------------- | ----------------------------- | ---------------------- | ---------------------------------------- |
|
|
77
|
+
| `url` | `string` | `WSL_URL` env | WebSocket server URL |
|
|
78
|
+
| `autoConnect` | `boolean` | `false` | Connect on mount |
|
|
79
|
+
| `initialData` | `Record<string, JsonValue>` | `{}` | Initial JSON state |
|
|
80
|
+
| `reconnectionDelay` | `number` | `3000` | Reconnection delay (ms) |
|
|
81
|
+
| `onSync` | `(data) => void` | — | Callback on remote data received |
|
|
82
|
+
| `onStatusChange` | `(status) => void` | — | Callback on connection status change |
|
|
83
|
+
| `onError` | `(error) => void` | — | Callback on connection error |
|
|
84
|
+
| `targetDocument` | `Document` | `window.document` | Target document for data-copy scan |
|
|
85
|
+
| `injectToggle` | `boolean` | `true` | Auto-inject floating edit button |
|
|
86
|
+
| `injectStatusIndicator`| `boolean` | `false` | Auto-inject WS status indicator (bottom-left) |
|
|
87
|
+
|
|
88
|
+
**Returns:**
|
|
89
|
+
|
|
90
|
+
| Property | Type | Description |
|
|
91
|
+
| ---------------- | -------------------------------- | ------------------------------ |
|
|
92
|
+
| `data` | `Record<string, JsonValue>` | Current synced JSON data |
|
|
93
|
+
| `setData` | `(data) => void` | Update all data (broadcasts) |
|
|
94
|
+
| `setPath` | `(path, value) => void` | Update a single dot-path |
|
|
95
|
+
| `status` | `WsStatus` | Connection status |
|
|
96
|
+
| `connect` | `(url?) => void` | Connect to server |
|
|
97
|
+
| `disconnect` | `() => void` | Disconnect from server |
|
|
98
|
+
| `url` | `string` | Resolved WebSocket URL |
|
|
99
|
+
| `editMode` | `boolean` | Whether edit mode is active |
|
|
100
|
+
| `toggleEditMode` | `() => void` | Toggle edit mode on/off |
|
|
101
|
+
| `setEditMode` | `(active) => void` | Set edit mode explicitly |
|
|
102
|
+
| `elements` | `DataCopyElement[]` | Scanned data-copy elements |
|
|
103
|
+
| `rescan` | `() => void` | Re-scan for data-copy elements |
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
### `useJsonifyWs(options?)`
|
|
108
|
+
|
|
109
|
+
Lower-level hook — WebSocket sync only (no data-copy support).
|
|
110
|
+
|
|
111
|
+
| Option | Type | Default | Description |
|
|
112
|
+
| ------------------ | ----------------------------- | ---------------------- | ------------------------------------ |
|
|
113
|
+
| `url` | `string` | `WSL_URL` env | WebSocket server URL |
|
|
114
|
+
| `autoConnect` | `boolean` | `false` | Connect on mount |
|
|
115
|
+
| `initialData` | `JsonValue` | `{}` | Initial JSON state |
|
|
116
|
+
| `reconnectionDelay`| `number` | `3000` | Reconnection delay (ms) |
|
|
117
|
+
| `onSync` | `(data: JsonValue) => void` | — | Callback on remote data received |
|
|
118
|
+
| `onStatusChange` | `(status: WsStatus) => void` | — | Callback on connection status change |
|
|
119
|
+
| `onError` | `(error: Error) => void` | — | Callback on connection error |
|
|
120
|
+
|
|
121
|
+
**Returns:**
|
|
122
|
+
|
|
123
|
+
| Property | Type | Description |
|
|
124
|
+
| ------------ | ----------------------------- | ------------------------------ |
|
|
125
|
+
| `data` | `JsonValue` | Current synced JSON data |
|
|
126
|
+
| `setData` | `(data: JsonValue) => void` | Update data (broadcasts) |
|
|
127
|
+
| `status` | `WsStatus` | `"disconnected" \| "connecting" \| "connected"` |
|
|
128
|
+
| `connect` | `(url?: string) => void` | Connect to server |
|
|
129
|
+
| `disconnect` | `() => void` | Disconnect from server |
|
|
130
|
+
| `url` | `string` | Resolved WebSocket URL |
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
### `createJsonifyServer(port?, corsOrigin?)`
|
|
135
|
+
|
|
136
|
+
Creates a Socket.IO server that broadcasts JSON updates between clients.
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
import { createJsonifyServer } from "jsonify-ws/server";
|
|
140
|
+
|
|
141
|
+
const io = createJsonifyServer(4000, "*");
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## `data-copy` Attribute System
|
|
147
|
+
|
|
148
|
+
Add `data-copy` attributes to any HTML element to map it to a JSON path:
|
|
149
|
+
|
|
150
|
+
```html
|
|
151
|
+
<h1 data-copy="home.hero.title">Welcome</h1>
|
|
152
|
+
<p data-copy="home.hero.subtitle">This is editable</p>
|
|
153
|
+
<span data-copy="footer.copyright">© 2026</span>
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
When **Edit Mode** is activated:
|
|
157
|
+
1. Elements with `data-copy` get a dashed outline and become `contentEditable`
|
|
158
|
+
2. Text changes are synced to the JSON data in real-time
|
|
159
|
+
3. JSON changes (from other clients) update the element text automatically
|
|
160
|
+
4. A floating "✏️ Edit Mode" button appears (configurable via `injectToggle`)
|
|
161
|
+
|
|
162
|
+
### WebSocket Status Indicator
|
|
163
|
+
|
|
164
|
+
Enable `injectStatusIndicator` to show a real-time connection status badge:
|
|
165
|
+
|
|
166
|
+
```tsx
|
|
167
|
+
const j = useJsonify({
|
|
168
|
+
autoConnect: true,
|
|
169
|
+
injectStatusIndicator: true, // Shows status in bottom-left corner
|
|
170
|
+
});
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
The indicator displays:
|
|
174
|
+
- 🔴 **Red** — Disconnected
|
|
175
|
+
- 🟡 **Yellow (pulsing)** — Connecting
|
|
176
|
+
- 🟢 **Green** — Connected
|
|
177
|
+
|
|
178
|
+
### Standalone utilities
|
|
179
|
+
|
|
180
|
+
```ts
|
|
181
|
+
import {
|
|
182
|
+
scanDataCopyElements,
|
|
183
|
+
enableEditMode,
|
|
184
|
+
disableEditMode,
|
|
185
|
+
syncElementsFromData,
|
|
186
|
+
getByPath,
|
|
187
|
+
setByPath,
|
|
188
|
+
injectEditToggle,
|
|
189
|
+
injectWsStatusIndicator,
|
|
190
|
+
} from "jsonify-ws";
|
|
191
|
+
|
|
192
|
+
// Scan for elements
|
|
193
|
+
const elements = scanDataCopyElements(document);
|
|
194
|
+
|
|
195
|
+
// Enable editing
|
|
196
|
+
enableEditMode(elements);
|
|
197
|
+
|
|
198
|
+
// Sync JSON data to elements
|
|
199
|
+
syncElementsFromData(elements, { home: { title: "Updated!" } });
|
|
200
|
+
|
|
201
|
+
// Path utilities
|
|
202
|
+
const val = getByPath({ a: { b: "hello" } }, "a.b"); // "hello"
|
|
203
|
+
const updated = setByPath({ a: { b: "hello" } }, "a.b", "world"); // { a: { b: "world" } }
|
|
204
|
+
|
|
205
|
+
// Inject floating edit toggle button
|
|
206
|
+
const cleanupToggle = injectEditToggle(document, (active) => {
|
|
207
|
+
console.log("Edit mode:", active);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// Inject WebSocket status indicator
|
|
211
|
+
const { cleanup, updateStatus } = injectWsStatusIndicator(document);
|
|
212
|
+
updateStatus("connected"); // "disconnected" | "connecting" | "connected"
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## How It Works
|
|
216
|
+
|
|
217
|
+
1. Client connects and pushes its current JSON state
|
|
218
|
+
2. Server stores latest state and syncs to all other clients
|
|
219
|
+
3. Any `setData()` or `setPath()` call broadcasts the update to all peers
|
|
220
|
+
4. New clients receive the latest state on connect
|
|
221
|
+
5. `data-copy` elements are scanned and mapped to JSON paths
|
|
222
|
+
6. In edit mode, contentEditable changes are captured and synced in real-time
|
|
223
|
+
|
|
224
|
+
## License
|
|
225
|
+
|
|
226
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
2
|
+
[key: string]: JsonValue;
|
|
3
|
+
};
|
|
4
|
+
type WsStatus = "disconnected" | "connecting" | "connected";
|
|
5
|
+
interface JsonifyWsOptions {
|
|
6
|
+
/**
|
|
7
|
+
* WebSocket server URL. Defaults to WSL_URL env variable.
|
|
8
|
+
* Falls back to "http://localhost:4000" if not set.
|
|
9
|
+
*/
|
|
10
|
+
url?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Auto-connect on mount. Default: false
|
|
13
|
+
*/
|
|
14
|
+
autoConnect?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Initial JSON data to sync. Default: {}
|
|
17
|
+
*/
|
|
18
|
+
initialData?: JsonValue;
|
|
19
|
+
/**
|
|
20
|
+
* Reconnection delay in ms. Default: 3000
|
|
21
|
+
*/
|
|
22
|
+
reconnectionDelay?: number;
|
|
23
|
+
/**
|
|
24
|
+
* Callback fired when remote data is received.
|
|
25
|
+
*/
|
|
26
|
+
onSync?: (data: JsonValue) => void;
|
|
27
|
+
/**
|
|
28
|
+
* Callback fired when connection status changes.
|
|
29
|
+
*/
|
|
30
|
+
onStatusChange?: (status: WsStatus) => void;
|
|
31
|
+
/**
|
|
32
|
+
* Callback fired on connection error.
|
|
33
|
+
*/
|
|
34
|
+
onError?: (error: Error) => void;
|
|
35
|
+
}
|
|
36
|
+
interface JsonifyWsReturn {
|
|
37
|
+
/** Current JSON data state */
|
|
38
|
+
data: JsonValue;
|
|
39
|
+
/** Update the JSON data (will broadcast to peers) */
|
|
40
|
+
setData: (data: JsonValue) => void;
|
|
41
|
+
/** Current connection status */
|
|
42
|
+
status: WsStatus;
|
|
43
|
+
/** Connect to the WebSocket server */
|
|
44
|
+
connect: (url?: string) => void;
|
|
45
|
+
/** Disconnect from the WebSocket server */
|
|
46
|
+
disconnect: () => void;
|
|
47
|
+
/** The resolved WebSocket URL */
|
|
48
|
+
url: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* React hook for real-time JSON synchronization over WebSocket.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```tsx
|
|
56
|
+
* import { useJsonifyWs } from "jsonify-ws";
|
|
57
|
+
*
|
|
58
|
+
* function App() {
|
|
59
|
+
* const { data, setData, status, connect, disconnect } = useJsonifyWs({
|
|
60
|
+
* autoConnect: true,
|
|
61
|
+
* initialData: { hello: "world" },
|
|
62
|
+
* });
|
|
63
|
+
*
|
|
64
|
+
* return <pre>{JSON.stringify(data, null, 2)}</pre>;
|
|
65
|
+
* }
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
declare function useJsonifyWs(options?: JsonifyWsOptions): JsonifyWsReturn;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* data-copy attribute utilities.
|
|
72
|
+
*
|
|
73
|
+
* Scans a document (or iframe) for elements with `data-copy="path.to.key"`
|
|
74
|
+
* and enables contentEditable on them, syncing edits back to the JSON state.
|
|
75
|
+
*/
|
|
76
|
+
|
|
77
|
+
interface DataCopyElement {
|
|
78
|
+
element: HTMLElement;
|
|
79
|
+
path: string;
|
|
80
|
+
originalValue: string;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Get a value from a nested object by dot-path.
|
|
84
|
+
* e.g. getByPath({ home: { title: "Hi" } }, "home.title") => "Hi"
|
|
85
|
+
*/
|
|
86
|
+
declare function getByPath(obj: Record<string, JsonValue>, path: string): JsonValue | undefined;
|
|
87
|
+
/**
|
|
88
|
+
* Set a value in a nested object by dot-path (immutable — returns new object).
|
|
89
|
+
*/
|
|
90
|
+
declare function setByPath(obj: Record<string, JsonValue>, path: string, value: JsonValue): Record<string, JsonValue>;
|
|
91
|
+
/**
|
|
92
|
+
* Scan a document for elements with `data-copy` attribute.
|
|
93
|
+
*/
|
|
94
|
+
declare function scanDataCopyElements(doc: Document): DataCopyElement[];
|
|
95
|
+
/**
|
|
96
|
+
* Apply contentEditable styling to data-copy elements.
|
|
97
|
+
*/
|
|
98
|
+
declare function enableEditMode(elements: DataCopyElement[]): void;
|
|
99
|
+
/**
|
|
100
|
+
* Remove contentEditable styling from data-copy elements.
|
|
101
|
+
*/
|
|
102
|
+
declare function disableEditMode(elements: DataCopyElement[]): void;
|
|
103
|
+
/**
|
|
104
|
+
* Update element text content from JSON data.
|
|
105
|
+
*/
|
|
106
|
+
declare function syncElementsFromData(elements: DataCopyElement[], data: Record<string, JsonValue>): void;
|
|
107
|
+
/**
|
|
108
|
+
* Inject a floating "Edit Mode" toggle button into the page.
|
|
109
|
+
* Returns a cleanup function.
|
|
110
|
+
*/
|
|
111
|
+
declare function injectEditToggle(doc: Document, onToggle: (active: boolean) => void): () => void;
|
|
112
|
+
|
|
113
|
+
interface UseJsonifyOptions {
|
|
114
|
+
/**
|
|
115
|
+
* WebSocket server URL. Defaults to WSL_URL env variable.
|
|
116
|
+
* Falls back to "http://localhost:4000" if not set.
|
|
117
|
+
*/
|
|
118
|
+
url?: string;
|
|
119
|
+
/**
|
|
120
|
+
* Auto-connect on mount. Default: false
|
|
121
|
+
*/
|
|
122
|
+
autoConnect?: boolean;
|
|
123
|
+
/**
|
|
124
|
+
* Initial JSON data. Default: {}
|
|
125
|
+
*/
|
|
126
|
+
initialData?: Record<string, JsonValue>;
|
|
127
|
+
/**
|
|
128
|
+
* Reconnection delay in ms. Default: 3000
|
|
129
|
+
*/
|
|
130
|
+
reconnectionDelay?: number;
|
|
131
|
+
/**
|
|
132
|
+
* Callback fired when remote data is received.
|
|
133
|
+
*/
|
|
134
|
+
onSync?: (data: Record<string, JsonValue>) => void;
|
|
135
|
+
/**
|
|
136
|
+
* Callback fired when connection status changes.
|
|
137
|
+
*/
|
|
138
|
+
onStatusChange?: (status: WsStatus) => void;
|
|
139
|
+
/**
|
|
140
|
+
* Callback fired on connection error.
|
|
141
|
+
*/
|
|
142
|
+
onError?: (error: Error) => void;
|
|
143
|
+
/**
|
|
144
|
+
* Target document for data-copy scanning.
|
|
145
|
+
* Defaults to window.document.
|
|
146
|
+
* Pass an iframe's contentDocument to scan an iframe.
|
|
147
|
+
*/
|
|
148
|
+
targetDocument?: Document;
|
|
149
|
+
/**
|
|
150
|
+
* Auto-inject the floating edit toggle button. Default: true
|
|
151
|
+
*/
|
|
152
|
+
injectToggle?: boolean;
|
|
153
|
+
/**
|
|
154
|
+
* Auto-inject the WebSocket connection status indicator. Default: false
|
|
155
|
+
*/
|
|
156
|
+
injectStatusIndicator?: boolean;
|
|
157
|
+
}
|
|
158
|
+
interface UseJsonifyReturn {
|
|
159
|
+
/** Current JSON data state */
|
|
160
|
+
data: Record<string, JsonValue>;
|
|
161
|
+
/** Update the JSON data (will broadcast to peers) */
|
|
162
|
+
setData: (data: Record<string, JsonValue>) => void;
|
|
163
|
+
/** Update a single path in the data */
|
|
164
|
+
setPath: (path: string, value: JsonValue) => void;
|
|
165
|
+
/** Current connection status */
|
|
166
|
+
status: WsStatus;
|
|
167
|
+
/** Connect to the WebSocket server */
|
|
168
|
+
connect: (url?: string) => void;
|
|
169
|
+
/** Disconnect from the WebSocket server */
|
|
170
|
+
disconnect: () => void;
|
|
171
|
+
/** The resolved WebSocket URL */
|
|
172
|
+
url: string;
|
|
173
|
+
/** Whether edit mode is active */
|
|
174
|
+
editMode: boolean;
|
|
175
|
+
/** Toggle edit mode on/off */
|
|
176
|
+
toggleEditMode: () => void;
|
|
177
|
+
/** Set edit mode explicitly */
|
|
178
|
+
setEditMode: (active: boolean) => void;
|
|
179
|
+
/** Scanned data-copy elements */
|
|
180
|
+
elements: DataCopyElement[];
|
|
181
|
+
/** Re-scan the document for data-copy elements */
|
|
182
|
+
rescan: () => void;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* React hook for real-time JSON synchronization with data-copy attribute support.
|
|
186
|
+
*
|
|
187
|
+
* @example
|
|
188
|
+
* ```tsx
|
|
189
|
+
* import { useJsonify } from "jsonify-ws";
|
|
190
|
+
*
|
|
191
|
+
* function App() {
|
|
192
|
+
* const j = useJsonify({
|
|
193
|
+
* autoConnect: true,
|
|
194
|
+
* initialData: { home: { title: "Hello" } },
|
|
195
|
+
* });
|
|
196
|
+
*
|
|
197
|
+
* return (
|
|
198
|
+
* <div>
|
|
199
|
+
* <p>Status: {j.status}</p>
|
|
200
|
+
* <button onClick={j.toggleEditMode}>
|
|
201
|
+
* {j.editMode ? "Stop Editing" : "Edit Mode"}
|
|
202
|
+
* </button>
|
|
203
|
+
* <h1 data-copy="home.title">{j.data.home?.title}</h1>
|
|
204
|
+
* </div>
|
|
205
|
+
* );
|
|
206
|
+
* }
|
|
207
|
+
* ```
|
|
208
|
+
*/
|
|
209
|
+
declare function useJsonify(options?: UseJsonifyOptions): UseJsonifyReturn;
|
|
210
|
+
|
|
211
|
+
export { type DataCopyElement, type JsonValue, type JsonifyWsOptions, type JsonifyWsReturn, type UseJsonifyOptions, type UseJsonifyReturn, type WsStatus, disableEditMode, enableEditMode, getByPath, injectEditToggle, scanDataCopyElements, setByPath, syncElementsFromData, useJsonify, useJsonifyWs };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
2
|
+
[key: string]: JsonValue;
|
|
3
|
+
};
|
|
4
|
+
type WsStatus = "disconnected" | "connecting" | "connected";
|
|
5
|
+
interface JsonifyWsOptions {
|
|
6
|
+
/**
|
|
7
|
+
* WebSocket server URL. Defaults to WSL_URL env variable.
|
|
8
|
+
* Falls back to "http://localhost:4000" if not set.
|
|
9
|
+
*/
|
|
10
|
+
url?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Auto-connect on mount. Default: false
|
|
13
|
+
*/
|
|
14
|
+
autoConnect?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Initial JSON data to sync. Default: {}
|
|
17
|
+
*/
|
|
18
|
+
initialData?: JsonValue;
|
|
19
|
+
/**
|
|
20
|
+
* Reconnection delay in ms. Default: 3000
|
|
21
|
+
*/
|
|
22
|
+
reconnectionDelay?: number;
|
|
23
|
+
/**
|
|
24
|
+
* Callback fired when remote data is received.
|
|
25
|
+
*/
|
|
26
|
+
onSync?: (data: JsonValue) => void;
|
|
27
|
+
/**
|
|
28
|
+
* Callback fired when connection status changes.
|
|
29
|
+
*/
|
|
30
|
+
onStatusChange?: (status: WsStatus) => void;
|
|
31
|
+
/**
|
|
32
|
+
* Callback fired on connection error.
|
|
33
|
+
*/
|
|
34
|
+
onError?: (error: Error) => void;
|
|
35
|
+
}
|
|
36
|
+
interface JsonifyWsReturn {
|
|
37
|
+
/** Current JSON data state */
|
|
38
|
+
data: JsonValue;
|
|
39
|
+
/** Update the JSON data (will broadcast to peers) */
|
|
40
|
+
setData: (data: JsonValue) => void;
|
|
41
|
+
/** Current connection status */
|
|
42
|
+
status: WsStatus;
|
|
43
|
+
/** Connect to the WebSocket server */
|
|
44
|
+
connect: (url?: string) => void;
|
|
45
|
+
/** Disconnect from the WebSocket server */
|
|
46
|
+
disconnect: () => void;
|
|
47
|
+
/** The resolved WebSocket URL */
|
|
48
|
+
url: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* React hook for real-time JSON synchronization over WebSocket.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```tsx
|
|
56
|
+
* import { useJsonifyWs } from "jsonify-ws";
|
|
57
|
+
*
|
|
58
|
+
* function App() {
|
|
59
|
+
* const { data, setData, status, connect, disconnect } = useJsonifyWs({
|
|
60
|
+
* autoConnect: true,
|
|
61
|
+
* initialData: { hello: "world" },
|
|
62
|
+
* });
|
|
63
|
+
*
|
|
64
|
+
* return <pre>{JSON.stringify(data, null, 2)}</pre>;
|
|
65
|
+
* }
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
declare function useJsonifyWs(options?: JsonifyWsOptions): JsonifyWsReturn;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* data-copy attribute utilities.
|
|
72
|
+
*
|
|
73
|
+
* Scans a document (or iframe) for elements with `data-copy="path.to.key"`
|
|
74
|
+
* and enables contentEditable on them, syncing edits back to the JSON state.
|
|
75
|
+
*/
|
|
76
|
+
|
|
77
|
+
interface DataCopyElement {
|
|
78
|
+
element: HTMLElement;
|
|
79
|
+
path: string;
|
|
80
|
+
originalValue: string;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Get a value from a nested object by dot-path.
|
|
84
|
+
* e.g. getByPath({ home: { title: "Hi" } }, "home.title") => "Hi"
|
|
85
|
+
*/
|
|
86
|
+
declare function getByPath(obj: Record<string, JsonValue>, path: string): JsonValue | undefined;
|
|
87
|
+
/**
|
|
88
|
+
* Set a value in a nested object by dot-path (immutable — returns new object).
|
|
89
|
+
*/
|
|
90
|
+
declare function setByPath(obj: Record<string, JsonValue>, path: string, value: JsonValue): Record<string, JsonValue>;
|
|
91
|
+
/**
|
|
92
|
+
* Scan a document for elements with `data-copy` attribute.
|
|
93
|
+
*/
|
|
94
|
+
declare function scanDataCopyElements(doc: Document): DataCopyElement[];
|
|
95
|
+
/**
|
|
96
|
+
* Apply contentEditable styling to data-copy elements.
|
|
97
|
+
*/
|
|
98
|
+
declare function enableEditMode(elements: DataCopyElement[]): void;
|
|
99
|
+
/**
|
|
100
|
+
* Remove contentEditable styling from data-copy elements.
|
|
101
|
+
*/
|
|
102
|
+
declare function disableEditMode(elements: DataCopyElement[]): void;
|
|
103
|
+
/**
|
|
104
|
+
* Update element text content from JSON data.
|
|
105
|
+
*/
|
|
106
|
+
declare function syncElementsFromData(elements: DataCopyElement[], data: Record<string, JsonValue>): void;
|
|
107
|
+
/**
|
|
108
|
+
* Inject a floating "Edit Mode" toggle button into the page.
|
|
109
|
+
* Returns a cleanup function.
|
|
110
|
+
*/
|
|
111
|
+
declare function injectEditToggle(doc: Document, onToggle: (active: boolean) => void): () => void;
|
|
112
|
+
|
|
113
|
+
interface UseJsonifyOptions {
|
|
114
|
+
/**
|
|
115
|
+
* WebSocket server URL. Defaults to WSL_URL env variable.
|
|
116
|
+
* Falls back to "http://localhost:4000" if not set.
|
|
117
|
+
*/
|
|
118
|
+
url?: string;
|
|
119
|
+
/**
|
|
120
|
+
* Auto-connect on mount. Default: false
|
|
121
|
+
*/
|
|
122
|
+
autoConnect?: boolean;
|
|
123
|
+
/**
|
|
124
|
+
* Initial JSON data. Default: {}
|
|
125
|
+
*/
|
|
126
|
+
initialData?: Record<string, JsonValue>;
|
|
127
|
+
/**
|
|
128
|
+
* Reconnection delay in ms. Default: 3000
|
|
129
|
+
*/
|
|
130
|
+
reconnectionDelay?: number;
|
|
131
|
+
/**
|
|
132
|
+
* Callback fired when remote data is received.
|
|
133
|
+
*/
|
|
134
|
+
onSync?: (data: Record<string, JsonValue>) => void;
|
|
135
|
+
/**
|
|
136
|
+
* Callback fired when connection status changes.
|
|
137
|
+
*/
|
|
138
|
+
onStatusChange?: (status: WsStatus) => void;
|
|
139
|
+
/**
|
|
140
|
+
* Callback fired on connection error.
|
|
141
|
+
*/
|
|
142
|
+
onError?: (error: Error) => void;
|
|
143
|
+
/**
|
|
144
|
+
* Target document for data-copy scanning.
|
|
145
|
+
* Defaults to window.document.
|
|
146
|
+
* Pass an iframe's contentDocument to scan an iframe.
|
|
147
|
+
*/
|
|
148
|
+
targetDocument?: Document;
|
|
149
|
+
/**
|
|
150
|
+
* Auto-inject the floating edit toggle button. Default: true
|
|
151
|
+
*/
|
|
152
|
+
injectToggle?: boolean;
|
|
153
|
+
/**
|
|
154
|
+
* Auto-inject the WebSocket connection status indicator. Default: false
|
|
155
|
+
*/
|
|
156
|
+
injectStatusIndicator?: boolean;
|
|
157
|
+
}
|
|
158
|
+
interface UseJsonifyReturn {
|
|
159
|
+
/** Current JSON data state */
|
|
160
|
+
data: Record<string, JsonValue>;
|
|
161
|
+
/** Update the JSON data (will broadcast to peers) */
|
|
162
|
+
setData: (data: Record<string, JsonValue>) => void;
|
|
163
|
+
/** Update a single path in the data */
|
|
164
|
+
setPath: (path: string, value: JsonValue) => void;
|
|
165
|
+
/** Current connection status */
|
|
166
|
+
status: WsStatus;
|
|
167
|
+
/** Connect to the WebSocket server */
|
|
168
|
+
connect: (url?: string) => void;
|
|
169
|
+
/** Disconnect from the WebSocket server */
|
|
170
|
+
disconnect: () => void;
|
|
171
|
+
/** The resolved WebSocket URL */
|
|
172
|
+
url: string;
|
|
173
|
+
/** Whether edit mode is active */
|
|
174
|
+
editMode: boolean;
|
|
175
|
+
/** Toggle edit mode on/off */
|
|
176
|
+
toggleEditMode: () => void;
|
|
177
|
+
/** Set edit mode explicitly */
|
|
178
|
+
setEditMode: (active: boolean) => void;
|
|
179
|
+
/** Scanned data-copy elements */
|
|
180
|
+
elements: DataCopyElement[];
|
|
181
|
+
/** Re-scan the document for data-copy elements */
|
|
182
|
+
rescan: () => void;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* React hook for real-time JSON synchronization with data-copy attribute support.
|
|
186
|
+
*
|
|
187
|
+
* @example
|
|
188
|
+
* ```tsx
|
|
189
|
+
* import { useJsonify } from "jsonify-ws";
|
|
190
|
+
*
|
|
191
|
+
* function App() {
|
|
192
|
+
* const j = useJsonify({
|
|
193
|
+
* autoConnect: true,
|
|
194
|
+
* initialData: { home: { title: "Hello" } },
|
|
195
|
+
* });
|
|
196
|
+
*
|
|
197
|
+
* return (
|
|
198
|
+
* <div>
|
|
199
|
+
* <p>Status: {j.status}</p>
|
|
200
|
+
* <button onClick={j.toggleEditMode}>
|
|
201
|
+
* {j.editMode ? "Stop Editing" : "Edit Mode"}
|
|
202
|
+
* </button>
|
|
203
|
+
* <h1 data-copy="home.title">{j.data.home?.title}</h1>
|
|
204
|
+
* </div>
|
|
205
|
+
* );
|
|
206
|
+
* }
|
|
207
|
+
* ```
|
|
208
|
+
*/
|
|
209
|
+
declare function useJsonify(options?: UseJsonifyOptions): UseJsonifyReturn;
|
|
210
|
+
|
|
211
|
+
export { type DataCopyElement, type JsonValue, type JsonifyWsOptions, type JsonifyWsReturn, type UseJsonifyOptions, type UseJsonifyReturn, type WsStatus, disableEditMode, enableEditMode, getByPath, injectEditToggle, scanDataCopyElements, setByPath, syncElementsFromData, useJsonify, useJsonifyWs };
|