atom.io 0.45.5 → 0.46.1
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/internal/index.js +2 -7
- package/dist/internal/index.js.map +1 -1
- package/dist/react-devtools/index.d.ts.map +1 -1
- package/dist/realtime/index.d.ts +6 -3
- package/dist/realtime/index.d.ts.map +1 -1
- package/dist/realtime/index.js +9 -1
- package/dist/realtime/index.js.map +1 -1
- package/dist/realtime-server/index.d.ts +73 -50
- package/dist/realtime-server/index.d.ts.map +1 -1
- package/dist/realtime-server/index.js +222 -182
- package/dist/realtime-server/index.js.map +1 -1
- package/dist/realtime-testing/index.d.ts +1 -2
- package/dist/realtime-testing/index.d.ts.map +1 -1
- package/dist/realtime-testing/index.js +31 -53
- package/dist/realtime-testing/index.js.map +1 -1
- package/package.json +10 -10
- package/src/internal/subscribe/subscribe-to-state.ts +9 -18
- package/src/realtime/cast-socket.ts +1 -0
- package/src/realtime/shared-room-store.ts +15 -0
- package/src/realtime/socket-interface.ts +5 -1
- package/src/realtime-server/index.ts +3 -2
- package/src/realtime-server/ipc-sockets/custom-socket.ts +18 -2
- package/src/realtime-server/ipc-sockets/parent-socket.ts +1 -1
- package/src/realtime-server/{realtime-server-stores/provide-rooms.ts → provide-rooms.ts} +32 -57
- package/src/realtime-server/realtime-state-provider.ts +5 -3
- package/src/realtime-server/realtime-state-receiver.ts +19 -3
- package/src/realtime-server/server-config.ts +112 -1
- package/src/realtime-server/{realtime-server-stores/server-user-store.ts → server-socket-state.ts} +1 -8
- package/src/realtime-testing/setup-realtime-test.tsx +38 -83
- package/src/realtime-server/realtime-server-stores/index.ts +0 -3
- package/src/realtime-server/realtime-server-stores/provide-identity.ts +0 -18
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "atom.io",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.46.1",
|
|
4
4
|
"description": "Composable and testable reactive data library.",
|
|
5
5
|
"homepage": "https://atom.io.fyi",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -60,19 +60,19 @@
|
|
|
60
60
|
}
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
|
-
"@storybook/addon-docs": "10.1.
|
|
64
|
-
"@storybook/addon-onboarding": "10.1.
|
|
65
|
-
"@storybook/react-vite": "10.1.
|
|
63
|
+
"@storybook/addon-docs": "10.1.5",
|
|
64
|
+
"@storybook/addon-onboarding": "10.1.5",
|
|
65
|
+
"@storybook/react-vite": "10.1.5",
|
|
66
66
|
"@testing-library/react": "16.3.0",
|
|
67
67
|
"@types/eslint": "9.6.1",
|
|
68
68
|
"@types/estree": "1.0.8",
|
|
69
69
|
"@types/http-proxy": "1.17.17",
|
|
70
70
|
"@types/react": "19.2.7",
|
|
71
71
|
"@types/tmp": "0.2.6",
|
|
72
|
-
"@typescript-eslint/parser": "8.
|
|
73
|
-
"@typescript-eslint/rule-tester": "8.
|
|
74
|
-
"@typescript-eslint/utils": "8.
|
|
75
|
-
"@typescript/native-preview": "7.0.0-dev.
|
|
72
|
+
"@typescript-eslint/parser": "8.49.0",
|
|
73
|
+
"@typescript-eslint/rule-tester": "8.49.0",
|
|
74
|
+
"@typescript-eslint/utils": "8.49.0",
|
|
75
|
+
"@typescript/native-preview": "7.0.0-dev.20251209.1",
|
|
76
76
|
"@vitest/coverage-v8": "4.0.15",
|
|
77
77
|
"@vitest/ui": "4.0.15",
|
|
78
78
|
"arktype": "2.1.28",
|
|
@@ -91,9 +91,9 @@
|
|
|
91
91
|
"recoverage": "0.1.13",
|
|
92
92
|
"socket.io": "4.8.1",
|
|
93
93
|
"socket.io-client": "4.8.1",
|
|
94
|
-
"storybook": "10.1.
|
|
94
|
+
"storybook": "10.1.5",
|
|
95
95
|
"tmp": "0.2.5",
|
|
96
|
-
"tsdown": "0.17.
|
|
96
|
+
"tsdown": "0.17.2",
|
|
97
97
|
"vite": "7.2.7",
|
|
98
98
|
"vite-tsconfig-paths": "5.1.4",
|
|
99
99
|
"vitest": "4.0.15",
|
|
@@ -15,25 +15,16 @@ export function subscribeToState<T, E>(
|
|
|
15
15
|
handleUpdate: UpdateHandler<E | T>,
|
|
16
16
|
): () => void {
|
|
17
17
|
function safelyHandleUpdate(update: StateUpdate<any>): void {
|
|
18
|
-
if (
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
const unsubscribe = store.on.operationClose.subscribe(
|
|
28
|
-
`state subscription ${key}`,
|
|
29
|
-
() => {
|
|
30
|
-
unsubscribe()
|
|
31
|
-
handleUpdate(update)
|
|
32
|
-
},
|
|
33
|
-
)
|
|
34
|
-
} else {
|
|
35
|
-
handleUpdate(update)
|
|
18
|
+
if (
|
|
19
|
+
store.operation.open &&
|
|
20
|
+
state?.type === `atom` &&
|
|
21
|
+
hasRole(state, `tracker:signal`) &&
|
|
22
|
+
`*` + store.operation.token.key === token.key &&
|
|
23
|
+
`inboundTracker` in handleUpdate
|
|
24
|
+
) {
|
|
25
|
+
return
|
|
36
26
|
}
|
|
27
|
+
handleUpdate(update)
|
|
37
28
|
}
|
|
38
29
|
reduceReference(store, token)
|
|
39
30
|
const state = withdraw(store, token)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
JoinToken,
|
|
3
3
|
MutableAtomToken,
|
|
4
|
+
PureSelectorFamilyToken,
|
|
4
5
|
ReadonlyPureSelectorFamilyToken,
|
|
5
6
|
} from "atom.io"
|
|
6
7
|
import { getInternalRelations, join, mutableAtom, selectorFamily } from "atom.io"
|
|
@@ -41,6 +42,20 @@ export const usersInRooms: JoinToken<`room`, RoomKey, `user`, UserKey, `1:n`> =
|
|
|
41
42
|
isBType: isUserKey,
|
|
42
43
|
})
|
|
43
44
|
|
|
45
|
+
export const visibleUsersInRoomsSelector: PureSelectorFamilyToken<
|
|
46
|
+
(RoomKey | UserKey)[],
|
|
47
|
+
UserKey
|
|
48
|
+
> = selectorFamily({
|
|
49
|
+
key: `selfList`,
|
|
50
|
+
get:
|
|
51
|
+
(userKey) =>
|
|
52
|
+
({ get }) => {
|
|
53
|
+
const [, roomsOfUsersAtoms] = getInternalRelations(usersInRooms, `split`)
|
|
54
|
+
const rooms = get(roomsOfUsersAtoms, userKey)
|
|
55
|
+
return [userKey, ...rooms]
|
|
56
|
+
},
|
|
57
|
+
})
|
|
58
|
+
|
|
44
59
|
export const ownersOfRooms: JoinToken<`user`, UserKey, `room`, RoomKey, `1:n`> =
|
|
45
60
|
join({
|
|
46
61
|
key: `ownersOfRooms`,
|
|
@@ -28,11 +28,12 @@ export type EventEmitter<EmitEvents extends EventsMap = EventsMap> = <
|
|
|
28
28
|
|
|
29
29
|
export type TypedSocket<
|
|
30
30
|
ListenEvents extends EventsMap = EventsMap,
|
|
31
|
-
EmitEvents extends EventsMap =
|
|
31
|
+
EmitEvents extends EventsMap = never,
|
|
32
32
|
> = {
|
|
33
33
|
id: string | undefined
|
|
34
34
|
on: ParticularEventListener<ListenEvents>
|
|
35
35
|
onAny: (listener: AllEventsListener<ListenEvents>) => void
|
|
36
|
+
onAnyOutgoing: (listener: AllEventsListener<EmitEvents>) => void
|
|
36
37
|
off: ParticularEventListener<ListenEvents>
|
|
37
38
|
offAny: (listener: AllEventsListener<ListenEvents>) => void
|
|
38
39
|
emit: EventEmitter<EmitEvents>
|
|
@@ -44,6 +45,9 @@ export type Socket = {
|
|
|
44
45
|
onAny: (
|
|
45
46
|
listener: (event: string, ...args: Json.Serializable[]) => void,
|
|
46
47
|
) => void
|
|
48
|
+
onAnyOutgoing: (
|
|
49
|
+
listener: (event: string, ...args: Json.Serializable[]) => void,
|
|
50
|
+
) => void
|
|
47
51
|
off: (event: string, listener: (...args: Json.Serializable[]) => void) => void
|
|
48
52
|
offAny: (
|
|
49
53
|
listener: (event: string, ...args: Json.Serializable[]) => void,
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
export * from "./continuity/provide-continuity"
|
|
2
2
|
export * from "./ipc-sockets"
|
|
3
|
+
export * from "./provide-rooms"
|
|
3
4
|
export * from "./realtime-family-provider"
|
|
4
5
|
export * from "./realtime-mutable-family-provider"
|
|
5
6
|
export * from "./realtime-mutable-provider"
|
|
6
|
-
export * from "./realtime-server-stores"
|
|
7
7
|
export * from "./realtime-state-provider"
|
|
8
8
|
export * from "./realtime-state-receiver"
|
|
9
|
-
export
|
|
9
|
+
export * from "./server-config"
|
|
10
|
+
export * from "./server-socket-state"
|
|
@@ -20,6 +20,9 @@ export abstract class CustomSocket<I extends Events, O extends Events>
|
|
|
20
20
|
{
|
|
21
21
|
protected listeners: Map<keyof O, Set<(...args: Json.Array) => void>>
|
|
22
22
|
protected globalListeners: Set<(event: string, ...args: Json.Array) => void>
|
|
23
|
+
protected globalListenersOutgoing: Set<
|
|
24
|
+
(event: string, ...args: Json.Array) => void
|
|
25
|
+
>
|
|
23
26
|
protected handleEvent<K extends string & keyof I>(
|
|
24
27
|
...args: EventPayload<I, K>
|
|
25
28
|
): void {
|
|
@@ -36,7 +39,7 @@ export abstract class CustomSocket<I extends Events, O extends Events>
|
|
|
36
39
|
}
|
|
37
40
|
|
|
38
41
|
public id = `no_id_retrieved`
|
|
39
|
-
public emit: <Event extends keyof I>(
|
|
42
|
+
public emit: <Event extends string & keyof I>(
|
|
40
43
|
event: Event,
|
|
41
44
|
...args: I[Event]
|
|
42
45
|
) => CustomSocket<I, O>
|
|
@@ -47,9 +50,15 @@ export abstract class CustomSocket<I extends Events, O extends Events>
|
|
|
47
50
|
...args: I[Event]
|
|
48
51
|
) => CustomSocket<I, O>,
|
|
49
52
|
) {
|
|
50
|
-
this.emit =
|
|
53
|
+
this.emit = (...args) => {
|
|
54
|
+
for (const listener of this.globalListenersOutgoing) {
|
|
55
|
+
listener(...args)
|
|
56
|
+
}
|
|
57
|
+
return emit(...args)
|
|
58
|
+
}
|
|
51
59
|
this.listeners = new Map()
|
|
52
60
|
this.globalListeners = new Set()
|
|
61
|
+
this.globalListenersOutgoing = new Set()
|
|
53
62
|
}
|
|
54
63
|
|
|
55
64
|
public on<Event extends keyof O>(
|
|
@@ -70,6 +79,13 @@ export abstract class CustomSocket<I extends Events, O extends Events>
|
|
|
70
79
|
return this
|
|
71
80
|
}
|
|
72
81
|
|
|
82
|
+
public onAnyOutgoing(
|
|
83
|
+
listener: (event: string, ...args: Json.Array) => void,
|
|
84
|
+
): this {
|
|
85
|
+
this.globalListenersOutgoing.add(listener)
|
|
86
|
+
return this
|
|
87
|
+
}
|
|
88
|
+
|
|
73
89
|
public off<Event extends keyof O>(
|
|
74
90
|
event: Event,
|
|
75
91
|
listener?: (...args: O[Event]) => void,
|
|
@@ -19,7 +19,6 @@ import type {
|
|
|
19
19
|
RoomSocketInterface,
|
|
20
20
|
Socket,
|
|
21
21
|
SocketGuard,
|
|
22
|
-
SocketKey,
|
|
23
22
|
StandardSchemaV1,
|
|
24
23
|
TypedSocket,
|
|
25
24
|
UserKey,
|
|
@@ -30,18 +29,13 @@ import {
|
|
|
30
29
|
ownersOfRooms,
|
|
31
30
|
roomKeysAtom,
|
|
32
31
|
usersInRooms,
|
|
32
|
+
visibleUsersInRoomsSelector,
|
|
33
33
|
} from "atom.io/realtime"
|
|
34
34
|
|
|
35
|
-
import { ChildSocket } from "
|
|
36
|
-
import { realtimeMutableFamilyProvider } from "
|
|
37
|
-
import { realtimeMutableProvider } from "
|
|
38
|
-
import type { ServerConfig } from "
|
|
39
|
-
import {
|
|
40
|
-
selfListSelectors,
|
|
41
|
-
socketKeysAtom,
|
|
42
|
-
userKeysAtom,
|
|
43
|
-
usersOfSockets,
|
|
44
|
-
} from "./server-user-store"
|
|
35
|
+
import { ChildSocket } from "./ipc-sockets"
|
|
36
|
+
import { realtimeMutableFamilyProvider } from "./realtime-mutable-family-provider"
|
|
37
|
+
import { realtimeMutableProvider } from "./realtime-mutable-provider"
|
|
38
|
+
import type { ServerConfig } from "./server-config"
|
|
45
39
|
|
|
46
40
|
export type RoomMap = Map<
|
|
47
41
|
string,
|
|
@@ -240,74 +234,55 @@ export function provideRooms<RoomNames extends string>({
|
|
|
240
234
|
socket,
|
|
241
235
|
resolveRoomScript,
|
|
242
236
|
roomNames,
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
)!
|
|
249
|
-
// const roomSocket = socket as TypedSocket<RoomSocketInterface<RoomNames>, {}>
|
|
250
|
-
const roomSocket = castSocket<TypedSocket<RoomSocketInterface<RoomNames>, {}>>(
|
|
251
|
-
socket,
|
|
252
|
-
createRoomSocketGuard(roomNames),
|
|
253
|
-
)
|
|
254
|
-
|
|
237
|
+
userKey,
|
|
238
|
+
}: ProvideRoomsConfig<RoomNames> & ServerConfig): () => void {
|
|
239
|
+
const roomSocket = castSocket<
|
|
240
|
+
TypedSocket<RoomSocketInterface<RoomNames>, never>
|
|
241
|
+
>(socket, createRoomSocketGuard(roomNames))
|
|
255
242
|
const exposeMutable = realtimeMutableProvider({ socket, store, userKey })
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
userKey,
|
|
260
|
-
})
|
|
261
|
-
|
|
262
|
-
exposeMutable(roomKeysAtom)
|
|
263
|
-
|
|
264
|
-
const [, usersInRoomsAtoms] = getInternalRelationsFromStore(
|
|
243
|
+
const unsubFromRoomKeys = exposeMutable(roomKeysAtom)
|
|
244
|
+
const usersInRoomsAtoms = getInternalRelationsFromStore(store, usersInRooms)
|
|
245
|
+
const [, usersInRoomsAtomsUsersOnly] = getInternalRelationsFromStore(
|
|
265
246
|
store,
|
|
266
247
|
usersInRooms,
|
|
267
248
|
`split`,
|
|
268
249
|
)
|
|
269
250
|
const usersWhoseRoomsCanBeSeenSelector = findInStore(
|
|
270
251
|
store,
|
|
271
|
-
|
|
252
|
+
visibleUsersInRoomsSelector,
|
|
272
253
|
userKey,
|
|
273
254
|
)
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
// usersOfSockets,
|
|
278
|
-
// )
|
|
279
|
-
// exposeMutableFamily(usersOfSocketsAtoms, socketKeysAtom)
|
|
280
|
-
const [ownersOfRoomsAtoms] = getInternalRelationsFromStore(
|
|
255
|
+
const ownersOfRoomsAtoms = getInternalRelationsFromStore(store, ownersOfRooms)
|
|
256
|
+
const exposeMutableFamily = realtimeMutableFamilyProvider({
|
|
257
|
+
socket,
|
|
281
258
|
store,
|
|
282
|
-
|
|
283
|
-
|
|
259
|
+
userKey,
|
|
260
|
+
})
|
|
261
|
+
const unsubFromUsersInRooms = exposeMutableFamily(
|
|
262
|
+
usersInRoomsAtoms,
|
|
263
|
+
usersWhoseRoomsCanBeSeenSelector,
|
|
264
|
+
)
|
|
265
|
+
const unsubFromOwnersOfRooms = exposeMutableFamily(
|
|
266
|
+
ownersOfRoomsAtoms,
|
|
267
|
+
usersWhoseRoomsCanBeSeenSelector,
|
|
284
268
|
)
|
|
285
|
-
exposeMutableFamily(ownersOfRoomsAtoms, usersWhoseRoomsCanBeSeenSelector)
|
|
286
|
-
|
|
287
269
|
const enterRoom = provideEnterAndExit({ store, socket, roomSocket, userKey })
|
|
288
270
|
|
|
289
|
-
const userRoomSet = getFromStore(store,
|
|
271
|
+
const userRoomSet = getFromStore(store, usersInRoomsAtomsUsersOnly, userKey)
|
|
290
272
|
for (const userRoomKey of userRoomSet) {
|
|
291
273
|
enterRoom(userRoomKey)
|
|
292
274
|
break
|
|
293
275
|
}
|
|
294
|
-
|
|
295
276
|
roomSocket.on(
|
|
296
277
|
`createRoom`,
|
|
297
278
|
spawnRoom({ store, socket, userKey, resolveRoomScript }),
|
|
298
279
|
)
|
|
299
280
|
roomSocket.on(`deleteRoom`, destroyRoom({ store, socket, userKey }))
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
`👤 ${userKey} disconnects`,
|
|
306
|
-
)
|
|
307
|
-
editRelationsInStore(store, usersOfSockets, (rel) => rel.delete(socketKey))
|
|
308
|
-
setIntoStore(store, userKeysAtom, (keys) => (keys.delete(userKey), keys))
|
|
309
|
-
setIntoStore(store, socketKeysAtom, (keys) => (keys.delete(socketKey), keys))
|
|
310
|
-
})
|
|
281
|
+
return () => {
|
|
282
|
+
unsubFromRoomKeys()
|
|
283
|
+
unsubFromUsersInRooms()
|
|
284
|
+
unsubFromOwnersOfRooms()
|
|
285
|
+
}
|
|
311
286
|
}
|
|
312
287
|
|
|
313
288
|
const roomKeySchema: StandardSchemaV1<Json.Array, [RoomKey]> = {
|
|
@@ -21,9 +21,11 @@ export function realtimeStateProvider({
|
|
|
21
21
|
store = IMPLICIT.STORE,
|
|
22
22
|
}: ServerConfig) {
|
|
23
23
|
store.logger.info(`🔌`, `user`, userKey, `initialized state provider`)
|
|
24
|
-
return function stateProvider<
|
|
25
|
-
clientToken: AtomIO.WritableToken<
|
|
26
|
-
serverData:
|
|
24
|
+
return function stateProvider<C extends Json.Serializable, S extends C>(
|
|
25
|
+
clientToken: AtomIO.WritableToken<C>,
|
|
26
|
+
serverData:
|
|
27
|
+
| AtomIO.ReadableToken<S>
|
|
28
|
+
| S = clientToken as AtomIO.ReadableToken<S>,
|
|
27
29
|
): () => void {
|
|
28
30
|
const isStatic = !isReadableToken(serverData)
|
|
29
31
|
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
subscribeToState,
|
|
10
10
|
} from "atom.io/internal"
|
|
11
11
|
import type { Json } from "atom.io/json"
|
|
12
|
+
import type { SocketKey, StandardSchemaV1 } from "atom.io/realtime"
|
|
12
13
|
import { employSocket, mutexAtoms } from "atom.io/realtime"
|
|
13
14
|
|
|
14
15
|
import type { ServerConfig } from "."
|
|
@@ -16,12 +17,15 @@ import type { ServerConfig } from "."
|
|
|
16
17
|
export type StateReceiver = ReturnType<typeof realtimeStateReceiver>
|
|
17
18
|
export function realtimeStateReceiver({
|
|
18
19
|
socket,
|
|
20
|
+
userKey,
|
|
19
21
|
store = IMPLICIT.STORE,
|
|
20
22
|
}: ServerConfig) {
|
|
21
23
|
return function stateReceiver<S extends Json.Serializable, C extends S>(
|
|
24
|
+
schema: StandardSchemaV1<unknown, C>,
|
|
22
25
|
clientToken: WritableToken<C>,
|
|
23
26
|
serverToken: WritableToken<S> = clientToken,
|
|
24
27
|
): () => void {
|
|
28
|
+
const socketKey = `socket::${socket.id}` satisfies SocketKey
|
|
25
29
|
const mutexAtom = findInStore(store, mutexAtoms, serverToken.key)
|
|
26
30
|
|
|
27
31
|
const subscriptions = new Set<() => void>()
|
|
@@ -33,8 +37,20 @@ export function realtimeStateReceiver({
|
|
|
33
37
|
const permitPublish = () => {
|
|
34
38
|
clearSubscriptions()
|
|
35
39
|
subscriptions.add(
|
|
36
|
-
employSocket(socket, `pub:${clientToken.key}`, (newValue) => {
|
|
37
|
-
|
|
40
|
+
employSocket(socket, `pub:${clientToken.key}`, async (newValue) => {
|
|
41
|
+
const parsed = await schema[`~standard`].validate(newValue)
|
|
42
|
+
if (parsed.issues) {
|
|
43
|
+
store.logger.error(
|
|
44
|
+
`❌`,
|
|
45
|
+
`user`,
|
|
46
|
+
userKey,
|
|
47
|
+
`attempted to publish invalid value`,
|
|
48
|
+
newValue,
|
|
49
|
+
`to state "${serverToken.key}"`,
|
|
50
|
+
)
|
|
51
|
+
return
|
|
52
|
+
}
|
|
53
|
+
setIntoStore(store, serverToken, parsed.value)
|
|
38
54
|
}),
|
|
39
55
|
)
|
|
40
56
|
subscriptions.add(
|
|
@@ -52,7 +68,7 @@ export function realtimeStateReceiver({
|
|
|
52
68
|
if (getFromStore(store, mutexAtom)) {
|
|
53
69
|
clearSubscriptions()
|
|
54
70
|
subscriptions.add(
|
|
55
|
-
subscribeToState(store, mutexAtom,
|
|
71
|
+
subscribeToState(store, mutexAtom, socketKey, () => {
|
|
56
72
|
const currentValue = getFromStore(store, mutexAtom)
|
|
57
73
|
if (currentValue === false) {
|
|
58
74
|
operateOnStore(OWN_OP, store, mutexAtom, true)
|
|
@@ -1,8 +1,119 @@
|
|
|
1
|
+
import type { IncomingHttpHeaders } from "node:http"
|
|
2
|
+
import type { ParsedUrlQuery } from "node:querystring"
|
|
3
|
+
|
|
4
|
+
import { Realm } from "atom.io"
|
|
1
5
|
import type { RootStore } from "atom.io/internal"
|
|
2
|
-
import
|
|
6
|
+
import {
|
|
7
|
+
editRelationsInStore,
|
|
8
|
+
findInStore,
|
|
9
|
+
findRelationsInStore,
|
|
10
|
+
getFromStore,
|
|
11
|
+
IMPLICIT,
|
|
12
|
+
setIntoStore,
|
|
13
|
+
} from "atom.io/internal"
|
|
14
|
+
import type { Socket, SocketKey, UserKey } from "atom.io/realtime"
|
|
15
|
+
import type { Server } from "socket.io"
|
|
16
|
+
|
|
17
|
+
import { realtimeStateProvider } from "./realtime-state-provider"
|
|
18
|
+
import type { SocketSystemHierarchy } from "./server-socket-state"
|
|
19
|
+
import {
|
|
20
|
+
socketAtoms,
|
|
21
|
+
socketKeysAtom,
|
|
22
|
+
userKeysAtom,
|
|
23
|
+
usersOfSockets,
|
|
24
|
+
} from "./server-socket-state"
|
|
3
25
|
|
|
4
26
|
export type ServerConfig = {
|
|
5
27
|
socket: Socket
|
|
6
28
|
userKey: UserKey
|
|
7
29
|
store?: RootStore
|
|
8
30
|
}
|
|
31
|
+
|
|
32
|
+
/** Socket Handshake details--taken from socket.io */
|
|
33
|
+
export type Handshake = {
|
|
34
|
+
/** The headers sent as part of the handshake */
|
|
35
|
+
headers: IncomingHttpHeaders
|
|
36
|
+
/** The date of creation (as string) */
|
|
37
|
+
time: string
|
|
38
|
+
/** The ip of the client */
|
|
39
|
+
address: string
|
|
40
|
+
/** Whether the connection is cross-domain */
|
|
41
|
+
xdomain: boolean
|
|
42
|
+
/** Whether the connection is secure */
|
|
43
|
+
secure: boolean
|
|
44
|
+
/** The date of creation (as unix timestamp) */
|
|
45
|
+
issued: number
|
|
46
|
+
/** The request URL string */
|
|
47
|
+
url: string
|
|
48
|
+
/** The query object */
|
|
49
|
+
query: ParsedUrlQuery
|
|
50
|
+
/** The auth object */
|
|
51
|
+
auth: {
|
|
52
|
+
[key: string]: any
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function realtime(
|
|
57
|
+
server: Server,
|
|
58
|
+
auth: (handshake: Handshake) => Error | UserKey,
|
|
59
|
+
onConnect: (config: ServerConfig) => () => void,
|
|
60
|
+
store: RootStore = IMPLICIT.STORE,
|
|
61
|
+
): () => Promise<void> {
|
|
62
|
+
const socketRealm = new Realm<SocketSystemHierarchy>(store)
|
|
63
|
+
|
|
64
|
+
server
|
|
65
|
+
.use((socket, next) => {
|
|
66
|
+
const result = auth(socket.handshake)
|
|
67
|
+
if (result instanceof Error) {
|
|
68
|
+
next(result)
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
const userClaim = socketRealm.allocate(`root`, result)
|
|
72
|
+
const socketClaim = socketRealm.allocate(`root`, `socket::${socket.id}`)
|
|
73
|
+
const socketState = findInStore(store, socketAtoms, socketClaim)
|
|
74
|
+
setIntoStore(store, socketState, socket)
|
|
75
|
+
editRelationsInStore(store, usersOfSockets, (relations) => {
|
|
76
|
+
relations.set(userClaim, socketClaim)
|
|
77
|
+
})
|
|
78
|
+
setIntoStore(store, userKeysAtom, (index) => index.add(userClaim))
|
|
79
|
+
setIntoStore(store, socketKeysAtom, (index) => index.add(socketClaim))
|
|
80
|
+
next()
|
|
81
|
+
})
|
|
82
|
+
.on(`connection`, (socket) => {
|
|
83
|
+
const socketKey = `socket::${socket.id}` satisfies SocketKey
|
|
84
|
+
const userKeyState = findRelationsInStore(
|
|
85
|
+
store,
|
|
86
|
+
usersOfSockets,
|
|
87
|
+
socketKey,
|
|
88
|
+
).userKeyOfSocket
|
|
89
|
+
const userKey = getFromStore(store, userKeyState)!
|
|
90
|
+
const serverConfig: ServerConfig = { store, socket, userKey }
|
|
91
|
+
const provideState = realtimeStateProvider(serverConfig)
|
|
92
|
+
const unsubFromMyUserKey = provideState(
|
|
93
|
+
{ key: `myUserKey`, type: `atom` },
|
|
94
|
+
userKey,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
const disposeServices = onConnect(serverConfig)
|
|
98
|
+
|
|
99
|
+
socket.on(`disconnect`, () => {
|
|
100
|
+
store.logger.info(`📡`, `socket`, socketKey, `👤 ${userKey} disconnects`)
|
|
101
|
+
disposeServices()
|
|
102
|
+
unsubFromMyUserKey()
|
|
103
|
+
editRelationsInStore(store, usersOfSockets, (rel) =>
|
|
104
|
+
rel.delete(socketKey),
|
|
105
|
+
)
|
|
106
|
+
setIntoStore(store, userKeysAtom, (keys) => (keys.delete(userKey), keys))
|
|
107
|
+
setIntoStore(
|
|
108
|
+
store,
|
|
109
|
+
socketKeysAtom,
|
|
110
|
+
(keys) => (keys.delete(socketKey), keys),
|
|
111
|
+
)
|
|
112
|
+
})
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
const disposeAll = async () => {
|
|
116
|
+
await server.close()
|
|
117
|
+
}
|
|
118
|
+
return disposeAll
|
|
119
|
+
}
|
package/src/realtime-server/{realtime-server-stores/server-user-store.ts → server-socket-state.ts}
RENAMED
|
@@ -2,10 +2,9 @@ import type {
|
|
|
2
2
|
Hierarchy,
|
|
3
3
|
JoinToken,
|
|
4
4
|
MutableAtomToken,
|
|
5
|
-
PureSelectorFamilyToken,
|
|
6
5
|
RegularAtomFamilyToken,
|
|
7
6
|
} from "atom.io"
|
|
8
|
-
import { atomFamily, join, mutableAtom
|
|
7
|
+
import { atomFamily, join, mutableAtom } from "atom.io"
|
|
9
8
|
import type { RoomKey, Socket, SocketKey, UserKey } from "atom.io/realtime"
|
|
10
9
|
import { isSocketKey, isUserKey } from "atom.io/realtime"
|
|
11
10
|
import { UList } from "atom.io/transceivers/u-list"
|
|
@@ -46,9 +45,3 @@ export const usersOfSockets: JoinToken<
|
|
|
46
45
|
isAType: isUserKey,
|
|
47
46
|
isBType: isSocketKey,
|
|
48
47
|
})
|
|
49
|
-
|
|
50
|
-
export const selfListSelectors: PureSelectorFamilyToken<UserKey[], UserKey> =
|
|
51
|
-
selectorFamily({
|
|
52
|
-
key: `selfList`,
|
|
53
|
-
get: (userKey) => () => [userKey],
|
|
54
|
-
})
|