@prsm/realtime 1.0.0 → 1.0.2
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 +9 -0
- package/package.json +2 -7
- package/src/adapters/postgres.js +3 -3
- package/src/client/client.js +10 -10
- package/src/client/connection.js +1 -1
- package/src/client/subscriptions/channels.js +3 -3
- package/src/client/subscriptions/collections.js +2 -2
- package/src/client/subscriptions/presence.js +5 -5
- package/src/client/subscriptions/records.js +3 -3
- package/src/client/subscriptions/rooms.js +3 -3
- package/src/server/managers/channels.js +4 -4
- package/src/server/managers/collections.js +5 -5
- package/src/server/managers/connections.js +3 -3
- package/src/server/managers/instance.js +20 -20
- package/src/server/managers/presence.js +7 -7
- package/src/server/managers/pubsub.js +14 -14
- package/src/server/managers/rooms.js +7 -7
- package/src/server/server.js +26 -26
- package/src/server/utils/constants.js +4 -4
- package/src/shared/logger.js +1 -1
- package/src/devtools/client/dist/assets/index-CGm1NqOQ.css +0 -1
- package/src/devtools/client/dist/assets/index-w2FI7RvC.js +0 -168
- package/src/devtools/client/dist/index.html +0 -16
- package/src/devtools/client/index.html +0 -15
- package/src/devtools/client/package.json +0 -17
- package/src/devtools/client/src/App.vue +0 -173
- package/src/devtools/client/src/components/ConnectionPicker.vue +0 -38
- package/src/devtools/client/src/components/JsonView.vue +0 -18
- package/src/devtools/client/src/composables/useApi.js +0 -71
- package/src/devtools/client/src/composables/useHighlight.js +0 -57
- package/src/devtools/client/src/main.js +0 -5
- package/src/devtools/client/src/style.css +0 -440
- package/src/devtools/client/src/views/ChannelsView.vue +0 -27
- package/src/devtools/client/src/views/CollectionsView.vue +0 -61
- package/src/devtools/client/src/views/MetadataView.vue +0 -108
- package/src/devtools/client/src/views/RecordsView.vue +0 -30
- package/src/devtools/client/src/views/RoomsView.vue +0 -39
- package/src/devtools/client/vite.config.js +0 -17
- package/src/devtools/demo/server.js +0 -144
- package/src/devtools/index.js +0 -186
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { defineConfig } from 'vite'
|
|
2
|
-
import vue from '@vitejs/plugin-vue'
|
|
3
|
-
|
|
4
|
-
export default defineConfig({
|
|
5
|
-
plugins: [vue()],
|
|
6
|
-
base: './',
|
|
7
|
-
build: {
|
|
8
|
-
outDir: 'dist',
|
|
9
|
-
emptyOutDir: true
|
|
10
|
-
},
|
|
11
|
-
server: {
|
|
12
|
-
port: 4173,
|
|
13
|
-
proxy: {
|
|
14
|
-
'/api': 'http://localhost:3399/devtools'
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
})
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
import express from "express"
|
|
2
|
-
import http from "node:http"
|
|
3
|
-
import { RealtimeServer } from "../../../src/index.js"
|
|
4
|
-
import { RealtimeClient } from "../../../src/client/index.js"
|
|
5
|
-
import { createDevtools } from "../index.js"
|
|
6
|
-
|
|
7
|
-
const PORT = 3399
|
|
8
|
-
|
|
9
|
-
const app = express()
|
|
10
|
-
const httpServer = http.createServer(app)
|
|
11
|
-
|
|
12
|
-
const mesh = new RealtimeServer({
|
|
13
|
-
redis: { host: "127.0.0.1", port: 6379, db: 15 }
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
mesh.exposeChannel(/^chat:.*/)
|
|
17
|
-
mesh.exposeChannel("notifications")
|
|
18
|
-
mesh.exposeRecord(/^user:.*/)
|
|
19
|
-
mesh.exposeWritableRecord(/^doc:.*/)
|
|
20
|
-
mesh.trackPresence(/^room:.*/)
|
|
21
|
-
|
|
22
|
-
mesh.exposeCollection("users:online", async () => {
|
|
23
|
-
return await mesh.listRecordsMatching("user:*")
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
mesh.exposeCollection("docs:shared", async () => {
|
|
27
|
-
return await mesh.listRecordsMatching("doc:*")
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
mesh.exposeCollection(/^users-by-role:.*/, async (_conn, collectionId) => {
|
|
31
|
-
const role = collectionId.split(":")[1]
|
|
32
|
-
const users = await mesh.listRecordsMatching("user:*")
|
|
33
|
-
return users.filter(u => u.role === role)
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
mesh.exposeCommand("echo", (ctx) => ctx.payload)
|
|
37
|
-
|
|
38
|
-
app.use("/devtools", createDevtools(mesh))
|
|
39
|
-
|
|
40
|
-
const redis = mesh.redisManager.redis
|
|
41
|
-
await redis.flushdb()
|
|
42
|
-
|
|
43
|
-
await mesh.attach(httpServer, { port: PORT })
|
|
44
|
-
|
|
45
|
-
const names = ["alice", "bob", "charlie", "diana", "eve"]
|
|
46
|
-
const statuses = ["online", "away", "busy", "idle"]
|
|
47
|
-
const rooms = ["room:lobby", "room:general", "room:random"]
|
|
48
|
-
const channels = ["chat:general", "chat:random", "notifications"]
|
|
49
|
-
|
|
50
|
-
const clients = []
|
|
51
|
-
|
|
52
|
-
for (let i = 0; i < names.length; i++) {
|
|
53
|
-
const client = new RealtimeClient(`ws://localhost:${PORT}`)
|
|
54
|
-
await client.connect()
|
|
55
|
-
|
|
56
|
-
await mesh.setConnectionMetadata(client.connectionId, {
|
|
57
|
-
name: names[i],
|
|
58
|
-
role: i === 0 ? "admin" : "member",
|
|
59
|
-
joinedAt: new Date().toISOString()
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
const role = i === 0 ? "admin" : "member"
|
|
63
|
-
await mesh.writeRecord(`user:${names[i]}`, {
|
|
64
|
-
id: `user:${names[i]}`,
|
|
65
|
-
name: names[i],
|
|
66
|
-
email: `${names[i]}@example.com`,
|
|
67
|
-
role,
|
|
68
|
-
active: true
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
if (i < 3) {
|
|
72
|
-
await mesh.writeRecord(`doc:doc-${i}`, {
|
|
73
|
-
id: `doc:doc-${i}`,
|
|
74
|
-
title: `Document ${i}`,
|
|
75
|
-
content: "lorem ipsum",
|
|
76
|
-
author: names[i]
|
|
77
|
-
})
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const clientRooms = rooms.slice(0, 1 + (i % rooms.length))
|
|
81
|
-
for (const room of clientRooms) {
|
|
82
|
-
await client.joinRoom(room)
|
|
83
|
-
await client.subscribePresence(room, () => {})
|
|
84
|
-
}
|
|
85
|
-
await new Promise(r => setTimeout(r, 100))
|
|
86
|
-
for (const room of clientRooms) {
|
|
87
|
-
await client.publishPresenceState(room, {
|
|
88
|
-
state: {
|
|
89
|
-
status: statuses[i % statuses.length],
|
|
90
|
-
cursor: { x: Math.floor(Math.random() * 800), y: Math.floor(Math.random() * 600) }
|
|
91
|
-
}
|
|
92
|
-
})
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const clientChannels = channels.slice(0, 1 + (i % channels.length))
|
|
96
|
-
for (const ch of clientChannels) {
|
|
97
|
-
await client.subscribeChannel(ch, () => {})
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
await client.subscribeRecord(`user:${names[i]}`, () => {})
|
|
101
|
-
if (i < 3) await client.subscribeRecord(`doc:doc-${i}`, () => {})
|
|
102
|
-
|
|
103
|
-
clients.push(client)
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
await new Promise(r => setTimeout(r, 300))
|
|
107
|
-
|
|
108
|
-
for (const client of clients) {
|
|
109
|
-
await client.subscribeCollection("users:online", { onDiff: () => {} })
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
for (const client of clients.slice(0, 3)) {
|
|
113
|
-
await client.subscribeCollection("docs:shared", { onDiff: () => {} })
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
await clients[0].subscribeCollection("users-by-role:admin", { onDiff: () => {} })
|
|
117
|
-
await clients[1].subscribeCollection("users-by-role:member", { onDiff: () => {} })
|
|
118
|
-
await clients[2].subscribeCollection("users-by-role:admin", { onDiff: () => {} })
|
|
119
|
-
|
|
120
|
-
setInterval(async () => {
|
|
121
|
-
const i = Math.floor(Math.random() * clients.length)
|
|
122
|
-
const room = rooms[Math.floor(Math.random() * rooms.length)]
|
|
123
|
-
const clientRooms = rooms.slice(0, 1 + (i % rooms.length))
|
|
124
|
-
if (!clientRooms.includes(room)) return
|
|
125
|
-
|
|
126
|
-
await clients[i].publishPresenceState(room, {
|
|
127
|
-
state: {
|
|
128
|
-
status: statuses[Math.floor(Math.random() * statuses.length)],
|
|
129
|
-
cursor: { x: Math.floor(Math.random() * 800), y: Math.floor(Math.random() * 600) }
|
|
130
|
-
}
|
|
131
|
-
})
|
|
132
|
-
}, 3000)
|
|
133
|
-
|
|
134
|
-
setInterval(async () => {
|
|
135
|
-
const ch = channels[Math.floor(Math.random() * channels.length)]
|
|
136
|
-
await mesh.writeChannel(ch, {
|
|
137
|
-
from: names[Math.floor(Math.random() * names.length)],
|
|
138
|
-
text: `message at ${new Date().toISOString()}`,
|
|
139
|
-
}, 20)
|
|
140
|
-
}, 5000)
|
|
141
|
-
|
|
142
|
-
console.log(`mesh devtools demo running on http://localhost:${PORT}/devtools`)
|
|
143
|
-
console.log(`${names.length} clients connected, ${rooms.length} rooms, ${channels.length} channels`)
|
|
144
|
-
console.log(`vite dev server: cd packages/devtools/client && npm run dev`)
|
package/src/devtools/index.js
DELETED
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
import express from "express"
|
|
2
|
-
import { fileURLToPath } from "node:url"
|
|
3
|
-
import { dirname, join } from "node:path"
|
|
4
|
-
import { existsSync } from "node:fs"
|
|
5
|
-
|
|
6
|
-
function patternToString(p) {
|
|
7
|
-
if (typeof p === "string") return p
|
|
8
|
-
if (p instanceof RegExp) return p.toString()
|
|
9
|
-
return String(p)
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function createDevtools(server) {
|
|
13
|
-
const router = express.Router()
|
|
14
|
-
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
15
|
-
const distPath = join(__dirname, "client", "dist")
|
|
16
|
-
|
|
17
|
-
router.get("/api/state", async (_req, res) => {
|
|
18
|
-
try {
|
|
19
|
-
const connIds = await server.connectionManager.getAllConnectionIds()
|
|
20
|
-
const allMeta = await server.connectionManager._getMetadataForConnectionIds(connIds)
|
|
21
|
-
const localConns = server.connectionManager.getLocalConnections()
|
|
22
|
-
const localMap = {}
|
|
23
|
-
for (const conn of localConns) {
|
|
24
|
-
localMap[conn.id] = conn
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const connections = allMeta.map(({ id, metadata }) => {
|
|
28
|
-
const local = localMap[id]
|
|
29
|
-
return {
|
|
30
|
-
id,
|
|
31
|
-
metadata,
|
|
32
|
-
local: !!local,
|
|
33
|
-
latency: local?.latency?.ms ?? null,
|
|
34
|
-
alive: local?.alive ?? null,
|
|
35
|
-
remoteAddress: local?.remoteAddress ?? null
|
|
36
|
-
}
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
const roomNames = await server.roomManager.getAllRooms()
|
|
40
|
-
const rooms = []
|
|
41
|
-
for (const name of roomNames) {
|
|
42
|
-
const members = await server.roomManager.getRoomConnectionIds(name)
|
|
43
|
-
const statesMap = await server.presenceManager.getAllPresenceStates(name)
|
|
44
|
-
const presence = {}
|
|
45
|
-
statesMap.forEach((state, connId) => { presence[connId] = state })
|
|
46
|
-
rooms.push({ name, members, presence })
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const channels = {}
|
|
50
|
-
for (const [channel, subscribers] of Object.entries(server.channelManager.channelSubscriptions)) {
|
|
51
|
-
if (channel.startsWith("mesh:presence:updates:")) continue
|
|
52
|
-
channels[channel] = [...subscribers].map(c => c.id)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const collections = {}
|
|
56
|
-
server.collectionManager.collectionSubscriptions.forEach((subs, collId) => {
|
|
57
|
-
const subscribers = {}
|
|
58
|
-
subs.forEach((info, connId) => { subscribers[connId] = info })
|
|
59
|
-
collections[collId] = { subscribers }
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
const records = {}
|
|
63
|
-
server.recordSubscriptionManager.recordSubscriptions.forEach((subs, recordId) => {
|
|
64
|
-
const subscribers = {}
|
|
65
|
-
subs.forEach((mode, connId) => { subscribers[connId] = mode })
|
|
66
|
-
records[recordId] = { subscribers }
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
const exposed = {
|
|
70
|
-
channels: server.channelManager.exposedChannels.map(patternToString),
|
|
71
|
-
records: server.recordSubscriptionManager.exposedRecords.map(patternToString),
|
|
72
|
-
writableRecords: server.recordSubscriptionManager.exposedWritableRecords.map(patternToString),
|
|
73
|
-
collections: server.collectionManager.exposedCollections.map(e => patternToString(e.pattern)),
|
|
74
|
-
presence: server.presenceManager.trackedRooms.map(patternToString),
|
|
75
|
-
commands: server.commandManager.commands
|
|
76
|
-
? Object.keys(server.commandManager.commands).filter(c => !c.startsWith("mesh/"))
|
|
77
|
-
: []
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
res.json({
|
|
81
|
-
instanceId: server.instanceId,
|
|
82
|
-
connections,
|
|
83
|
-
rooms,
|
|
84
|
-
channels,
|
|
85
|
-
collections,
|
|
86
|
-
records,
|
|
87
|
-
exposed
|
|
88
|
-
})
|
|
89
|
-
} catch (err) {
|
|
90
|
-
res.status(500).json({ error: err.message })
|
|
91
|
-
}
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
router.get("/api/connection/:id", async (req, res) => {
|
|
95
|
-
try {
|
|
96
|
-
const { id } = req.params
|
|
97
|
-
const metadata = await server.connectionManager.getMetadata(id)
|
|
98
|
-
const rooms = await server.roomManager.getRoomsForConnection(id)
|
|
99
|
-
|
|
100
|
-
const presence = {}
|
|
101
|
-
for (const room of rooms) {
|
|
102
|
-
const state = await server.presenceManager.getPresenceState(id, room)
|
|
103
|
-
if (state) presence[room] = state
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const channels = []
|
|
107
|
-
for (const [channel, subscribers] of Object.entries(server.channelManager.channelSubscriptions)) {
|
|
108
|
-
if (channel.startsWith("mesh:presence:updates:")) continue
|
|
109
|
-
for (const conn of subscribers) {
|
|
110
|
-
if (conn.id === id) { channels.push(channel); break }
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const collections = []
|
|
115
|
-
server.collectionManager.collectionSubscriptions.forEach((subs, collId) => {
|
|
116
|
-
if (subs.has(id)) collections.push({ id: collId, ...subs.get(id) })
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
const records = []
|
|
120
|
-
server.recordSubscriptionManager.recordSubscriptions.forEach((subs, recordId) => {
|
|
121
|
-
if (subs.has(id)) records.push({ id: recordId, mode: subs.get(id) })
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
const local = server.connectionManager.getLocalConnection(id)
|
|
125
|
-
|
|
126
|
-
res.json({
|
|
127
|
-
id,
|
|
128
|
-
metadata,
|
|
129
|
-
rooms,
|
|
130
|
-
presence,
|
|
131
|
-
channels,
|
|
132
|
-
collections,
|
|
133
|
-
records,
|
|
134
|
-
local: !!local,
|
|
135
|
-
latency: local?.latency?.ms ?? null,
|
|
136
|
-
alive: local?.alive ?? null,
|
|
137
|
-
remoteAddress: local?.remoteAddress ?? null
|
|
138
|
-
})
|
|
139
|
-
} catch (err) {
|
|
140
|
-
res.status(500).json({ error: err.message })
|
|
141
|
-
}
|
|
142
|
-
})
|
|
143
|
-
|
|
144
|
-
router.get("/api/room/:name", async (req, res) => {
|
|
145
|
-
try {
|
|
146
|
-
const { name } = req.params
|
|
147
|
-
const membersWithMeta = await server.getRoomMembersWithMetadata(name)
|
|
148
|
-
const statesMap = await server.presenceManager.getAllPresenceStates(name)
|
|
149
|
-
const presence = {}
|
|
150
|
-
statesMap.forEach((state, connId) => { presence[connId] = state })
|
|
151
|
-
res.json({ name, members: membersWithMeta, presence })
|
|
152
|
-
} catch (err) {
|
|
153
|
-
res.status(500).json({ error: err.message })
|
|
154
|
-
}
|
|
155
|
-
})
|
|
156
|
-
|
|
157
|
-
router.get("/api/collection/:id/records", async (req, res) => {
|
|
158
|
-
try {
|
|
159
|
-
const collId = req.params.id
|
|
160
|
-
const connId = req.query.connId
|
|
161
|
-
if (!connId) return res.status(400).json({ error: "connId query param required" })
|
|
162
|
-
|
|
163
|
-
const raw = await server.redisManager.redis.get(`mesh:collection:${collId}:${connId}`)
|
|
164
|
-
if (!raw) return res.json({ recordIds: [], records: [] })
|
|
165
|
-
|
|
166
|
-
const recordIds = JSON.parse(raw)
|
|
167
|
-
const records = []
|
|
168
|
-
for (const rid of recordIds) {
|
|
169
|
-
const data = await server.recordManager.getRecord(rid)
|
|
170
|
-
records.push({ id: rid, data })
|
|
171
|
-
}
|
|
172
|
-
res.json({ recordIds, records })
|
|
173
|
-
} catch (err) {
|
|
174
|
-
res.status(500).json({ error: err.message })
|
|
175
|
-
}
|
|
176
|
-
})
|
|
177
|
-
|
|
178
|
-
if (existsSync(distPath)) {
|
|
179
|
-
router.use(express.static(distPath))
|
|
180
|
-
router.get("/{*splat}", (_req, res) => {
|
|
181
|
-
res.sendFile(join(distPath, "index.html"))
|
|
182
|
-
})
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
return router
|
|
186
|
-
}
|