live-cursors 0.1.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/LICENSE +21 -0
- package/README.md +210 -0
- package/dist/broadcast.d.mts +13 -0
- package/dist/broadcast.mjs +24 -0
- package/dist/color-CN1S_Vq9.mjs +27 -0
- package/dist/cursors-BhKZnRWg.mjs +348 -0
- package/dist/cursors-CyiNtLCv.d.mts +107 -0
- package/dist/dom.d.mts +20 -0
- package/dist/dom.mjs +97 -0
- package/dist/echo.d.mts +26 -0
- package/dist/echo.mjs +24 -0
- package/dist/index-4O7Ji1vk.d.mts +40 -0
- package/dist/index.d.mts +3 -0
- package/dist/index.mjs +3 -0
- package/dist/react.d.mts +26 -0
- package/dist/react.mjs +91 -0
- package/dist/vue.d.mts +60 -0
- package/dist/vue.mjs +144 -0
- package/dist/websocket.d.mts +20 -0
- package/dist/websocket.mjs +75 -0
- package/package.json +91 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
//#region src/transports/websocket.ts
|
|
2
|
+
/**
|
|
3
|
+
* Raw WebSocket transport. Cursor updates are disposable, so nothing is buffered
|
|
4
|
+
* while the socket is down — the next pointer move takes over.
|
|
5
|
+
*/
|
|
6
|
+
function websocketTransport(options) {
|
|
7
|
+
const { url, room, protocols, reconnect = true, reconnectDelayMs = 500, maxDelayMs = 1e4, onError } = options;
|
|
8
|
+
const handlers = /* @__PURE__ */ new Set();
|
|
9
|
+
let socket = null;
|
|
10
|
+
let delay = reconnectDelayMs;
|
|
11
|
+
let retryTimer = null;
|
|
12
|
+
let closed = false;
|
|
13
|
+
function scheduleRetry() {
|
|
14
|
+
if (!reconnect || closed || retryTimer) return;
|
|
15
|
+
retryTimer = setTimeout(() => {
|
|
16
|
+
retryTimer = null;
|
|
17
|
+
delay = Math.min(delay * 2, maxDelayMs);
|
|
18
|
+
connect();
|
|
19
|
+
}, delay);
|
|
20
|
+
}
|
|
21
|
+
function connect() {
|
|
22
|
+
if (closed) return;
|
|
23
|
+
try {
|
|
24
|
+
const target = typeof url === "function" ? url() : url;
|
|
25
|
+
socket = protocols === void 0 ? new WebSocket(target) : new WebSocket(target, protocols);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
onError?.(error);
|
|
28
|
+
scheduleRetry();
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
socket.addEventListener("open", () => {
|
|
32
|
+
delay = reconnectDelayMs;
|
|
33
|
+
});
|
|
34
|
+
socket.addEventListener("message", (event) => {
|
|
35
|
+
let data = event.data;
|
|
36
|
+
if (typeof data === "string") try {
|
|
37
|
+
data = JSON.parse(data);
|
|
38
|
+
} catch {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const senderId = typeof data === "object" && data !== null && typeof data.from === "string" ? data.from : void 0;
|
|
42
|
+
for (const handler of handlers) handler(data, senderId);
|
|
43
|
+
});
|
|
44
|
+
socket.addEventListener("close", () => {
|
|
45
|
+
socket = null;
|
|
46
|
+
scheduleRetry();
|
|
47
|
+
});
|
|
48
|
+
socket.addEventListener("error", (event) => {
|
|
49
|
+
onError?.(event);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
connect();
|
|
53
|
+
return {
|
|
54
|
+
send(message) {
|
|
55
|
+
if (socket?.readyState !== WebSocket.OPEN) return;
|
|
56
|
+
socket.send(JSON.stringify(room === void 0 ? message : {
|
|
57
|
+
...message,
|
|
58
|
+
room
|
|
59
|
+
}));
|
|
60
|
+
},
|
|
61
|
+
onMessage(handler) {
|
|
62
|
+
handlers.add(handler);
|
|
63
|
+
return () => handlers.delete(handler);
|
|
64
|
+
},
|
|
65
|
+
close() {
|
|
66
|
+
closed = true;
|
|
67
|
+
if (retryTimer) clearTimeout(retryTimer);
|
|
68
|
+
handlers.clear();
|
|
69
|
+
socket?.close();
|
|
70
|
+
socket = null;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
//#endregion
|
|
75
|
+
export { websocketTransport };
|
package/package.json
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "live-cursors",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"packageManager": "pnpm@10.33.0",
|
|
6
|
+
"description": "Multiplayer cursors for any transport. Zero dependencies, no SaaS. Works over Laravel Echo, raw WebSocket, BroadcastChannel or your own channel.",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"homepage": "https://github.com/qretro/live-cursors#readme",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/qretro/live-cursors.git"
|
|
12
|
+
},
|
|
13
|
+
"bugs": "https://github.com/qretro/live-cursors/issues",
|
|
14
|
+
"keywords": [
|
|
15
|
+
"live-cursors",
|
|
16
|
+
"multiplayer",
|
|
17
|
+
"cursors",
|
|
18
|
+
"presence",
|
|
19
|
+
"collaborative",
|
|
20
|
+
"realtime",
|
|
21
|
+
"websocket",
|
|
22
|
+
"laravel-echo",
|
|
23
|
+
"vue",
|
|
24
|
+
"react"
|
|
25
|
+
],
|
|
26
|
+
"sideEffects": false,
|
|
27
|
+
"exports": {
|
|
28
|
+
".": "./dist/index.mjs",
|
|
29
|
+
"./dom": "./dist/dom.mjs",
|
|
30
|
+
"./vue": "./dist/vue.mjs",
|
|
31
|
+
"./react": "./dist/react.mjs",
|
|
32
|
+
"./broadcast": "./dist/broadcast.mjs",
|
|
33
|
+
"./websocket": "./dist/websocket.mjs",
|
|
34
|
+
"./echo": "./dist/echo.mjs",
|
|
35
|
+
"./package.json": "./package.json"
|
|
36
|
+
},
|
|
37
|
+
"files": [
|
|
38
|
+
"dist"
|
|
39
|
+
],
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=18"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "tsdown",
|
|
45
|
+
"dev": "tsdown --watch",
|
|
46
|
+
"test": "vitest run",
|
|
47
|
+
"test:watch": "vitest",
|
|
48
|
+
"test:e2e": "playwright test",
|
|
49
|
+
"typecheck": "tsc --noEmit",
|
|
50
|
+
"lint": "eslint .",
|
|
51
|
+
"lint:fix": "eslint . --fix",
|
|
52
|
+
"check:pkg": "pnpm build && publint && attw --pack . --profile esm-only",
|
|
53
|
+
"play": "pnpm -F playground dev",
|
|
54
|
+
"demo:gif": "node scripts/record-demo.mjs",
|
|
55
|
+
"release": "pnpm test && pnpm check:pkg && bumpp",
|
|
56
|
+
"prepack": "pnpm build"
|
|
57
|
+
},
|
|
58
|
+
"peerDependencies": {
|
|
59
|
+
"react": ">=18",
|
|
60
|
+
"vue": ">=3.4"
|
|
61
|
+
},
|
|
62
|
+
"peerDependenciesMeta": {
|
|
63
|
+
"react": {
|
|
64
|
+
"optional": true
|
|
65
|
+
},
|
|
66
|
+
"vue": {
|
|
67
|
+
"optional": true
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
"devDependencies": {
|
|
71
|
+
"@antfu/eslint-config": "catalog:",
|
|
72
|
+
"@arethetypeswrong/cli": "catalog:",
|
|
73
|
+
"@eslint-react/eslint-plugin": "catalog:",
|
|
74
|
+
"@playwright/test": "catalog:",
|
|
75
|
+
"@types/node": "catalog:",
|
|
76
|
+
"@types/react": "catalog:",
|
|
77
|
+
"@types/ws": "catalog:",
|
|
78
|
+
"bumpp": "catalog:",
|
|
79
|
+
"eslint": "catalog:",
|
|
80
|
+
"eslint-plugin-react-hooks": "catalog:",
|
|
81
|
+
"eslint-plugin-react-refresh": "catalog:",
|
|
82
|
+
"happy-dom": "catalog:",
|
|
83
|
+
"publint": "catalog:",
|
|
84
|
+
"react": "catalog:",
|
|
85
|
+
"tsdown": "catalog:",
|
|
86
|
+
"typescript": "catalog:",
|
|
87
|
+
"vitest": "catalog:",
|
|
88
|
+
"vue": "catalog:",
|
|
89
|
+
"ws": "catalog:"
|
|
90
|
+
}
|
|
91
|
+
}
|