@pluv/react 0.0.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/.eslintrc.js +4 -0
- package/.turbo/turbo-build.log +17 -0
- package/dist/index.d.ts +57 -0
- package/dist/index.js +320 -0
- package/dist/index.mjs +286 -0
- package/package.json +36 -0
- package/src/createBundle.tsx +71 -0
- package/src/createRoomBundle.tsx +447 -0
- package/src/index.ts +16 -0
- package/src/internal/hooks/index.ts +3 -0
- package/src/internal/hooks/useRerender.ts +9 -0
- package/src/internal/hooks/useSyncExternalStore.ts +14 -0
- package/src/internal/hooks/useSyncExternalStoreWithSelector.ts +1 -0
- package/src/internal/index.ts +6 -0
- package/src/internal/utils/identity.ts +1 -0
- package/src/internal/utils/index.ts +2 -0
- package/src/internal/utils/shallowArrayEqual.ts +6 -0
- package/tsconfig.json +5 -0
package/.eslintrc.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
|
|
2
|
+
> @pluv/react@0.0.0 build /home/runner/work/pluv/pluv/packages/react
|
|
3
|
+
> tsup src/index.ts --format esm,cjs --dts --external react
|
|
4
|
+
|
|
5
|
+
[34mCLI[39m Building entry: src/index.ts
|
|
6
|
+
[34mCLI[39m Using tsconfig: tsconfig.json
|
|
7
|
+
[34mCLI[39m tsup v6.5.0
|
|
8
|
+
[34mCLI[39m Target: es6
|
|
9
|
+
[34mESM[39m Build start
|
|
10
|
+
[34mCJS[39m Build start
|
|
11
|
+
[32mCJS[39m [1mdist/index.js [22m[32m9.71 KB[39m
|
|
12
|
+
[32mCJS[39m ⚡️ Build success in 107ms
|
|
13
|
+
[32mESM[39m [1mdist/index.mjs [22m[32m7.71 KB[39m
|
|
14
|
+
[32mESM[39m ⚡️ Build success in 112ms
|
|
15
|
+
[34mDTS[39m Build start
|
|
16
|
+
[32mDTS[39m ⚡️ Build success in 4395ms
|
|
17
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m3.92 KB[39m
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { PluvRoomDebug, WebSocketConnection, UserInfo, PluvRoom, InferYjsSharedTypeJson, PluvClient } from '@pluv/client';
|
|
2
|
+
export { AbstractType, ConnectionState, createClient, y } from '@pluv/client';
|
|
3
|
+
import * as _pluv_types from '@pluv/types';
|
|
4
|
+
import { JsonObject, InputZodLike, IOLike, InferEventMessage, InferIOInput, Id, InferIOOutput, IOEventMessage } from '@pluv/types';
|
|
5
|
+
export { BaseUser, EventMessage, EventRecord, IOEventMessage } from '@pluv/types';
|
|
6
|
+
import { ReactNode, FC, Dispatch } from 'react';
|
|
7
|
+
import { AbstractType } from 'yjs';
|
|
8
|
+
|
|
9
|
+
interface CreateRoomBundleOptions<TPresence extends JsonObject = {}, TStorage extends Record<string, AbstractType<any>> = {}> {
|
|
10
|
+
initialStorage?: () => TStorage;
|
|
11
|
+
presence?: InputZodLike<TPresence>;
|
|
12
|
+
}
|
|
13
|
+
type PluvRoomProviderProps<TIO extends IOLike, TPresence extends JsonObject, TStorage extends Record<string, AbstractType<any>>> = {
|
|
14
|
+
children?: ReactNode;
|
|
15
|
+
debug?: boolean | PluvRoomDebug<TIO>;
|
|
16
|
+
onAuthorizationFail?: (error: Error) => void;
|
|
17
|
+
room: string;
|
|
18
|
+
} & (keyof TPresence extends never ? {
|
|
19
|
+
initialPresence?: never;
|
|
20
|
+
} : {
|
|
21
|
+
initialPresence: TPresence;
|
|
22
|
+
}) & (keyof TStorage extends never ? {
|
|
23
|
+
initialStorage?: never;
|
|
24
|
+
} : {
|
|
25
|
+
initialStorage?: () => TStorage;
|
|
26
|
+
});
|
|
27
|
+
interface SubscriptionHookOptions<T extends unknown> {
|
|
28
|
+
isEqual?: (a: T, b: T) => boolean;
|
|
29
|
+
}
|
|
30
|
+
type UpdateMyPresenceAction<TPresence extends JsonObject> = Partial<TPresence> | ((oldPresence: TPresence | null) => Partial<TPresence>);
|
|
31
|
+
interface CreateRoomBundle<TIO extends IOLike, TPresence extends JsonObject, TStorage extends Record<string, AbstractType<any>>> {
|
|
32
|
+
PluvRoomProvider: FC<PluvRoomProviderProps<TIO, TPresence, TStorage>>;
|
|
33
|
+
usePluvBroadcast: <TMessage extends InferEventMessage<InferIOInput<TIO>>>() => (message: TMessage) => void;
|
|
34
|
+
usePluvConnection: <T extends unknown = WebSocketConnection>(selector: (connection: WebSocketConnection) => T, options?: SubscriptionHookOptions<Id<T>>) => Id<T>;
|
|
35
|
+
usePluvEvent: <TType extends keyof InferIOOutput<TIO>>(type: TType, callback: (data: Id<IOEventMessage<TIO, TType>>) => void) => void;
|
|
36
|
+
usePluvMyPresence: <T extends unknown = TPresence>(selector?: (myPresence: TPresence) => T, options?: SubscriptionHookOptions<Id<T> | null>) => [
|
|
37
|
+
myPresence: Id<T> | null,
|
|
38
|
+
updateMyPresence: Dispatch<UpdateMyPresenceAction<TPresence>>
|
|
39
|
+
];
|
|
40
|
+
usePluvMyself: <T extends unknown = UserInfo<TIO, TPresence>>(selector?: (myself: Id<UserInfo<TIO, TPresence>>) => T, options?: SubscriptionHookOptions<Id<T> | null>) => Id<T> | null;
|
|
41
|
+
usePluvOther: <T extends unknown = UserInfo<TIO, TPresence>>(connectionId: string, selector?: (other: UserInfo<TIO, TPresence>) => T, options?: SubscriptionHookOptions<Id<T> | null>) => Id<T> | null;
|
|
42
|
+
usePluvOthers: <T extends unknown = UserInfo<TIO, TPresence>>(selector?: (other: readonly Id<UserInfo<TIO, TPresence>>[]) => T[], options?: SubscriptionHookOptions<readonly Id<T>[]>) => readonly Id<T>[];
|
|
43
|
+
usePluvRoom: () => PluvRoom<TIO, TPresence>;
|
|
44
|
+
usePluvStorage: <TKey extends keyof TStorage, TData extends unknown = InferYjsSharedTypeJson<TStorage[TKey]>>(key: TKey, selector?: (data: InferYjsSharedTypeJson<TStorage[TKey]>) => TData, options?: SubscriptionHookOptions<TData | null>) => [data: TData | null, sharedType: TStorage[TKey] | null];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface PluvProviderProps {
|
|
48
|
+
children?: ReactNode;
|
|
49
|
+
}
|
|
50
|
+
interface CreateBundle<TIO extends IOLike> {
|
|
51
|
+
createRoomBundle: <TPresence extends JsonObject = {}, TStorage extends Record<string, AbstractType<any>> = {}>(options?: CreateRoomBundleOptions<TPresence, TStorage>) => CreateRoomBundle<TIO, TPresence, TStorage>;
|
|
52
|
+
PluvProvider: FC<PluvProviderProps>;
|
|
53
|
+
usePluvClient: () => PluvClient<TIO>;
|
|
54
|
+
}
|
|
55
|
+
declare const createBundle: <TIO extends IOLike<_pluv_types.IOAuthorize<any, any>, {}, {}>>(client: PluvClient<TIO>) => CreateBundle<TIO>;
|
|
56
|
+
|
|
57
|
+
export { CreateBundle, CreateRoomBundle, CreateRoomBundleOptions, PluvProviderProps, PluvRoomProviderProps, SubscriptionHookOptions, createBundle };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
22
|
+
mod
|
|
23
|
+
));
|
|
24
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
25
|
+
|
|
26
|
+
// src/index.ts
|
|
27
|
+
var src_exports = {};
|
|
28
|
+
__export(src_exports, {
|
|
29
|
+
ConnectionState: () => import_client.ConnectionState,
|
|
30
|
+
createBundle: () => createBundle,
|
|
31
|
+
createClient: () => import_client.createClient,
|
|
32
|
+
y: () => import_client.y
|
|
33
|
+
});
|
|
34
|
+
module.exports = __toCommonJS(src_exports);
|
|
35
|
+
var import_client = require("@pluv/client");
|
|
36
|
+
|
|
37
|
+
// src/createBundle.tsx
|
|
38
|
+
var import_react4 = require("react");
|
|
39
|
+
var import_react5 = require("react");
|
|
40
|
+
|
|
41
|
+
// src/createRoomBundle.tsx
|
|
42
|
+
var import_fast_deep_equal = __toESM(require("fast-deep-equal"));
|
|
43
|
+
var import_react2 = require("react");
|
|
44
|
+
var import_react3 = require("react");
|
|
45
|
+
|
|
46
|
+
// src/internal/hooks/useRerender.ts
|
|
47
|
+
var import_react = require("react");
|
|
48
|
+
var useRerender = () => {
|
|
49
|
+
const [, setCount] = (0, import_react.useState)(0);
|
|
50
|
+
return (0, import_react.useCallback)(() => {
|
|
51
|
+
setCount((oldCount) => (oldCount + 1) % Number.MAX_SAFE_INTEGER);
|
|
52
|
+
}, []);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// src/internal/hooks/useSyncExternalStoreWithSelector.ts
|
|
56
|
+
var import_with_selector = require("use-sync-external-store/with-selector");
|
|
57
|
+
|
|
58
|
+
// src/internal/utils/identity.ts
|
|
59
|
+
var identity = (x) => x;
|
|
60
|
+
|
|
61
|
+
// src/internal/utils/shallowArrayEqual.ts
|
|
62
|
+
var shallowArrayEqual = (a, b) => {
|
|
63
|
+
return a.length === b.length && a.every((item, i) => item === b[i]);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// src/createRoomBundle.tsx
|
|
67
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
68
|
+
var createRoomBundle = (client, options = {}) => {
|
|
69
|
+
const PluvRoomContext = (0, import_react2.createContext)(
|
|
70
|
+
null
|
|
71
|
+
);
|
|
72
|
+
const PluvRoomProvider = (0, import_react3.memo)((props) => {
|
|
73
|
+
const {
|
|
74
|
+
children,
|
|
75
|
+
debug,
|
|
76
|
+
initialPresence,
|
|
77
|
+
initialStorage,
|
|
78
|
+
onAuthorizationFail,
|
|
79
|
+
room: _room
|
|
80
|
+
} = props;
|
|
81
|
+
const rerender = useRerender();
|
|
82
|
+
const [room] = (0, import_react2.useState)(() => {
|
|
83
|
+
return client.createRoom(_room, {
|
|
84
|
+
debug,
|
|
85
|
+
initialPresence,
|
|
86
|
+
initialStorage: typeof initialStorage === "function" ? initialStorage : options.initialStorage,
|
|
87
|
+
presence: options.presence,
|
|
88
|
+
onAuthorizationFail
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
(0, import_react3.useEffect)(() => {
|
|
92
|
+
const unsubscribe = room.subscribe("connection", () => {
|
|
93
|
+
rerender();
|
|
94
|
+
});
|
|
95
|
+
return () => {
|
|
96
|
+
unsubscribe();
|
|
97
|
+
};
|
|
98
|
+
}, [rerender, room]);
|
|
99
|
+
(0, import_react3.useEffect)(() => {
|
|
100
|
+
client.enter(_room);
|
|
101
|
+
return () => {
|
|
102
|
+
client.leave(_room);
|
|
103
|
+
};
|
|
104
|
+
}, [_room]);
|
|
105
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PluvRoomContext.Provider, {
|
|
106
|
+
value: room,
|
|
107
|
+
children
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
PluvRoomProvider.displayName = "PluvRoomProvider";
|
|
111
|
+
const usePluvRoom = () => (0, import_react3.useContext)(PluvRoomContext);
|
|
112
|
+
const usePluvBroadcast = () => {
|
|
113
|
+
const room = usePluvRoom();
|
|
114
|
+
return (0, import_react3.useCallback)(
|
|
115
|
+
(message) => {
|
|
116
|
+
room.broadcast(message);
|
|
117
|
+
},
|
|
118
|
+
[room]
|
|
119
|
+
);
|
|
120
|
+
};
|
|
121
|
+
const usePluvConnection = (selector = identity, options2) => {
|
|
122
|
+
const room = usePluvRoom();
|
|
123
|
+
const subscribe = (0, import_react3.useCallback)(
|
|
124
|
+
(onStoreChange) => {
|
|
125
|
+
return room.subscribe("connection", onStoreChange);
|
|
126
|
+
},
|
|
127
|
+
[room]
|
|
128
|
+
);
|
|
129
|
+
const getSnapshot = room.getConnection;
|
|
130
|
+
const _selector = (0, import_react3.useCallback)(
|
|
131
|
+
(snapshot) => selector(snapshot),
|
|
132
|
+
[selector]
|
|
133
|
+
);
|
|
134
|
+
return (0, import_with_selector.useSyncExternalStoreWithSelector)(
|
|
135
|
+
subscribe,
|
|
136
|
+
getSnapshot,
|
|
137
|
+
getSnapshot,
|
|
138
|
+
_selector,
|
|
139
|
+
options2 == null ? void 0 : options2.isEqual
|
|
140
|
+
);
|
|
141
|
+
};
|
|
142
|
+
const usePluvEvent = (type, callback) => {
|
|
143
|
+
const room = usePluvRoom();
|
|
144
|
+
(0, import_react3.useEffect)(() => {
|
|
145
|
+
const unsubscribe = room.event(type, callback);
|
|
146
|
+
return () => {
|
|
147
|
+
unsubscribe();
|
|
148
|
+
};
|
|
149
|
+
}, [callback, room, type]);
|
|
150
|
+
};
|
|
151
|
+
const usePluvMyPresence = (selector = identity, options2) => {
|
|
152
|
+
const room = usePluvRoom();
|
|
153
|
+
const subscribe = (0, import_react3.useCallback)(
|
|
154
|
+
(onStoreChange) => {
|
|
155
|
+
return room.subscribe("my-presence", onStoreChange);
|
|
156
|
+
},
|
|
157
|
+
[room]
|
|
158
|
+
);
|
|
159
|
+
const getSnapshot = room.getMyPresence;
|
|
160
|
+
const _selector = (0, import_react3.useCallback)(
|
|
161
|
+
(snapshot) => {
|
|
162
|
+
return !snapshot ? null : selector(snapshot);
|
|
163
|
+
},
|
|
164
|
+
[selector]
|
|
165
|
+
);
|
|
166
|
+
const myPresence = (0, import_with_selector.useSyncExternalStoreWithSelector)(
|
|
167
|
+
subscribe,
|
|
168
|
+
getSnapshot,
|
|
169
|
+
getSnapshot,
|
|
170
|
+
_selector,
|
|
171
|
+
options2 == null ? void 0 : options2.isEqual
|
|
172
|
+
);
|
|
173
|
+
const updateMyPresence = (0, import_react3.useCallback)(
|
|
174
|
+
(value) => {
|
|
175
|
+
const oldMyPresence = room.getMyPresence();
|
|
176
|
+
const presence = typeof value === "function" ? value(oldMyPresence) : value;
|
|
177
|
+
room.broadcast({
|
|
178
|
+
type: "$UPDATE_PRESENCE",
|
|
179
|
+
data: { presence }
|
|
180
|
+
});
|
|
181
|
+
},
|
|
182
|
+
[room]
|
|
183
|
+
);
|
|
184
|
+
return [myPresence, updateMyPresence];
|
|
185
|
+
};
|
|
186
|
+
const usePluvMyself = (selector = identity, options2) => {
|
|
187
|
+
const room = usePluvRoom();
|
|
188
|
+
const subscribe = (0, import_react3.useCallback)(
|
|
189
|
+
(onStoreChange) => {
|
|
190
|
+
return room.subscribe("myself", onStoreChange);
|
|
191
|
+
},
|
|
192
|
+
[room]
|
|
193
|
+
);
|
|
194
|
+
const getSnapshot = room.getMyself;
|
|
195
|
+
const _selector = (0, import_react3.useCallback)(
|
|
196
|
+
(snapshot) => {
|
|
197
|
+
return !snapshot ? null : selector(snapshot);
|
|
198
|
+
},
|
|
199
|
+
[selector]
|
|
200
|
+
);
|
|
201
|
+
return (0, import_with_selector.useSyncExternalStoreWithSelector)(
|
|
202
|
+
subscribe,
|
|
203
|
+
getSnapshot,
|
|
204
|
+
getSnapshot,
|
|
205
|
+
_selector,
|
|
206
|
+
options2 == null ? void 0 : options2.isEqual
|
|
207
|
+
);
|
|
208
|
+
};
|
|
209
|
+
const usePluvOther = (connectionId, selector = identity, options2) => {
|
|
210
|
+
const room = usePluvRoom();
|
|
211
|
+
const subscribe = (0, import_react3.useCallback)(
|
|
212
|
+
(onStoreChange) => {
|
|
213
|
+
return room.other(connectionId, onStoreChange);
|
|
214
|
+
},
|
|
215
|
+
[room, connectionId]
|
|
216
|
+
);
|
|
217
|
+
const getSnapshot = (0, import_react3.useCallback)(
|
|
218
|
+
() => room.getOther(connectionId),
|
|
219
|
+
[room, connectionId]
|
|
220
|
+
);
|
|
221
|
+
const _selector = (0, import_react3.useCallback)(
|
|
222
|
+
(snapshot) => {
|
|
223
|
+
return !snapshot ? null : selector(snapshot);
|
|
224
|
+
},
|
|
225
|
+
[selector]
|
|
226
|
+
);
|
|
227
|
+
return (0, import_with_selector.useSyncExternalStoreWithSelector)(
|
|
228
|
+
subscribe,
|
|
229
|
+
getSnapshot,
|
|
230
|
+
getSnapshot,
|
|
231
|
+
_selector,
|
|
232
|
+
options2 == null ? void 0 : options2.isEqual
|
|
233
|
+
);
|
|
234
|
+
};
|
|
235
|
+
const usePluvOthers = (selector = identity, options2) => {
|
|
236
|
+
var _a;
|
|
237
|
+
const room = usePluvRoom();
|
|
238
|
+
const subscribe = (0, import_react3.useCallback)(
|
|
239
|
+
(onStoreChange) => {
|
|
240
|
+
return room.subscribe("others", onStoreChange);
|
|
241
|
+
},
|
|
242
|
+
[room]
|
|
243
|
+
);
|
|
244
|
+
const getSnapshot = room.getOthers;
|
|
245
|
+
return (0, import_with_selector.useSyncExternalStoreWithSelector)(
|
|
246
|
+
subscribe,
|
|
247
|
+
getSnapshot,
|
|
248
|
+
getSnapshot,
|
|
249
|
+
selector,
|
|
250
|
+
(_a = options2 == null ? void 0 : options2.isEqual) != null ? _a : shallowArrayEqual
|
|
251
|
+
);
|
|
252
|
+
};
|
|
253
|
+
const usePluvStorage = (key, selector = identity, options2) => {
|
|
254
|
+
var _a;
|
|
255
|
+
const room = usePluvRoom();
|
|
256
|
+
const subscribe = (0, import_react3.useCallback)(
|
|
257
|
+
(onStoreChange) => room.storage(key, onStoreChange),
|
|
258
|
+
[key, room]
|
|
259
|
+
);
|
|
260
|
+
const getSnapshot = (0, import_react3.useCallback)(() => {
|
|
261
|
+
var _a2, _b;
|
|
262
|
+
return (_b = (_a2 = room.getStorage(key)) == null ? void 0 : _a2.toJSON()) != null ? _b : null;
|
|
263
|
+
}, [key, room]);
|
|
264
|
+
const _selector = (0, import_react3.useCallback)(
|
|
265
|
+
(snapshot) => {
|
|
266
|
+
return !snapshot ? null : selector(snapshot);
|
|
267
|
+
},
|
|
268
|
+
[selector]
|
|
269
|
+
);
|
|
270
|
+
const data = (0, import_with_selector.useSyncExternalStoreWithSelector)(
|
|
271
|
+
subscribe,
|
|
272
|
+
getSnapshot,
|
|
273
|
+
getSnapshot,
|
|
274
|
+
_selector,
|
|
275
|
+
(_a = options2 == null ? void 0 : options2.isEqual) != null ? _a : import_fast_deep_equal.default
|
|
276
|
+
);
|
|
277
|
+
const sharedType = room.getStorage(key);
|
|
278
|
+
return [data, sharedType];
|
|
279
|
+
};
|
|
280
|
+
return {
|
|
281
|
+
PluvRoomProvider,
|
|
282
|
+
usePluvBroadcast,
|
|
283
|
+
usePluvConnection,
|
|
284
|
+
usePluvEvent,
|
|
285
|
+
usePluvMyPresence,
|
|
286
|
+
usePluvMyself,
|
|
287
|
+
usePluvOther,
|
|
288
|
+
usePluvOthers,
|
|
289
|
+
usePluvRoom,
|
|
290
|
+
usePluvStorage
|
|
291
|
+
};
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
// src/createBundle.tsx
|
|
295
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
296
|
+
var createBundle = (client) => {
|
|
297
|
+
const PluvContext = (0, import_react4.createContext)(null);
|
|
298
|
+
const PluvProvider = (0, import_react5.memo)((props) => {
|
|
299
|
+
const { children } = props;
|
|
300
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(PluvContext.Provider, {
|
|
301
|
+
value: client,
|
|
302
|
+
children
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
PluvProvider.displayName = "PluvProvider";
|
|
306
|
+
const usePluvClient = () => (0, import_react5.useContext)(PluvContext);
|
|
307
|
+
const createRoomBundle2 = (options = {}) => createRoomBundle(client, options);
|
|
308
|
+
return {
|
|
309
|
+
createRoomBundle: createRoomBundle2,
|
|
310
|
+
PluvProvider,
|
|
311
|
+
usePluvClient
|
|
312
|
+
};
|
|
313
|
+
};
|
|
314
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
315
|
+
0 && (module.exports = {
|
|
316
|
+
ConnectionState,
|
|
317
|
+
createBundle,
|
|
318
|
+
createClient,
|
|
319
|
+
y
|
|
320
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { ConnectionState, createClient, y } from "@pluv/client";
|
|
3
|
+
|
|
4
|
+
// src/createBundle.tsx
|
|
5
|
+
import { createContext as createContext2 } from "react";
|
|
6
|
+
import { memo as memo2, useContext as useContext2 } from "react";
|
|
7
|
+
|
|
8
|
+
// src/createRoomBundle.tsx
|
|
9
|
+
import fastDeepEqual from "fast-deep-equal";
|
|
10
|
+
import { createContext, useState as useState2 } from "react";
|
|
11
|
+
import { memo, useCallback as useCallback2, useContext, useEffect } from "react";
|
|
12
|
+
|
|
13
|
+
// src/internal/hooks/useRerender.ts
|
|
14
|
+
import { useCallback, useState } from "react";
|
|
15
|
+
var useRerender = () => {
|
|
16
|
+
const [, setCount] = useState(0);
|
|
17
|
+
return useCallback(() => {
|
|
18
|
+
setCount((oldCount) => (oldCount + 1) % Number.MAX_SAFE_INTEGER);
|
|
19
|
+
}, []);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// src/internal/hooks/useSyncExternalStoreWithSelector.ts
|
|
23
|
+
import { useSyncExternalStoreWithSelector } from "use-sync-external-store/with-selector";
|
|
24
|
+
|
|
25
|
+
// src/internal/utils/identity.ts
|
|
26
|
+
var identity = (x) => x;
|
|
27
|
+
|
|
28
|
+
// src/internal/utils/shallowArrayEqual.ts
|
|
29
|
+
var shallowArrayEqual = (a, b) => {
|
|
30
|
+
return a.length === b.length && a.every((item, i) => item === b[i]);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// src/createRoomBundle.tsx
|
|
34
|
+
import { jsx } from "react/jsx-runtime";
|
|
35
|
+
var createRoomBundle = (client, options = {}) => {
|
|
36
|
+
const PluvRoomContext = createContext(
|
|
37
|
+
null
|
|
38
|
+
);
|
|
39
|
+
const PluvRoomProvider = memo((props) => {
|
|
40
|
+
const {
|
|
41
|
+
children,
|
|
42
|
+
debug,
|
|
43
|
+
initialPresence,
|
|
44
|
+
initialStorage,
|
|
45
|
+
onAuthorizationFail,
|
|
46
|
+
room: _room
|
|
47
|
+
} = props;
|
|
48
|
+
const rerender = useRerender();
|
|
49
|
+
const [room] = useState2(() => {
|
|
50
|
+
return client.createRoom(_room, {
|
|
51
|
+
debug,
|
|
52
|
+
initialPresence,
|
|
53
|
+
initialStorage: typeof initialStorage === "function" ? initialStorage : options.initialStorage,
|
|
54
|
+
presence: options.presence,
|
|
55
|
+
onAuthorizationFail
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
const unsubscribe = room.subscribe("connection", () => {
|
|
60
|
+
rerender();
|
|
61
|
+
});
|
|
62
|
+
return () => {
|
|
63
|
+
unsubscribe();
|
|
64
|
+
};
|
|
65
|
+
}, [rerender, room]);
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
client.enter(_room);
|
|
68
|
+
return () => {
|
|
69
|
+
client.leave(_room);
|
|
70
|
+
};
|
|
71
|
+
}, [_room]);
|
|
72
|
+
return /* @__PURE__ */ jsx(PluvRoomContext.Provider, {
|
|
73
|
+
value: room,
|
|
74
|
+
children
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
PluvRoomProvider.displayName = "PluvRoomProvider";
|
|
78
|
+
const usePluvRoom = () => useContext(PluvRoomContext);
|
|
79
|
+
const usePluvBroadcast = () => {
|
|
80
|
+
const room = usePluvRoom();
|
|
81
|
+
return useCallback2(
|
|
82
|
+
(message) => {
|
|
83
|
+
room.broadcast(message);
|
|
84
|
+
},
|
|
85
|
+
[room]
|
|
86
|
+
);
|
|
87
|
+
};
|
|
88
|
+
const usePluvConnection = (selector = identity, options2) => {
|
|
89
|
+
const room = usePluvRoom();
|
|
90
|
+
const subscribe = useCallback2(
|
|
91
|
+
(onStoreChange) => {
|
|
92
|
+
return room.subscribe("connection", onStoreChange);
|
|
93
|
+
},
|
|
94
|
+
[room]
|
|
95
|
+
);
|
|
96
|
+
const getSnapshot = room.getConnection;
|
|
97
|
+
const _selector = useCallback2(
|
|
98
|
+
(snapshot) => selector(snapshot),
|
|
99
|
+
[selector]
|
|
100
|
+
);
|
|
101
|
+
return useSyncExternalStoreWithSelector(
|
|
102
|
+
subscribe,
|
|
103
|
+
getSnapshot,
|
|
104
|
+
getSnapshot,
|
|
105
|
+
_selector,
|
|
106
|
+
options2 == null ? void 0 : options2.isEqual
|
|
107
|
+
);
|
|
108
|
+
};
|
|
109
|
+
const usePluvEvent = (type, callback) => {
|
|
110
|
+
const room = usePluvRoom();
|
|
111
|
+
useEffect(() => {
|
|
112
|
+
const unsubscribe = room.event(type, callback);
|
|
113
|
+
return () => {
|
|
114
|
+
unsubscribe();
|
|
115
|
+
};
|
|
116
|
+
}, [callback, room, type]);
|
|
117
|
+
};
|
|
118
|
+
const usePluvMyPresence = (selector = identity, options2) => {
|
|
119
|
+
const room = usePluvRoom();
|
|
120
|
+
const subscribe = useCallback2(
|
|
121
|
+
(onStoreChange) => {
|
|
122
|
+
return room.subscribe("my-presence", onStoreChange);
|
|
123
|
+
},
|
|
124
|
+
[room]
|
|
125
|
+
);
|
|
126
|
+
const getSnapshot = room.getMyPresence;
|
|
127
|
+
const _selector = useCallback2(
|
|
128
|
+
(snapshot) => {
|
|
129
|
+
return !snapshot ? null : selector(snapshot);
|
|
130
|
+
},
|
|
131
|
+
[selector]
|
|
132
|
+
);
|
|
133
|
+
const myPresence = useSyncExternalStoreWithSelector(
|
|
134
|
+
subscribe,
|
|
135
|
+
getSnapshot,
|
|
136
|
+
getSnapshot,
|
|
137
|
+
_selector,
|
|
138
|
+
options2 == null ? void 0 : options2.isEqual
|
|
139
|
+
);
|
|
140
|
+
const updateMyPresence = useCallback2(
|
|
141
|
+
(value) => {
|
|
142
|
+
const oldMyPresence = room.getMyPresence();
|
|
143
|
+
const presence = typeof value === "function" ? value(oldMyPresence) : value;
|
|
144
|
+
room.broadcast({
|
|
145
|
+
type: "$UPDATE_PRESENCE",
|
|
146
|
+
data: { presence }
|
|
147
|
+
});
|
|
148
|
+
},
|
|
149
|
+
[room]
|
|
150
|
+
);
|
|
151
|
+
return [myPresence, updateMyPresence];
|
|
152
|
+
};
|
|
153
|
+
const usePluvMyself = (selector = identity, options2) => {
|
|
154
|
+
const room = usePluvRoom();
|
|
155
|
+
const subscribe = useCallback2(
|
|
156
|
+
(onStoreChange) => {
|
|
157
|
+
return room.subscribe("myself", onStoreChange);
|
|
158
|
+
},
|
|
159
|
+
[room]
|
|
160
|
+
);
|
|
161
|
+
const getSnapshot = room.getMyself;
|
|
162
|
+
const _selector = useCallback2(
|
|
163
|
+
(snapshot) => {
|
|
164
|
+
return !snapshot ? null : selector(snapshot);
|
|
165
|
+
},
|
|
166
|
+
[selector]
|
|
167
|
+
);
|
|
168
|
+
return useSyncExternalStoreWithSelector(
|
|
169
|
+
subscribe,
|
|
170
|
+
getSnapshot,
|
|
171
|
+
getSnapshot,
|
|
172
|
+
_selector,
|
|
173
|
+
options2 == null ? void 0 : options2.isEqual
|
|
174
|
+
);
|
|
175
|
+
};
|
|
176
|
+
const usePluvOther = (connectionId, selector = identity, options2) => {
|
|
177
|
+
const room = usePluvRoom();
|
|
178
|
+
const subscribe = useCallback2(
|
|
179
|
+
(onStoreChange) => {
|
|
180
|
+
return room.other(connectionId, onStoreChange);
|
|
181
|
+
},
|
|
182
|
+
[room, connectionId]
|
|
183
|
+
);
|
|
184
|
+
const getSnapshot = useCallback2(
|
|
185
|
+
() => room.getOther(connectionId),
|
|
186
|
+
[room, connectionId]
|
|
187
|
+
);
|
|
188
|
+
const _selector = useCallback2(
|
|
189
|
+
(snapshot) => {
|
|
190
|
+
return !snapshot ? null : selector(snapshot);
|
|
191
|
+
},
|
|
192
|
+
[selector]
|
|
193
|
+
);
|
|
194
|
+
return useSyncExternalStoreWithSelector(
|
|
195
|
+
subscribe,
|
|
196
|
+
getSnapshot,
|
|
197
|
+
getSnapshot,
|
|
198
|
+
_selector,
|
|
199
|
+
options2 == null ? void 0 : options2.isEqual
|
|
200
|
+
);
|
|
201
|
+
};
|
|
202
|
+
const usePluvOthers = (selector = identity, options2) => {
|
|
203
|
+
var _a;
|
|
204
|
+
const room = usePluvRoom();
|
|
205
|
+
const subscribe = useCallback2(
|
|
206
|
+
(onStoreChange) => {
|
|
207
|
+
return room.subscribe("others", onStoreChange);
|
|
208
|
+
},
|
|
209
|
+
[room]
|
|
210
|
+
);
|
|
211
|
+
const getSnapshot = room.getOthers;
|
|
212
|
+
return useSyncExternalStoreWithSelector(
|
|
213
|
+
subscribe,
|
|
214
|
+
getSnapshot,
|
|
215
|
+
getSnapshot,
|
|
216
|
+
selector,
|
|
217
|
+
(_a = options2 == null ? void 0 : options2.isEqual) != null ? _a : shallowArrayEqual
|
|
218
|
+
);
|
|
219
|
+
};
|
|
220
|
+
const usePluvStorage = (key, selector = identity, options2) => {
|
|
221
|
+
var _a;
|
|
222
|
+
const room = usePluvRoom();
|
|
223
|
+
const subscribe = useCallback2(
|
|
224
|
+
(onStoreChange) => room.storage(key, onStoreChange),
|
|
225
|
+
[key, room]
|
|
226
|
+
);
|
|
227
|
+
const getSnapshot = useCallback2(() => {
|
|
228
|
+
var _a2, _b;
|
|
229
|
+
return (_b = (_a2 = room.getStorage(key)) == null ? void 0 : _a2.toJSON()) != null ? _b : null;
|
|
230
|
+
}, [key, room]);
|
|
231
|
+
const _selector = useCallback2(
|
|
232
|
+
(snapshot) => {
|
|
233
|
+
return !snapshot ? null : selector(snapshot);
|
|
234
|
+
},
|
|
235
|
+
[selector]
|
|
236
|
+
);
|
|
237
|
+
const data = useSyncExternalStoreWithSelector(
|
|
238
|
+
subscribe,
|
|
239
|
+
getSnapshot,
|
|
240
|
+
getSnapshot,
|
|
241
|
+
_selector,
|
|
242
|
+
(_a = options2 == null ? void 0 : options2.isEqual) != null ? _a : fastDeepEqual
|
|
243
|
+
);
|
|
244
|
+
const sharedType = room.getStorage(key);
|
|
245
|
+
return [data, sharedType];
|
|
246
|
+
};
|
|
247
|
+
return {
|
|
248
|
+
PluvRoomProvider,
|
|
249
|
+
usePluvBroadcast,
|
|
250
|
+
usePluvConnection,
|
|
251
|
+
usePluvEvent,
|
|
252
|
+
usePluvMyPresence,
|
|
253
|
+
usePluvMyself,
|
|
254
|
+
usePluvOther,
|
|
255
|
+
usePluvOthers,
|
|
256
|
+
usePluvRoom,
|
|
257
|
+
usePluvStorage
|
|
258
|
+
};
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
// src/createBundle.tsx
|
|
262
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
263
|
+
var createBundle = (client) => {
|
|
264
|
+
const PluvContext = createContext2(null);
|
|
265
|
+
const PluvProvider = memo2((props) => {
|
|
266
|
+
const { children } = props;
|
|
267
|
+
return /* @__PURE__ */ jsx2(PluvContext.Provider, {
|
|
268
|
+
value: client,
|
|
269
|
+
children
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
PluvProvider.displayName = "PluvProvider";
|
|
273
|
+
const usePluvClient = () => useContext2(PluvContext);
|
|
274
|
+
const createRoomBundle2 = (options = {}) => createRoomBundle(client, options);
|
|
275
|
+
return {
|
|
276
|
+
createRoomBundle: createRoomBundle2,
|
|
277
|
+
PluvProvider,
|
|
278
|
+
usePluvClient
|
|
279
|
+
};
|
|
280
|
+
};
|
|
281
|
+
export {
|
|
282
|
+
ConnectionState,
|
|
283
|
+
createBundle,
|
|
284
|
+
createClient,
|
|
285
|
+
y
|
|
286
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pluv/react",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"main": "./dist/index.js",
|
|
5
|
+
"module": "./dist/index.mjs",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"access": "public"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@pluv/client": "^0.0.0",
|
|
12
|
+
"@pluv/types": "^0.0.0",
|
|
13
|
+
"@types/use-sync-external-store": "^0.0.3",
|
|
14
|
+
"fast-deep-equal": "^3.1.3",
|
|
15
|
+
"use-sync-external-store": "^1.2.0"
|
|
16
|
+
},
|
|
17
|
+
"peerDependencies": {
|
|
18
|
+
"react": "^17.0.0 || ^18.0.0",
|
|
19
|
+
"yjs": "^13.0.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@pluv/tsconfig": "^0.0.0",
|
|
23
|
+
"@types/react": "18.0.26",
|
|
24
|
+
"eslint-config-pluv": "^0.0.0",
|
|
25
|
+
"react": "18.2.0",
|
|
26
|
+
"tsup": "^6.5.0",
|
|
27
|
+
"typescript": "^4.9.4",
|
|
28
|
+
"yjs": "^13.5.44"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "tsup src/index.ts --format esm,cjs --dts --external react",
|
|
32
|
+
"dev": "tsup src/index.ts --format esm,cjs --watch --dts",
|
|
33
|
+
"lint": "eslint src/**/*.ts* --fix",
|
|
34
|
+
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { PluvClient } from "@pluv/client";
|
|
2
|
+
import type { IOLike, JsonObject } from "@pluv/types";
|
|
3
|
+
import { createContext, FC, ReactNode } from "react";
|
|
4
|
+
import { memo, useContext } from "react";
|
|
5
|
+
import type { AbstractType } from "yjs";
|
|
6
|
+
import {
|
|
7
|
+
CreateRoomBundle,
|
|
8
|
+
createRoomBundle as _createRoomBundle,
|
|
9
|
+
CreateRoomBundleOptions,
|
|
10
|
+
} from "./createRoomBundle";
|
|
11
|
+
|
|
12
|
+
export interface PluvProviderProps {
|
|
13
|
+
children?: ReactNode;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface CreateBundle<TIO extends IOLike> {
|
|
17
|
+
// factories
|
|
18
|
+
createRoomBundle: <
|
|
19
|
+
TPresence extends JsonObject = {},
|
|
20
|
+
TStorage extends Record<string, AbstractType<any>> = {}
|
|
21
|
+
>(
|
|
22
|
+
options?: CreateRoomBundleOptions<TPresence, TStorage>
|
|
23
|
+
) => CreateRoomBundle<TIO, TPresence, TStorage>;
|
|
24
|
+
|
|
25
|
+
// components
|
|
26
|
+
PluvProvider: FC<PluvProviderProps>;
|
|
27
|
+
|
|
28
|
+
// hooks
|
|
29
|
+
usePluvClient: () => PluvClient<TIO>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const createBundle = <TIO extends IOLike>(
|
|
33
|
+
client: PluvClient<TIO>
|
|
34
|
+
): CreateBundle<TIO> => {
|
|
35
|
+
/**
|
|
36
|
+
* !HACK
|
|
37
|
+
* @description We'll let the context error out if client is not provided,
|
|
38
|
+
* and let the users deal with it.
|
|
39
|
+
* @author leedavidcs
|
|
40
|
+
* @date October 27, 2022
|
|
41
|
+
*/
|
|
42
|
+
const PluvContext = createContext<PluvClient<any>>(null as any);
|
|
43
|
+
|
|
44
|
+
const PluvProvider = memo<PluvProviderProps>((props) => {
|
|
45
|
+
const { children } = props;
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<PluvContext.Provider value={client}>
|
|
49
|
+
{children}
|
|
50
|
+
</PluvContext.Provider>
|
|
51
|
+
);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
PluvProvider.displayName = "PluvProvider";
|
|
55
|
+
|
|
56
|
+
const usePluvClient = (): PluvClient<TIO> => useContext(PluvContext);
|
|
57
|
+
|
|
58
|
+
const createRoomBundle = <
|
|
59
|
+
TPresence extends JsonObject = {},
|
|
60
|
+
TStorage extends Record<string, AbstractType<any>> = {}
|
|
61
|
+
>(
|
|
62
|
+
options: CreateRoomBundleOptions<TPresence, TStorage> = {}
|
|
63
|
+
): CreateRoomBundle<TIO, TPresence, TStorage> =>
|
|
64
|
+
_createRoomBundle(client, options);
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
createRoomBundle,
|
|
68
|
+
PluvProvider,
|
|
69
|
+
usePluvClient,
|
|
70
|
+
};
|
|
71
|
+
};
|
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
InferYjsSharedTypeJson,
|
|
3
|
+
PluvClient,
|
|
4
|
+
PluvRoomDebug,
|
|
5
|
+
UserInfo,
|
|
6
|
+
WebSocketConnection,
|
|
7
|
+
} from "@pluv/client";
|
|
8
|
+
import { ConnectionState, PluvRoom } from "@pluv/client";
|
|
9
|
+
import type {
|
|
10
|
+
Id,
|
|
11
|
+
InferEventMessage,
|
|
12
|
+
InferIOInput,
|
|
13
|
+
InferIOOutput,
|
|
14
|
+
InputZodLike,
|
|
15
|
+
IOEventMessage,
|
|
16
|
+
IOLike,
|
|
17
|
+
JsonObject,
|
|
18
|
+
} from "@pluv/types";
|
|
19
|
+
import fastDeepEqual from "fast-deep-equal";
|
|
20
|
+
import { createContext, Dispatch, FC, ReactNode, useState } from "react";
|
|
21
|
+
import { memo, useCallback, useContext, useEffect } from "react";
|
|
22
|
+
import type { AbstractType } from "yjs";
|
|
23
|
+
import {
|
|
24
|
+
identity,
|
|
25
|
+
shallowArrayEqual,
|
|
26
|
+
useRerender,
|
|
27
|
+
useSyncExternalStoreWithSelector,
|
|
28
|
+
} from "./internal";
|
|
29
|
+
|
|
30
|
+
export interface CreateRoomBundleOptions<
|
|
31
|
+
TPresence extends JsonObject = {},
|
|
32
|
+
TStorage extends Record<string, AbstractType<any>> = {}
|
|
33
|
+
> {
|
|
34
|
+
initialStorage?: () => TStorage;
|
|
35
|
+
presence?: InputZodLike<TPresence>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type PluvRoomProviderProps<
|
|
39
|
+
TIO extends IOLike,
|
|
40
|
+
TPresence extends JsonObject,
|
|
41
|
+
TStorage extends Record<string, AbstractType<any>>
|
|
42
|
+
> = {
|
|
43
|
+
children?: ReactNode;
|
|
44
|
+
debug?: boolean | PluvRoomDebug<TIO>;
|
|
45
|
+
onAuthorizationFail?: (error: Error) => void;
|
|
46
|
+
room: string;
|
|
47
|
+
} & (keyof TPresence extends never
|
|
48
|
+
? { initialPresence?: never }
|
|
49
|
+
: { initialPresence: TPresence }) &
|
|
50
|
+
(keyof TStorage extends never
|
|
51
|
+
? { initialStorage?: never }
|
|
52
|
+
: { initialStorage?: () => TStorage });
|
|
53
|
+
|
|
54
|
+
export interface SubscriptionHookOptions<T extends unknown> {
|
|
55
|
+
isEqual?: (a: T, b: T) => boolean;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export type UpdateMyPresenceAction<TPresence extends JsonObject> =
|
|
59
|
+
| Partial<TPresence>
|
|
60
|
+
| ((oldPresence: TPresence | null) => Partial<TPresence>);
|
|
61
|
+
|
|
62
|
+
export interface CreateRoomBundle<
|
|
63
|
+
TIO extends IOLike,
|
|
64
|
+
TPresence extends JsonObject,
|
|
65
|
+
TStorage extends Record<string, AbstractType<any>>
|
|
66
|
+
> {
|
|
67
|
+
// components
|
|
68
|
+
PluvRoomProvider: FC<PluvRoomProviderProps<TIO, TPresence, TStorage>>;
|
|
69
|
+
|
|
70
|
+
// hooks
|
|
71
|
+
usePluvBroadcast: <
|
|
72
|
+
TMessage extends InferEventMessage<InferIOInput<TIO>>
|
|
73
|
+
>() => (message: TMessage) => void;
|
|
74
|
+
usePluvConnection: <T extends unknown = WebSocketConnection>(
|
|
75
|
+
selector: (connection: WebSocketConnection) => T,
|
|
76
|
+
options?: SubscriptionHookOptions<Id<T>>
|
|
77
|
+
) => Id<T>;
|
|
78
|
+
usePluvEvent: <TType extends keyof InferIOOutput<TIO>>(
|
|
79
|
+
type: TType,
|
|
80
|
+
callback: (data: Id<IOEventMessage<TIO, TType>>) => void
|
|
81
|
+
) => void;
|
|
82
|
+
usePluvMyPresence: <T extends unknown = TPresence>(
|
|
83
|
+
selector?: (myPresence: TPresence) => T,
|
|
84
|
+
options?: SubscriptionHookOptions<Id<T> | null>
|
|
85
|
+
) => [
|
|
86
|
+
myPresence: Id<T> | null,
|
|
87
|
+
updateMyPresence: Dispatch<UpdateMyPresenceAction<TPresence>>
|
|
88
|
+
];
|
|
89
|
+
usePluvMyself: <T extends unknown = UserInfo<TIO, TPresence>>(
|
|
90
|
+
selector?: (myself: Id<UserInfo<TIO, TPresence>>) => T,
|
|
91
|
+
options?: SubscriptionHookOptions<Id<T> | null>
|
|
92
|
+
) => Id<T> | null;
|
|
93
|
+
usePluvOther: <T extends unknown = UserInfo<TIO, TPresence>>(
|
|
94
|
+
connectionId: string,
|
|
95
|
+
selector?: (other: UserInfo<TIO, TPresence>) => T,
|
|
96
|
+
options?: SubscriptionHookOptions<Id<T> | null>
|
|
97
|
+
) => Id<T> | null;
|
|
98
|
+
usePluvOthers: <T extends unknown = UserInfo<TIO, TPresence>>(
|
|
99
|
+
selector?: (other: readonly Id<UserInfo<TIO, TPresence>>[]) => T[],
|
|
100
|
+
options?: SubscriptionHookOptions<readonly Id<T>[]>
|
|
101
|
+
) => readonly Id<T>[];
|
|
102
|
+
usePluvRoom: () => PluvRoom<TIO, TPresence>;
|
|
103
|
+
usePluvStorage: <
|
|
104
|
+
TKey extends keyof TStorage,
|
|
105
|
+
TData extends unknown = InferYjsSharedTypeJson<TStorage[TKey]>
|
|
106
|
+
>(
|
|
107
|
+
key: TKey,
|
|
108
|
+
selector?: (data: InferYjsSharedTypeJson<TStorage[TKey]>) => TData,
|
|
109
|
+
options?: SubscriptionHookOptions<TData | null>
|
|
110
|
+
) => [data: TData | null, sharedType: TStorage[TKey] | null];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export const createRoomBundle = <
|
|
114
|
+
TIO extends IOLike,
|
|
115
|
+
TPresence extends JsonObject = {},
|
|
116
|
+
TStorage extends Record<string, AbstractType<any>> = {}
|
|
117
|
+
>(
|
|
118
|
+
client: PluvClient<TIO>,
|
|
119
|
+
options: CreateRoomBundleOptions<TPresence, TStorage> = {}
|
|
120
|
+
): CreateRoomBundle<TIO, TPresence, TStorage> => {
|
|
121
|
+
/**
|
|
122
|
+
* !HACK
|
|
123
|
+
* @description We'll let the context error out if the room is not provided,
|
|
124
|
+
* and let the users deal with it.
|
|
125
|
+
* @author leedavidcs
|
|
126
|
+
* @date November 11, 2022
|
|
127
|
+
*/
|
|
128
|
+
const PluvRoomContext = createContext<PluvRoom<TIO, TPresence, TStorage>>(
|
|
129
|
+
null as any
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
const PluvRoomProvider = memo<
|
|
133
|
+
PluvRoomProviderProps<TIO, TPresence, TStorage>
|
|
134
|
+
>((props) => {
|
|
135
|
+
const {
|
|
136
|
+
children,
|
|
137
|
+
debug,
|
|
138
|
+
initialPresence,
|
|
139
|
+
initialStorage,
|
|
140
|
+
onAuthorizationFail,
|
|
141
|
+
room: _room,
|
|
142
|
+
} = props;
|
|
143
|
+
|
|
144
|
+
const rerender = useRerender();
|
|
145
|
+
|
|
146
|
+
const [room] = useState<PluvRoom<TIO, TPresence, TStorage>>(() => {
|
|
147
|
+
return client.createRoom<TPresence, TStorage>(_room, {
|
|
148
|
+
debug,
|
|
149
|
+
initialPresence,
|
|
150
|
+
initialStorage:
|
|
151
|
+
typeof initialStorage === "function"
|
|
152
|
+
? initialStorage
|
|
153
|
+
: options.initialStorage,
|
|
154
|
+
presence: options.presence,
|
|
155
|
+
onAuthorizationFail,
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
useEffect(() => {
|
|
160
|
+
const unsubscribe = room.subscribe("connection", () => {
|
|
161
|
+
rerender();
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
return () => {
|
|
165
|
+
unsubscribe();
|
|
166
|
+
};
|
|
167
|
+
}, [rerender, room]);
|
|
168
|
+
|
|
169
|
+
useEffect(() => {
|
|
170
|
+
client.enter(_room);
|
|
171
|
+
|
|
172
|
+
return () => {
|
|
173
|
+
client.leave(_room);
|
|
174
|
+
};
|
|
175
|
+
}, [_room]);
|
|
176
|
+
|
|
177
|
+
return (
|
|
178
|
+
<PluvRoomContext.Provider value={room}>
|
|
179
|
+
{children}
|
|
180
|
+
</PluvRoomContext.Provider>
|
|
181
|
+
);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
PluvRoomProvider.displayName = "PluvRoomProvider";
|
|
185
|
+
|
|
186
|
+
const usePluvRoom = () => useContext(PluvRoomContext);
|
|
187
|
+
|
|
188
|
+
const usePluvBroadcast = <
|
|
189
|
+
TMessage extends InferEventMessage<InferIOInput<TIO>>
|
|
190
|
+
>(): ((message: TMessage) => void) => {
|
|
191
|
+
const room = usePluvRoom();
|
|
192
|
+
|
|
193
|
+
return useCallback(
|
|
194
|
+
(message: TMessage) => {
|
|
195
|
+
room.broadcast(message);
|
|
196
|
+
},
|
|
197
|
+
[room]
|
|
198
|
+
);
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const usePluvConnection = <T extends unknown = WebSocketConnection>(
|
|
202
|
+
selector = identity as (connection: WebSocketConnection) => T,
|
|
203
|
+
options?: SubscriptionHookOptions<Id<T>>
|
|
204
|
+
): Id<T> => {
|
|
205
|
+
const room = usePluvRoom();
|
|
206
|
+
|
|
207
|
+
const subscribe = useCallback(
|
|
208
|
+
(onStoreChange: () => void) => {
|
|
209
|
+
return room.subscribe("connection", onStoreChange);
|
|
210
|
+
},
|
|
211
|
+
[room]
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
const getSnapshot = room.getConnection;
|
|
215
|
+
|
|
216
|
+
const _selector = useCallback(
|
|
217
|
+
(snapshot: WebSocketConnection) => selector(snapshot) as Id<T>,
|
|
218
|
+
[selector]
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
return useSyncExternalStoreWithSelector(
|
|
222
|
+
subscribe,
|
|
223
|
+
getSnapshot,
|
|
224
|
+
getSnapshot,
|
|
225
|
+
_selector,
|
|
226
|
+
options?.isEqual
|
|
227
|
+
);
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
const usePluvEvent = <TType extends keyof InferIOOutput<TIO>>(
|
|
231
|
+
type: TType,
|
|
232
|
+
callback: (data: Id<IOEventMessage<TIO, TType>>) => void
|
|
233
|
+
): void => {
|
|
234
|
+
const room = usePluvRoom();
|
|
235
|
+
|
|
236
|
+
useEffect(() => {
|
|
237
|
+
const unsubscribe = room.event(type, callback);
|
|
238
|
+
|
|
239
|
+
return () => {
|
|
240
|
+
unsubscribe();
|
|
241
|
+
};
|
|
242
|
+
}, [callback, room, type]);
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
const usePluvMyPresence = <T extends unknown = TPresence>(
|
|
246
|
+
selector = identity as (myPresence: TPresence) => T,
|
|
247
|
+
options?: SubscriptionHookOptions<Id<T> | null>
|
|
248
|
+
): [Id<T> | null, Dispatch<UpdateMyPresenceAction<TPresence>>] => {
|
|
249
|
+
const room = usePluvRoom();
|
|
250
|
+
|
|
251
|
+
const subscribe = useCallback(
|
|
252
|
+
(onStoreChange: () => void) => {
|
|
253
|
+
return room.subscribe("my-presence", onStoreChange);
|
|
254
|
+
},
|
|
255
|
+
[room]
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
const getSnapshot = room.getMyPresence;
|
|
259
|
+
|
|
260
|
+
const _selector = useCallback(
|
|
261
|
+
(snapshot: TPresence | null) => {
|
|
262
|
+
return !snapshot ? null : (selector(snapshot) as Id<T>);
|
|
263
|
+
},
|
|
264
|
+
[selector]
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
const myPresence = useSyncExternalStoreWithSelector(
|
|
268
|
+
subscribe,
|
|
269
|
+
getSnapshot,
|
|
270
|
+
getSnapshot,
|
|
271
|
+
_selector,
|
|
272
|
+
options?.isEqual
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
const updateMyPresence: Dispatch<UpdateMyPresenceAction<TPresence>> =
|
|
276
|
+
useCallback(
|
|
277
|
+
(value: UpdateMyPresenceAction<TPresence>): void => {
|
|
278
|
+
const oldMyPresence = room.getMyPresence();
|
|
279
|
+
const presence =
|
|
280
|
+
typeof value === "function"
|
|
281
|
+
? value(oldMyPresence)
|
|
282
|
+
: value;
|
|
283
|
+
|
|
284
|
+
room.broadcast({
|
|
285
|
+
type: "$UPDATE_PRESENCE",
|
|
286
|
+
data: { presence },
|
|
287
|
+
} as InferEventMessage<InferIOInput<TIO>>);
|
|
288
|
+
},
|
|
289
|
+
[room]
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
return [myPresence, updateMyPresence];
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
const usePluvMyself = <T extends unknown = UserInfo<TIO, TPresence>>(
|
|
296
|
+
selector = identity as (myself: Id<UserInfo<TIO, TPresence>>) => T,
|
|
297
|
+
options?: SubscriptionHookOptions<Id<T> | null>
|
|
298
|
+
): Id<T> | null => {
|
|
299
|
+
const room = usePluvRoom();
|
|
300
|
+
|
|
301
|
+
const subscribe = useCallback(
|
|
302
|
+
(onStoreChange: () => void) => {
|
|
303
|
+
return room.subscribe("myself", onStoreChange);
|
|
304
|
+
},
|
|
305
|
+
[room]
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
const getSnapshot = room.getMyself;
|
|
309
|
+
|
|
310
|
+
const _selector = useCallback(
|
|
311
|
+
(snapshot: Id<UserInfo<TIO, TPresence>> | null) => {
|
|
312
|
+
return !snapshot ? null : (selector(snapshot) as Id<T>);
|
|
313
|
+
},
|
|
314
|
+
[selector]
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
return useSyncExternalStoreWithSelector(
|
|
318
|
+
subscribe,
|
|
319
|
+
getSnapshot,
|
|
320
|
+
getSnapshot,
|
|
321
|
+
_selector,
|
|
322
|
+
options?.isEqual
|
|
323
|
+
);
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
const usePluvOther = <T extends unknown = UserInfo<TIO, TPresence>>(
|
|
327
|
+
connectionId: string,
|
|
328
|
+
selector = identity as (other: UserInfo<TIO, TPresence>) => T,
|
|
329
|
+
options?: SubscriptionHookOptions<Id<T> | null>
|
|
330
|
+
): Id<T> | null => {
|
|
331
|
+
const room = usePluvRoom();
|
|
332
|
+
|
|
333
|
+
const subscribe = useCallback(
|
|
334
|
+
(onStoreChange: () => void) => {
|
|
335
|
+
return room.other(connectionId, onStoreChange);
|
|
336
|
+
},
|
|
337
|
+
[room, connectionId]
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
const getSnapshot = useCallback(
|
|
341
|
+
() => room.getOther(connectionId),
|
|
342
|
+
[room, connectionId]
|
|
343
|
+
);
|
|
344
|
+
|
|
345
|
+
const _selector = useCallback(
|
|
346
|
+
(snapshot: Id<UserInfo<TIO, TPresence>> | null) => {
|
|
347
|
+
return !snapshot ? null : (selector(snapshot) as Id<T>);
|
|
348
|
+
},
|
|
349
|
+
[selector]
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
return useSyncExternalStoreWithSelector(
|
|
353
|
+
subscribe,
|
|
354
|
+
getSnapshot,
|
|
355
|
+
getSnapshot,
|
|
356
|
+
_selector,
|
|
357
|
+
options?.isEqual
|
|
358
|
+
);
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
const usePluvOthers = <T extends unknown = UserInfo<TIO, TPresence>>(
|
|
362
|
+
selector = identity as (
|
|
363
|
+
other: readonly Id<UserInfo<TIO, TPresence>>[]
|
|
364
|
+
) => T[],
|
|
365
|
+
options?: SubscriptionHookOptions<readonly Id<T>[]>
|
|
366
|
+
): readonly Id<T>[] => {
|
|
367
|
+
const room = usePluvRoom();
|
|
368
|
+
|
|
369
|
+
const subscribe = useCallback(
|
|
370
|
+
(onStoreChange: () => void) => {
|
|
371
|
+
return room.subscribe("others", onStoreChange);
|
|
372
|
+
},
|
|
373
|
+
[room]
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
const getSnapshot = room.getOthers;
|
|
377
|
+
|
|
378
|
+
return useSyncExternalStoreWithSelector(
|
|
379
|
+
subscribe,
|
|
380
|
+
getSnapshot,
|
|
381
|
+
getSnapshot,
|
|
382
|
+
selector as (
|
|
383
|
+
other: readonly Id<UserInfo<TIO, TPresence>>[]
|
|
384
|
+
) => Id<T>[],
|
|
385
|
+
options?.isEqual ?? shallowArrayEqual
|
|
386
|
+
);
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
const usePluvStorage = <
|
|
390
|
+
TKey extends keyof TStorage,
|
|
391
|
+
TData extends unknown = InferYjsSharedTypeJson<TStorage[TKey]>
|
|
392
|
+
>(
|
|
393
|
+
key: TKey,
|
|
394
|
+
selector = identity as (
|
|
395
|
+
data: InferYjsSharedTypeJson<TStorage[TKey]>
|
|
396
|
+
) => TData,
|
|
397
|
+
options?: SubscriptionHookOptions<TData | null>
|
|
398
|
+
): [data: TData | null, sharedType: TStorage[TKey] | null] => {
|
|
399
|
+
const room = usePluvRoom();
|
|
400
|
+
|
|
401
|
+
const subscribe = useCallback(
|
|
402
|
+
(onStoreChange: () => void) => room.storage(key, onStoreChange),
|
|
403
|
+
[key, room]
|
|
404
|
+
);
|
|
405
|
+
|
|
406
|
+
const getSnapshot = useCallback((): InferYjsSharedTypeJson<
|
|
407
|
+
TStorage[TKey]
|
|
408
|
+
> | null => {
|
|
409
|
+
return room.getStorage(key)?.toJSON() ?? null;
|
|
410
|
+
}, [key, room]);
|
|
411
|
+
|
|
412
|
+
const _selector = useCallback(
|
|
413
|
+
(snapshot: InferYjsSharedTypeJson<TStorage[TKey]> | null) => {
|
|
414
|
+
return !snapshot ? null : selector(snapshot);
|
|
415
|
+
},
|
|
416
|
+
[selector]
|
|
417
|
+
);
|
|
418
|
+
|
|
419
|
+
const data = useSyncExternalStoreWithSelector(
|
|
420
|
+
subscribe,
|
|
421
|
+
getSnapshot,
|
|
422
|
+
getSnapshot,
|
|
423
|
+
_selector,
|
|
424
|
+
options?.isEqual ?? fastDeepEqual
|
|
425
|
+
);
|
|
426
|
+
|
|
427
|
+
const sharedType = room.getStorage(key);
|
|
428
|
+
|
|
429
|
+
return [data, sharedType];
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
return {
|
|
433
|
+
// components
|
|
434
|
+
PluvRoomProvider,
|
|
435
|
+
|
|
436
|
+
// hooks
|
|
437
|
+
usePluvBroadcast,
|
|
438
|
+
usePluvConnection,
|
|
439
|
+
usePluvEvent,
|
|
440
|
+
usePluvMyPresence,
|
|
441
|
+
usePluvMyself,
|
|
442
|
+
usePluvOther,
|
|
443
|
+
usePluvOthers,
|
|
444
|
+
usePluvRoom,
|
|
445
|
+
usePluvStorage,
|
|
446
|
+
};
|
|
447
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type { AbstractType } from "@pluv/client";
|
|
2
|
+
export { ConnectionState, createClient, y } from "@pluv/client";
|
|
3
|
+
export type {
|
|
4
|
+
BaseUser,
|
|
5
|
+
EventMessage,
|
|
6
|
+
EventRecord,
|
|
7
|
+
IOEventMessage,
|
|
8
|
+
} from "@pluv/types";
|
|
9
|
+
export type { CreateBundle, PluvProviderProps } from "./createBundle";
|
|
10
|
+
export { createBundle } from "./createBundle";
|
|
11
|
+
export type {
|
|
12
|
+
CreateRoomBundle,
|
|
13
|
+
CreateRoomBundleOptions,
|
|
14
|
+
PluvRoomProviderProps,
|
|
15
|
+
SubscriptionHookOptions,
|
|
16
|
+
} from "./createRoomBundle";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { useSyncExternalStoreWithSelector } from "./useSyncExternalStoreWithSelector";
|
|
2
|
+
import { identity } from "../utils";
|
|
3
|
+
|
|
4
|
+
export const useSyncExternalStore = <TSnapshot extends unknown>(
|
|
5
|
+
subscribe: (onStoreChange: () => void) => () => void,
|
|
6
|
+
getSnapshot: () => TSnapshot,
|
|
7
|
+
getServerSnapshot: undefined | null | (() => TSnapshot)
|
|
8
|
+
) =>
|
|
9
|
+
useSyncExternalStoreWithSelector(
|
|
10
|
+
subscribe,
|
|
11
|
+
getSnapshot,
|
|
12
|
+
getServerSnapshot,
|
|
13
|
+
identity
|
|
14
|
+
);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useSyncExternalStoreWithSelector } from "use-sync-external-store/with-selector";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const identity = <T extends unknown>(x: T): T => x;
|