@pluv/client 5.0.1 → 5.0.3
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/index.d.mts +355 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +1765 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +8 -8
- package/CHANGELOG.md +0 -2415
- package/eslint.config.mjs +0 -4
- package/src/AbstractStorageStore.ts +0 -19
- package/src/CrdtManager.ts +0 -119
- package/src/CrdtNotifier.ts +0 -46
- package/src/EventNotifier.ts +0 -39
- package/src/ListenerManager.ts +0 -21
- package/src/MockedRoom.ts +0 -457
- package/src/PluvClient.ts +0 -204
- package/src/PluvProcedure.ts +0 -87
- package/src/PluvRoom.ts +0 -1610
- package/src/PluvRouter.ts +0 -71
- package/src/StateNotifier.ts +0 -41
- package/src/StorageStore.ts +0 -31
- package/src/UsersManager.ts +0 -341
- package/src/UsersNotifier.ts +0 -66
- package/src/constants.ts +0 -2
- package/src/createClient.ts +0 -14
- package/src/index.ts +0 -36
- package/src/infer.ts +0 -12
- package/src/register.ts +0 -20
- package/src/types.ts +0 -78
- package/src/utils/debounce.ts +0 -27
- package/src/utils/identity.ts +0 -1
- package/src/utils/index.ts +0 -3
- package/src/utils/pickBy.ts +0 -16
- package/tsconfig.json +0 -5
- package/tsdown.config.ts +0 -14
package/src/PluvRouter.ts
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import type { AbstractCrdtDocFactory } from "@pluv/crdt";
|
|
2
|
-
import type { IOLike, IORouterLike, JsonObject } from "@pluv/types";
|
|
3
|
-
import type { PluvProcedure } from "./PluvProcedure";
|
|
4
|
-
|
|
5
|
-
export type PluvRouterEventConfig<
|
|
6
|
-
TIO extends IOLike,
|
|
7
|
-
TPresence extends Record<string, any>,
|
|
8
|
-
TCrdt extends AbstractCrdtDocFactory<any, any>,
|
|
9
|
-
> = { [P: string]: Pick<PluvProcedure<TIO, any, any, TPresence, TCrdt, "">, "config"> };
|
|
10
|
-
|
|
11
|
-
export type MergedRouter<
|
|
12
|
-
TRouters extends PluvRouter<TIO, TPresence, TCrdt, any>[],
|
|
13
|
-
TIO extends IOLike,
|
|
14
|
-
TPresence extends Record<string, any>,
|
|
15
|
-
TCrdt extends AbstractCrdtDocFactory<any, any>,
|
|
16
|
-
TRoot extends TRouters[0]["_defs"]["events"],
|
|
17
|
-
> = TRouters extends [
|
|
18
|
-
infer IHead extends PluvRouter<TIO, TPresence, TCrdt, any>,
|
|
19
|
-
...infer ITail extends PluvRouter<TIO, TPresence, TCrdt, any>[],
|
|
20
|
-
]
|
|
21
|
-
? MergedRouter<ITail, TIO, TPresence, TCrdt, TRoot & IHead["_defs"]["events"]>
|
|
22
|
-
: PluvRouter<TIO, TPresence, TCrdt, TRoot>;
|
|
23
|
-
|
|
24
|
-
export class PluvRouter<
|
|
25
|
-
TIO extends IOLike,
|
|
26
|
-
TPresence extends Record<string, any>,
|
|
27
|
-
TCrdt extends AbstractCrdtDocFactory<any, any>,
|
|
28
|
-
TEvents extends PluvRouterEventConfig<TIO, TPresence, TCrdt> = {},
|
|
29
|
-
> implements IORouterLike<TEvents> {
|
|
30
|
-
readonly _defs: { events: TEvents } = { events: {} as TEvents };
|
|
31
|
-
|
|
32
|
-
constructor(events: TEvents) {
|
|
33
|
-
const invalidName = Object.keys(events).find((name) => !this._isValidEventName(name));
|
|
34
|
-
|
|
35
|
-
if (typeof invalidName === "string") {
|
|
36
|
-
throw new Error(
|
|
37
|
-
`Invalid event name. Event names must be formatted as valid JavaScript variable names: "${invalidName}"`,
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
this._defs = { events };
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
public static merge<
|
|
45
|
-
TRouters extends PluvRouter<TIO, TPresence, TCrdt, any>[],
|
|
46
|
-
TIO extends IOLike,
|
|
47
|
-
TPresence extends Record<string, any>,
|
|
48
|
-
TCrdt extends AbstractCrdtDocFactory<any, any>,
|
|
49
|
-
>(...routers: TRouters): MergedRouter<TRouters, TIO, TPresence, TCrdt, {}> {
|
|
50
|
-
const events = Object.assign(
|
|
51
|
-
Object.create(null),
|
|
52
|
-
...routers.map((router) => router._defs.events),
|
|
53
|
-
);
|
|
54
|
-
|
|
55
|
-
if (Object.keys(events).some((key) => key.startsWith("$"))) {
|
|
56
|
-
throw new Error('Procedures may not start with "$"');
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return new PluvRouter<any, any, any, any>(events) as MergedRouter<
|
|
60
|
-
TRouters,
|
|
61
|
-
TIO,
|
|
62
|
-
TPresence,
|
|
63
|
-
TCrdt,
|
|
64
|
-
{}
|
|
65
|
-
>;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
private _isValidEventName(name: string): boolean {
|
|
69
|
-
return /^[a-z_$][a-z0-9_$]*$/gi.test(name);
|
|
70
|
-
}
|
|
71
|
-
}
|
package/src/StateNotifier.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
Id,
|
|
3
|
-
IOLike,
|
|
4
|
-
JsonObject,
|
|
5
|
-
StateNotifierSubjects,
|
|
6
|
-
SubscriptionCallback,
|
|
7
|
-
UserInfo,
|
|
8
|
-
WebSocketState,
|
|
9
|
-
} from "@pluv/types";
|
|
10
|
-
import type { Subject } from "wonka";
|
|
11
|
-
import { makeSubject, subscribe } from "wonka";
|
|
12
|
-
|
|
13
|
-
type InferSubjectValue<
|
|
14
|
-
TIO extends IOLike,
|
|
15
|
-
TPresence extends Record<string, any>,
|
|
16
|
-
TSubject extends keyof StateNotifierSubjects<TIO, TPresence>,
|
|
17
|
-
> =
|
|
18
|
-
StateNotifierSubjects<TIO, TPresence>[TSubject] extends Subject<infer IValue>
|
|
19
|
-
? Id<IValue>
|
|
20
|
-
: never;
|
|
21
|
-
|
|
22
|
-
export class StateNotifier<TIO extends IOLike, TPresence extends Record<string, any> = {}> {
|
|
23
|
-
public subjects: StateNotifierSubjects<TIO, TPresence> = {
|
|
24
|
-
connection: makeSubject<Id<WebSocketState<TIO>>>(),
|
|
25
|
-
"my-presence": makeSubject<TPresence>(),
|
|
26
|
-
myself: makeSubject<Readonly<Id<UserInfo<TIO, TPresence>>> | null>(),
|
|
27
|
-
others: makeSubject<readonly Id<UserInfo<TIO, TPresence>>[]>(),
|
|
28
|
-
"storage-loaded": makeSubject<true>(),
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
public subscribe<TSubject extends keyof StateNotifierSubjects<TIO, TPresence>>(
|
|
32
|
-
name: TSubject,
|
|
33
|
-
callback: SubscriptionCallback<TIO, TPresence, TSubject>,
|
|
34
|
-
): () => void {
|
|
35
|
-
const source = this.subjects[name]?.source as any;
|
|
36
|
-
|
|
37
|
-
if (!source) throw new Error(`Could not subscribe to source: ${name}`);
|
|
38
|
-
|
|
39
|
-
return subscribe(callback)(source).unsubscribe;
|
|
40
|
-
}
|
|
41
|
-
}
|
package/src/StorageStore.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { AbstractStorageStore } from "./AbstractStorageStore";
|
|
2
|
-
|
|
3
|
-
export class StorageStore extends AbstractStorageStore {
|
|
4
|
-
public addUpdate() {
|
|
5
|
-
return Promise.resolve();
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
public applyUpdates() {
|
|
9
|
-
return Promise.resolve();
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
public destroy() {
|
|
13
|
-
return Promise.resolve();
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
public flatten() {
|
|
17
|
-
return Promise.resolve();
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
public getShouldFlatten() {
|
|
21
|
-
return Promise.resolve(false);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
public getUpdates() {
|
|
25
|
-
return Promise.resolve([]);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
public initialize() {
|
|
29
|
-
return Promise.resolve();
|
|
30
|
-
}
|
|
31
|
-
}
|
package/src/UsersManager.ts
DELETED
|
@@ -1,341 +0,0 @@
|
|
|
1
|
-
import type { InputZodLike, IOLike, JsonObject, OptionalProps, UserInfo } from "@pluv/types";
|
|
2
|
-
import { PLUV_PRESENCE_META_KEY } from "./constants";
|
|
3
|
-
import type { PluvClientLimits } from "./types";
|
|
4
|
-
import { pickBy } from "./utils";
|
|
5
|
-
|
|
6
|
-
export type Presence = Record<string, unknown>;
|
|
7
|
-
|
|
8
|
-
export type UsersManagerConfig<TPresence extends Record<string, any> = {}> = {
|
|
9
|
-
initialPresence?: TPresence;
|
|
10
|
-
limits: PluvClientLimits;
|
|
11
|
-
presence?: InputZodLike<TPresence>;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export type AddConnectionResult<TIO extends IOLike, TPresence extends Record<string, any> = {}> = {
|
|
15
|
-
clientId: string;
|
|
16
|
-
data: UserInfo<TIO, TPresence>;
|
|
17
|
-
isMyself: boolean;
|
|
18
|
-
remaining: number;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export type DeleteConnectionResult<
|
|
22
|
-
TIO extends IOLike,
|
|
23
|
-
TPresence extends Record<string, any> = {},
|
|
24
|
-
> = {
|
|
25
|
-
clientId: string;
|
|
26
|
-
data: UserInfo<TIO, TPresence>;
|
|
27
|
-
remaining: number;
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
export class UsersManager<TIO extends IOLike, TPresence extends Record<string, any> = {}> {
|
|
31
|
-
public readonly initialPresence: TPresence;
|
|
32
|
-
|
|
33
|
-
private readonly _idMap = {
|
|
34
|
-
fromConnectionId: new Map<[connectionId: string][0], [clientId: string][0]>(),
|
|
35
|
-
fromClientId: new Map<[clientId: string][0], Set<[connectionId: string][0]>>(),
|
|
36
|
-
};
|
|
37
|
-
private _limits: PluvClientLimits;
|
|
38
|
-
/**
|
|
39
|
-
* @description This presence can be updated while the user is not
|
|
40
|
-
* connected.
|
|
41
|
-
*/
|
|
42
|
-
private _myPresence: TPresence;
|
|
43
|
-
/**
|
|
44
|
-
* @description This is only set when the user is connected.
|
|
45
|
-
*/
|
|
46
|
-
private _myself: UserInfo<TIO, TPresence> | null = null;
|
|
47
|
-
private _others = new Map<[clientId: string][0], UserInfo<TIO, TPresence>>();
|
|
48
|
-
private _presence: InputZodLike<TPresence> | null = null;
|
|
49
|
-
|
|
50
|
-
constructor(config: UsersManagerConfig<TPresence>) {
|
|
51
|
-
const { initialPresence, limits, presence = null } = config;
|
|
52
|
-
|
|
53
|
-
this.initialPresence = initialPresence as TPresence;
|
|
54
|
-
|
|
55
|
-
this._limits = limits;
|
|
56
|
-
this._myPresence = initialPresence as TPresence;
|
|
57
|
-
this._presence = presence;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
public get myPresence(): TPresence {
|
|
61
|
-
return this._myPresence;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
public get myself(): UserInfo<TIO, TPresence> | null {
|
|
65
|
-
return this._myself;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
public addConnection(
|
|
69
|
-
userInfo: OptionalProps<UserInfo<TIO, TPresence>, "presence">,
|
|
70
|
-
): AddConnectionResult<TIO, TPresence> {
|
|
71
|
-
/**
|
|
72
|
-
* @description Ensure that the presence is complete, so that it does not fail schema
|
|
73
|
-
* validation
|
|
74
|
-
* @date April 20, 2025
|
|
75
|
-
*/
|
|
76
|
-
const cleaned = pickBy(userInfo.presence ?? {}, (value) => typeof value !== "undefined");
|
|
77
|
-
const presence = { ...this.initialPresence, ...cleaned };
|
|
78
|
-
const data: UserInfo<TIO, TPresence> = { ...userInfo, presence };
|
|
79
|
-
|
|
80
|
-
const myClientId = !!this._myself ? this.getClientId(this._myself) : null;
|
|
81
|
-
const clientId = this.getClientId(data);
|
|
82
|
-
const remaining = this._setConnectionId(userInfo.connectionId, clientId).size;
|
|
83
|
-
|
|
84
|
-
if (myClientId === clientId) {
|
|
85
|
-
const result: AddConnectionResult<TIO, TPresence> = {
|
|
86
|
-
clientId,
|
|
87
|
-
data,
|
|
88
|
-
isMyself: true,
|
|
89
|
-
remaining,
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
return result;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const other = this._others.get(clientId);
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* !HACK
|
|
99
|
-
* @description Don't override the other user, so that we ensure the user's presence isn't
|
|
100
|
-
* overwritten by whatever incoming connection.
|
|
101
|
-
* @date April 16, 2025
|
|
102
|
-
*/
|
|
103
|
-
if (!other) this._others.set(clientId, data);
|
|
104
|
-
|
|
105
|
-
const result: AddConnectionResult<TIO, TPresence> = {
|
|
106
|
-
clientId,
|
|
107
|
-
data,
|
|
108
|
-
isMyself: false,
|
|
109
|
-
remaining,
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
return result;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
public clearConnections(): void {
|
|
116
|
-
this.removeMyself();
|
|
117
|
-
this._others.clear();
|
|
118
|
-
this._idMap.fromClientId.clear();
|
|
119
|
-
this._idMap.fromConnectionId.clear();
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
public deleteConnection(connectionId: string): DeleteConnectionResult<TIO, TPresence> | null {
|
|
123
|
-
const clientId = this.getClientId(connectionId);
|
|
124
|
-
|
|
125
|
-
this._deleteConnectionId(connectionId);
|
|
126
|
-
|
|
127
|
-
// We expect that a clientId should always exist here, but we'll just check this anyways
|
|
128
|
-
if (!clientId) return null;
|
|
129
|
-
|
|
130
|
-
const userInfo = this._others.get(clientId) ?? null;
|
|
131
|
-
|
|
132
|
-
// We also expect this should always exist here, but we'll just check this anyways
|
|
133
|
-
if (!userInfo) return null;
|
|
134
|
-
|
|
135
|
-
const remaining = this._getConnectionIds(clientId)?.size ?? 0;
|
|
136
|
-
const result: DeleteConnectionResult<TIO, TPresence> = {
|
|
137
|
-
clientId,
|
|
138
|
-
remaining,
|
|
139
|
-
data: userInfo,
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* @description A user may have multiple websocket connections open. If one connection is
|
|
144
|
-
* dropped, the user may still be connected via another websocket (e.g. on anther browser
|
|
145
|
-
* tab). In this case, we just want to remove the connection mapping for the connection
|
|
146
|
-
* that is dropped, but keep the remaining connections and the user.
|
|
147
|
-
* @date April 16, 2025
|
|
148
|
-
*/
|
|
149
|
-
if (!remaining) this._others.delete(clientId);
|
|
150
|
-
|
|
151
|
-
return result;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* @description The client id is either the user's id (if authorized) or the connection id
|
|
156
|
-
* otherwise. This is so that, despite having multiple connections, the user will only have one
|
|
157
|
-
* presence to all other users.
|
|
158
|
-
* @date April 16, 2025
|
|
159
|
-
*/
|
|
160
|
-
public getClientId(connectionId: string): string | null;
|
|
161
|
-
public getClientId(userInfo: UserInfo<TIO, TPresence>): string;
|
|
162
|
-
public getClientId(input: string | UserInfo<TIO, TPresence>): string | null {
|
|
163
|
-
if (typeof input !== "string") {
|
|
164
|
-
const userInfo = input;
|
|
165
|
-
const connectionId = userInfo.connectionId;
|
|
166
|
-
const userId = userInfo.user?.id ?? null;
|
|
167
|
-
|
|
168
|
-
return userId ?? connectionId;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const connectionId = input;
|
|
172
|
-
|
|
173
|
-
if (this._myself?.connectionId === connectionId) {
|
|
174
|
-
return this.getClientId(this._myself);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const clientId = this._idMap.fromConnectionId.get(connectionId) ?? null;
|
|
178
|
-
|
|
179
|
-
return clientId;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
public getOther(connectionId: string): UserInfo<TIO, TPresence> | null {
|
|
183
|
-
const clientId = this.getClientId(connectionId);
|
|
184
|
-
|
|
185
|
-
if (!clientId) return null;
|
|
186
|
-
|
|
187
|
-
return this._others.get(clientId) ?? null;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
public getOthers(): readonly UserInfo<TIO, TPresence>[] {
|
|
191
|
-
return Array.from(this._others.values());
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
public patchPresence(connectionId: string, patch: Partial<TPresence>): TPresence | null {
|
|
195
|
-
const clientId = this.getClientId(connectionId);
|
|
196
|
-
const myClientId = !!this._myself ? this.getClientId(this._myself) : null;
|
|
197
|
-
|
|
198
|
-
if (!clientId) return null;
|
|
199
|
-
if (myClientId === clientId) return this.updateMyPresence(patch);
|
|
200
|
-
|
|
201
|
-
const other = this._others.get(clientId);
|
|
202
|
-
|
|
203
|
-
if (!other) return null;
|
|
204
|
-
|
|
205
|
-
const cleanedPatch = pickBy(patch ?? {}, (value) => typeof value !== "undefined");
|
|
206
|
-
const cleanedPresence = pickBy(
|
|
207
|
-
other.presence ?? {},
|
|
208
|
-
(value) => typeof value !== "undefined",
|
|
209
|
-
);
|
|
210
|
-
const presence = {
|
|
211
|
-
...this.initialPresence,
|
|
212
|
-
...cleanedPresence,
|
|
213
|
-
...cleanedPatch,
|
|
214
|
-
} as TPresence;
|
|
215
|
-
const validated = this._presence ? this._presence.parse(presence) : presence;
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* !HACK
|
|
219
|
-
* @description We're patching internal metadata back into the presence field so it doesn't
|
|
220
|
-
* get stripped from validation
|
|
221
|
-
* @date May 13, 2025
|
|
222
|
-
*/
|
|
223
|
-
if (!!presence[PLUV_PRESENCE_META_KEY]) {
|
|
224
|
-
(validated as any)[PLUV_PRESENCE_META_KEY] = presence[PLUV_PRESENCE_META_KEY];
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
this._others.set(clientId, { ...other, presence: validated });
|
|
228
|
-
|
|
229
|
-
return validated;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
public removeMyself(): void {
|
|
233
|
-
if (!this._myself) return;
|
|
234
|
-
|
|
235
|
-
const connectionId = this._myself.connectionId;
|
|
236
|
-
|
|
237
|
-
this._deleteConnectionId(connectionId);
|
|
238
|
-
this._myself = null;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
public setMyself(userInfo: OptionalProps<UserInfo<TIO, TPresence>, "presence">): void {
|
|
242
|
-
const connectionId = userInfo.connectionId;
|
|
243
|
-
const presence = userInfo.presence ?? this.initialPresence;
|
|
244
|
-
|
|
245
|
-
this._myself = { ...userInfo, presence };
|
|
246
|
-
this._myPresence = presence;
|
|
247
|
-
|
|
248
|
-
const clientId = this.getClientId(this._myself);
|
|
249
|
-
|
|
250
|
-
this._setConnectionId(connectionId, clientId);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
public setPresence(connectionId: string, presence: TPresence): void {
|
|
254
|
-
const clientId = this.getClientId(connectionId);
|
|
255
|
-
const myClientId = !!this._myself ? this.getClientId(this._myself) : null;
|
|
256
|
-
|
|
257
|
-
if (!clientId) return;
|
|
258
|
-
|
|
259
|
-
if (myClientId === clientId) {
|
|
260
|
-
if (!this._myself) return;
|
|
261
|
-
|
|
262
|
-
this._myself.presence = presence;
|
|
263
|
-
this._myPresence = presence;
|
|
264
|
-
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
const other = this._others.get(clientId);
|
|
269
|
-
|
|
270
|
-
if (!other) return;
|
|
271
|
-
|
|
272
|
-
this._others.set(clientId, { ...other, presence });
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* @description This method need not care about being connected. This is because a user should
|
|
277
|
-
* be able to view and update their own presence without being online
|
|
278
|
-
* @date April 20, 2025
|
|
279
|
-
*/
|
|
280
|
-
public updateMyPresence(patch: Partial<TPresence>): TPresence {
|
|
281
|
-
const cleanedPatch = pickBy(patch, (value) => typeof value !== "undefined");
|
|
282
|
-
const cleanedPresence = pickBy(this._myPresence, (value) => typeof value !== "undefined");
|
|
283
|
-
const updated = {
|
|
284
|
-
...this.initialPresence,
|
|
285
|
-
...cleanedPresence,
|
|
286
|
-
...cleanedPatch,
|
|
287
|
-
} as TPresence;
|
|
288
|
-
const bytes = new TextEncoder().encode(JSON.stringify(updated)).length;
|
|
289
|
-
|
|
290
|
-
if (!!this._limits.presenceMaxSize && bytes > this._limits.presenceMaxSize) {
|
|
291
|
-
throw new Error(
|
|
292
|
-
`Large presence. Presence must be at most 512 bytes. Current size: ${bytes.toLocaleString()}`,
|
|
293
|
-
);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
this._myPresence = updated;
|
|
297
|
-
|
|
298
|
-
if (!this._myself) return updated;
|
|
299
|
-
|
|
300
|
-
this._myself.presence = updated;
|
|
301
|
-
|
|
302
|
-
return updated;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
private _deleteConnectionId(connectionId: string): void {
|
|
306
|
-
const clientId = this.getClientId(connectionId);
|
|
307
|
-
|
|
308
|
-
this._idMap.fromConnectionId.delete(connectionId);
|
|
309
|
-
|
|
310
|
-
if (!clientId) return;
|
|
311
|
-
|
|
312
|
-
const set = this._idMap.fromClientId.get(clientId) ?? null;
|
|
313
|
-
|
|
314
|
-
if (!set) return;
|
|
315
|
-
|
|
316
|
-
set.delete(connectionId);
|
|
317
|
-
|
|
318
|
-
if (!!set.size) return;
|
|
319
|
-
|
|
320
|
-
this._idMap.fromClientId.delete(clientId);
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
private _getConnectionIds(clientId: string): Set<[connectionId: string][0]> | null {
|
|
324
|
-
const set = this._idMap.fromClientId.get(clientId);
|
|
325
|
-
|
|
326
|
-
return set ?? null;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
private _setConnectionId(
|
|
330
|
-
connectionId: string,
|
|
331
|
-
clientId: string,
|
|
332
|
-
): Set<[connectionId: string][0]> {
|
|
333
|
-
const set = this._idMap.fromClientId.get(clientId) ?? new Set<string>();
|
|
334
|
-
const updated = set.add(connectionId);
|
|
335
|
-
|
|
336
|
-
this._idMap.fromConnectionId.set(connectionId, clientId);
|
|
337
|
-
this._idMap.fromClientId.set(clientId, updated);
|
|
338
|
-
|
|
339
|
-
return updated;
|
|
340
|
-
}
|
|
341
|
-
}
|
package/src/UsersNotifier.ts
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
Id,
|
|
3
|
-
IOLike,
|
|
4
|
-
JsonObject,
|
|
5
|
-
OtherSubscriptionCallback,
|
|
6
|
-
OthersSubscriptionCallback,
|
|
7
|
-
OthersSubscriptionEvent,
|
|
8
|
-
UserInfo,
|
|
9
|
-
} from "@pluv/types";
|
|
10
|
-
import type { Subject } from "wonka";
|
|
11
|
-
import { makeSubject, subscribe, TypeOfSource } from "wonka";
|
|
12
|
-
|
|
13
|
-
export class UsersNotifier<TIO extends IOLike, TPresence extends Record<string, any> = {}> {
|
|
14
|
-
public readonly others = makeSubject<{
|
|
15
|
-
others: readonly Id<UserInfo<TIO, TPresence>>[];
|
|
16
|
-
event: OthersSubscriptionEvent<TIO, TPresence>;
|
|
17
|
-
}>();
|
|
18
|
-
|
|
19
|
-
private _otherSubjects = new Map<
|
|
20
|
-
[clientId: string][0],
|
|
21
|
-
Subject<Id<UserInfo<TIO, TPresence>> | null>
|
|
22
|
-
>();
|
|
23
|
-
|
|
24
|
-
public clear(): void {
|
|
25
|
-
this._otherSubjects.forEach((subject) => {
|
|
26
|
-
subject.next(null);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
this._otherSubjects.clear();
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
public delete(clientId: string): void {
|
|
33
|
-
this.other(clientId).next(null);
|
|
34
|
-
this._otherSubjects.delete(clientId);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
public other(clientId: string): Subject<Id<UserInfo<TIO, TPresence>> | null> {
|
|
38
|
-
const subject = this._otherSubjects.get(clientId);
|
|
39
|
-
|
|
40
|
-
if (subject) return subject;
|
|
41
|
-
|
|
42
|
-
const newSubject = makeSubject<Id<UserInfo<TIO, TPresence>>>();
|
|
43
|
-
|
|
44
|
-
this._otherSubjects.set(clientId, newSubject);
|
|
45
|
-
|
|
46
|
-
return newSubject;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
public subscribeOther(
|
|
50
|
-
clientId: string,
|
|
51
|
-
callback: OtherSubscriptionCallback<TIO, TPresence>,
|
|
52
|
-
): () => void {
|
|
53
|
-
const source = this.other(clientId).source;
|
|
54
|
-
const subscription = subscribe(callback)(source);
|
|
55
|
-
|
|
56
|
-
return subscription.unsubscribe;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
public subscribeOthers(callback: OthersSubscriptionCallback<TIO, TPresence>): () => void {
|
|
60
|
-
const subscription = subscribe<TypeOfSource<typeof this.others.source>>(
|
|
61
|
-
({ others, event }) => callback(others, event),
|
|
62
|
-
)(this.others.source);
|
|
63
|
-
|
|
64
|
-
return subscription.unsubscribe;
|
|
65
|
-
}
|
|
66
|
-
}
|
package/src/constants.ts
DELETED
package/src/createClient.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { InferIOCrdtKind, IOLike, JsonObject } from "@pluv/types";
|
|
2
|
-
import type { PluvClientOptions } from "./PluvClient";
|
|
3
|
-
import { PluvClient } from "./PluvClient";
|
|
4
|
-
|
|
5
|
-
export const createClient = <
|
|
6
|
-
TIO extends IOLike<any, any, any>,
|
|
7
|
-
TPresence extends Record<string, any> = {},
|
|
8
|
-
TCrdt extends InferIOCrdtKind<TIO> = InferIOCrdtKind<TIO>,
|
|
9
|
-
TMetadata extends JsonObject = {},
|
|
10
|
-
>(
|
|
11
|
-
options: PluvClientOptions<TIO, TPresence, TCrdt, TMetadata>,
|
|
12
|
-
): PluvClient<TIO, TPresence, TCrdt, TMetadata> => {
|
|
13
|
-
return new PluvClient<TIO, TPresence, TCrdt, TMetadata>(options);
|
|
14
|
-
};
|
package/src/index.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
export type { CrdtType, InferCrdtJson } from "@pluv/crdt";
|
|
2
|
-
export { ConnectionState } from "@pluv/types";
|
|
3
|
-
export type {
|
|
4
|
-
BaseUser,
|
|
5
|
-
EventMessage,
|
|
6
|
-
EventNotifierSubscriptionCallback,
|
|
7
|
-
EventRecord,
|
|
8
|
-
IOEventMessage,
|
|
9
|
-
MergeEvents,
|
|
10
|
-
RoomLike,
|
|
11
|
-
UserInfo,
|
|
12
|
-
WebSocketConnection,
|
|
13
|
-
WebSocketState,
|
|
14
|
-
} from "@pluv/types";
|
|
15
|
-
export { AbstractStorageStore } from "./AbstractStorageStore";
|
|
16
|
-
export { createClient } from "./createClient";
|
|
17
|
-
export { infer } from "./infer";
|
|
18
|
-
export type { InferCallback } from "./infer";
|
|
19
|
-
export { MockedRoom } from "./MockedRoom";
|
|
20
|
-
export type { MockedRoomConfig, MockedRoomEvents } from "./MockedRoom";
|
|
21
|
-
export { PluvClient } from "./PluvClient";
|
|
22
|
-
export type { CreateRoomOptions, EnterRoomParams, PluvClientOptions } from "./PluvClient";
|
|
23
|
-
export type { PluvProcedureConfig } from "./PluvProcedure";
|
|
24
|
-
export { PluvRoom } from "./PluvRoom";
|
|
25
|
-
export type {
|
|
26
|
-
PluvRoomAddon,
|
|
27
|
-
PluvRoomAddonInput,
|
|
28
|
-
PluvRoomAddonResult,
|
|
29
|
-
PluvRoomDebug,
|
|
30
|
-
RoomConnectParams,
|
|
31
|
-
RoomEndpoints,
|
|
32
|
-
} from "./PluvRoom";
|
|
33
|
-
export type { PluvRouter, PluvRouterEventConfig } from "./PluvRouter";
|
|
34
|
-
export { register } from "./register";
|
|
35
|
-
export type { RegisterParams } from "./register";
|
|
36
|
-
export type { InferMetadata, PublicKey, PublicKeyParams } from "./types";
|
package/src/infer.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { InferIOAuthorize, InferIOCrdt, InferIOEvents, IOLike } from "@pluv/types";
|
|
2
|
-
import type { identity } from "./utils";
|
|
3
|
-
|
|
4
|
-
export type InferCallback<TIO extends IOLike<any, any, any>> = (i: typeof identity) => {
|
|
5
|
-
io: (io: TIO) => TIO;
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
export const infer = <TIO extends IOLike<any, any, any>>(
|
|
9
|
-
callback: InferCallback<TIO>,
|
|
10
|
-
): InferCallback<IOLike<InferIOAuthorize<TIO>, InferIOCrdt<TIO>, InferIOEvents<TIO>>> => {
|
|
11
|
-
return callback as any;
|
|
12
|
-
};
|
package/src/register.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import type { JsonObject } from "@pluv/types";
|
|
2
|
-
import type { AuthEndpoint, WsEndpoint } from "./PluvRoom";
|
|
3
|
-
|
|
4
|
-
export interface RegisterParams<TMetadata extends JsonObject> {
|
|
5
|
-
authEndpoint: AuthEndpoint<TMetadata>;
|
|
6
|
-
publicKey: string;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export const register = <TMetadata extends JsonObject>(params: RegisterParams<TMetadata>) => {
|
|
10
|
-
const { authEndpoint, publicKey } = params;
|
|
11
|
-
|
|
12
|
-
const wsEndpoint: WsEndpoint<TMetadata> = ({ room }) =>
|
|
13
|
-
`wss://connect.pluv.io/api/room?room=${room}`;
|
|
14
|
-
|
|
15
|
-
return {
|
|
16
|
-
authEndpoint,
|
|
17
|
-
publicKey,
|
|
18
|
-
wsEndpoint,
|
|
19
|
-
};
|
|
20
|
-
};
|