@liveblocks/redux 0.15.0-alpha.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/README.md ADDED
File without changes
@@ -0,0 +1,277 @@
1
+ import { patchLiveObjectKey, liveNodeToJson, patchImmutableObject } from '@liveblocks/client';
2
+
3
+ const ERROR_PREFIX = "Invalid @liveblocks/redux middleware config.";
4
+ function missingClient() {
5
+ return new Error(`${ERROR_PREFIX} client is missing`);
6
+ }
7
+ function missingMapping(mappingType) {
8
+ return new Error(`${ERROR_PREFIX} ${mappingType} is missing.`);
9
+ }
10
+ function mappingShouldBeAnObject(mappingType) {
11
+ return new Error(`${ERROR_PREFIX} ${mappingType} should be an object where the values are boolean.`);
12
+ }
13
+ function mappingValueShouldBeABoolean(mappingType, key) {
14
+ return new Error(`${ERROR_PREFIX} ${mappingType}.${key} value should be a boolean`);
15
+ }
16
+ function mappingShouldNotHaveTheSameKeys(key) {
17
+ return new Error(`${ERROR_PREFIX} "${key}" is mapped on presenceMapping and storageMapping. A key shouldn't exist on both mapping.`);
18
+ }
19
+ function mappingToFunctionIsNotAllowed(key) {
20
+ return new Error(`${ERROR_PREFIX} mapping.${key} is invalid. Mapping to a function is not allowed.`);
21
+ }
22
+
23
+ var __defProp = Object.defineProperty;
24
+ var __defProps = Object.defineProperties;
25
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
26
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
27
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
28
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
29
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
30
+ var __spreadValues = (a, b) => {
31
+ for (var prop in b || (b = {}))
32
+ if (__hasOwnProp.call(b, prop))
33
+ __defNormalProp(a, prop, b[prop]);
34
+ if (__getOwnPropSymbols)
35
+ for (var prop of __getOwnPropSymbols(b)) {
36
+ if (__propIsEnum.call(b, prop))
37
+ __defNormalProp(a, prop, b[prop]);
38
+ }
39
+ return a;
40
+ };
41
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
42
+ const ACTION_TYPES = {
43
+ ENTER: "@@LIVEBLOCKS/ENTER",
44
+ LEAVE: "@@LIVEBLOCKS/LEAVE",
45
+ START_LOADING_STORAGE: "@@LIVEBLOCKS/START_LOADING_STORAGE",
46
+ INIT_STORAGE: "@@LIVEBLOCKS/INIT_STORAGE",
47
+ PATCH_STORAGE: "@@LIVEBLOCKS/PATCH_STORAGE",
48
+ UPDATE_CONNECTION: "@@LIVEBLOCKS/UPDATE_CONNECTION",
49
+ UPDATE_OTHERS: "@@LIVEBLOCKS/UPDATE_OTHERS"
50
+ };
51
+ const internalPlugin = (options) => {
52
+ if (process.env.NODE_ENV !== "production" && options.client == null) {
53
+ throw missingClient();
54
+ }
55
+ const client = options.client;
56
+ const mapping = validateMapping(options.storageMapping, "storageMapping");
57
+ const presenceMapping = validateMapping(options.presenceMapping || {}, "presenceMapping");
58
+ if (process.env.NODE_ENV !== "production") {
59
+ validateNoDuplicateKeys(mapping, presenceMapping);
60
+ }
61
+ return (createStore) => (reducer, initialState, enhancer2) => {
62
+ let room = null;
63
+ let isPatching = false;
64
+ let storageRoot = null;
65
+ let unsubscribeCallbacks = [];
66
+ const newReducer = (state, action) => {
67
+ switch (action.type) {
68
+ case ACTION_TYPES.PATCH_STORAGE:
69
+ return __spreadValues(__spreadValues({}, state), action.state);
70
+ case ACTION_TYPES.INIT_STORAGE:
71
+ return __spreadProps(__spreadValues(__spreadValues({}, state), action.state), {
72
+ liveblocks: __spreadProps(__spreadValues({}, state.liveblocks), {
73
+ isStorageLoading: false
74
+ })
75
+ });
76
+ case ACTION_TYPES.START_LOADING_STORAGE:
77
+ return __spreadProps(__spreadValues({}, state), {
78
+ liveblocks: __spreadProps(__spreadValues({}, state.liveblocks), {
79
+ isStorageLoading: true
80
+ })
81
+ });
82
+ case ACTION_TYPES.UPDATE_CONNECTION: {
83
+ return __spreadProps(__spreadValues({}, state), {
84
+ liveblocks: __spreadProps(__spreadValues({}, state.liveblocks), {
85
+ connection: action.connection
86
+ })
87
+ });
88
+ }
89
+ case ACTION_TYPES.UPDATE_OTHERS: {
90
+ return __spreadProps(__spreadValues({}, state), {
91
+ liveblocks: __spreadProps(__spreadValues({}, state.liveblocks), {
92
+ others: action.others
93
+ })
94
+ });
95
+ }
96
+ default: {
97
+ const newState = reducer(state, action);
98
+ if (room) {
99
+ isPatching = true;
100
+ updatePresence(room, state, newState, presenceMapping);
101
+ room.batch(() => {
102
+ if (storageRoot) {
103
+ patchLiveblocksStorage(storageRoot, state, newState, mapping);
104
+ }
105
+ });
106
+ isPatching = false;
107
+ }
108
+ if (newState.liveblocks == null) {
109
+ return __spreadProps(__spreadValues({}, newState), {
110
+ liveblocks: {
111
+ others: [],
112
+ isStorageLoading: false,
113
+ connection: "closed"
114
+ }
115
+ });
116
+ }
117
+ return newState;
118
+ }
119
+ }
120
+ };
121
+ const store = createStore(newReducer, initialState, enhancer2);
122
+ function enterRoom2(roomId, storageInitialState = {}, reduxState) {
123
+ if (storageRoot) {
124
+ return;
125
+ }
126
+ room = client.enter(roomId);
127
+ broadcastInitialPresence(room, reduxState, presenceMapping);
128
+ unsubscribeCallbacks.push(room.subscribe("connection", () => {
129
+ store.dispatch({
130
+ type: ACTION_TYPES.UPDATE_CONNECTION,
131
+ connection: room.getConnectionState()
132
+ });
133
+ }));
134
+ unsubscribeCallbacks.push(room.subscribe("others", (others) => {
135
+ store.dispatch({
136
+ type: ACTION_TYPES.UPDATE_OTHERS,
137
+ others: others.toArray()
138
+ });
139
+ }));
140
+ store.dispatch({
141
+ type: ACTION_TYPES.START_LOADING_STORAGE
142
+ });
143
+ room.getStorage().then(({ root }) => {
144
+ const updates = {};
145
+ room.batch(() => {
146
+ for (const key in mapping) {
147
+ const liveblocksStatePart = root.get(key);
148
+ if (liveblocksStatePart == null) {
149
+ updates[key] = storageInitialState[key];
150
+ patchLiveObjectKey(root, key, void 0, storageInitialState[key]);
151
+ } else {
152
+ updates[key] = liveNodeToJson(liveblocksStatePart);
153
+ }
154
+ }
155
+ });
156
+ store.dispatch({
157
+ type: ACTION_TYPES.INIT_STORAGE,
158
+ state: updates
159
+ });
160
+ storageRoot = root;
161
+ unsubscribeCallbacks.push(room.subscribe(root, (updates2) => {
162
+ if (isPatching === false) {
163
+ store.dispatch({
164
+ type: ACTION_TYPES.PATCH_STORAGE,
165
+ state: patchState(store.getState(), updates2, mapping)
166
+ });
167
+ }
168
+ }, { isDeep: true }));
169
+ });
170
+ }
171
+ function leaveRoom2(roomId) {
172
+ for (const unsubscribe of unsubscribeCallbacks) {
173
+ unsubscribe();
174
+ }
175
+ storageRoot = null;
176
+ room = null;
177
+ isPatching = false;
178
+ unsubscribeCallbacks = [];
179
+ client.leave(roomId);
180
+ }
181
+ function newDispatch(action, state) {
182
+ if (action.type === ACTION_TYPES.ENTER) {
183
+ enterRoom2(action.roomId, action.initialState, store.getState());
184
+ } else if (action.type === ACTION_TYPES.LEAVE) {
185
+ leaveRoom2(action.roomId);
186
+ } else {
187
+ store.dispatch(action, state);
188
+ }
189
+ }
190
+ return __spreadProps(__spreadValues({}, store), {
191
+ dispatch: newDispatch
192
+ });
193
+ };
194
+ };
195
+ function enterRoom(roomId, initialState) {
196
+ return {
197
+ type: ACTION_TYPES.ENTER,
198
+ roomId,
199
+ initialState
200
+ };
201
+ }
202
+ function leaveRoom(roomId) {
203
+ return {
204
+ type: ACTION_TYPES.LEAVE,
205
+ roomId
206
+ };
207
+ }
208
+ const enhancer = internalPlugin;
209
+ function patchLiveblocksStorage(root, oldState, newState, mapping) {
210
+ for (const key in mapping) {
211
+ if (process.env.NODE_ENV !== "production" && typeof newState[key] === "function") {
212
+ throw mappingToFunctionIsNotAllowed("value");
213
+ }
214
+ if (oldState[key] !== newState[key]) {
215
+ patchLiveObjectKey(root, key, oldState[key], newState[key]);
216
+ }
217
+ }
218
+ }
219
+ function broadcastInitialPresence(room, state, mapping) {
220
+ for (const key in mapping) {
221
+ room == null ? void 0 : room.updatePresence({ [key]: state[key] });
222
+ }
223
+ }
224
+ function updatePresence(room, oldState, newState, presenceMapping) {
225
+ for (const key in presenceMapping) {
226
+ if (typeof newState[key] === "function") {
227
+ throw mappingToFunctionIsNotAllowed("value");
228
+ }
229
+ if (oldState[key] !== newState[key]) {
230
+ room.updatePresence({ [key]: newState[key] });
231
+ }
232
+ }
233
+ }
234
+ function isObject(value) {
235
+ return Object.prototype.toString.call(value) === "[object Object]";
236
+ }
237
+ function validateNoDuplicateKeys(storageMapping, presenceMapping) {
238
+ for (const key in storageMapping) {
239
+ if (presenceMapping[key] !== void 0) {
240
+ throw mappingShouldNotHaveTheSameKeys(key);
241
+ }
242
+ }
243
+ }
244
+ function patchState(state, updates, mapping) {
245
+ const partialState = {};
246
+ for (const key in mapping) {
247
+ partialState[key] = state[key];
248
+ }
249
+ const patched = patchImmutableObject(partialState, updates);
250
+ const result = {};
251
+ for (const key in mapping) {
252
+ result[key] = patched[key];
253
+ }
254
+ return result;
255
+ }
256
+ function validateMapping(mapping, mappingType) {
257
+ if (process.env.NODE_ENV !== "production") {
258
+ if (mapping == null) {
259
+ throw missingMapping(mappingType);
260
+ }
261
+ if (!isObject(mapping)) {
262
+ throw mappingShouldBeAnObject(mappingType);
263
+ }
264
+ }
265
+ const result = {};
266
+ for (const key in mapping) {
267
+ if (process.env.NODE_ENV !== "production" && typeof mapping[key] !== "boolean") {
268
+ throw mappingValueShouldBeABoolean(mappingType, key);
269
+ }
270
+ if (mapping[key] === true) {
271
+ result[key] = true;
272
+ }
273
+ }
274
+ return result;
275
+ }
276
+
277
+ export { enhancer, enterRoom, leaveRoom };
@@ -0,0 +1,277 @@
1
+ import { patchLiveObjectKey, liveNodeToJson, patchImmutableObject } from '@liveblocks/client';
2
+
3
+ const ERROR_PREFIX = "Invalid @liveblocks/redux middleware config.";
4
+ function missingClient() {
5
+ return new Error(`${ERROR_PREFIX} client is missing`);
6
+ }
7
+ function missingMapping(mappingType) {
8
+ return new Error(`${ERROR_PREFIX} ${mappingType} is missing.`);
9
+ }
10
+ function mappingShouldBeAnObject(mappingType) {
11
+ return new Error(`${ERROR_PREFIX} ${mappingType} should be an object where the values are boolean.`);
12
+ }
13
+ function mappingValueShouldBeABoolean(mappingType, key) {
14
+ return new Error(`${ERROR_PREFIX} ${mappingType}.${key} value should be a boolean`);
15
+ }
16
+ function mappingShouldNotHaveTheSameKeys(key) {
17
+ return new Error(`${ERROR_PREFIX} "${key}" is mapped on presenceMapping and storageMapping. A key shouldn't exist on both mapping.`);
18
+ }
19
+ function mappingToFunctionIsNotAllowed(key) {
20
+ return new Error(`${ERROR_PREFIX} mapping.${key} is invalid. Mapping to a function is not allowed.`);
21
+ }
22
+
23
+ var __defProp = Object.defineProperty;
24
+ var __defProps = Object.defineProperties;
25
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
26
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
27
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
28
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
29
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
30
+ var __spreadValues = (a, b) => {
31
+ for (var prop in b || (b = {}))
32
+ if (__hasOwnProp.call(b, prop))
33
+ __defNormalProp(a, prop, b[prop]);
34
+ if (__getOwnPropSymbols)
35
+ for (var prop of __getOwnPropSymbols(b)) {
36
+ if (__propIsEnum.call(b, prop))
37
+ __defNormalProp(a, prop, b[prop]);
38
+ }
39
+ return a;
40
+ };
41
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
42
+ const ACTION_TYPES = {
43
+ ENTER: "@@LIVEBLOCKS/ENTER",
44
+ LEAVE: "@@LIVEBLOCKS/LEAVE",
45
+ START_LOADING_STORAGE: "@@LIVEBLOCKS/START_LOADING_STORAGE",
46
+ INIT_STORAGE: "@@LIVEBLOCKS/INIT_STORAGE",
47
+ PATCH_STORAGE: "@@LIVEBLOCKS/PATCH_STORAGE",
48
+ UPDATE_CONNECTION: "@@LIVEBLOCKS/UPDATE_CONNECTION",
49
+ UPDATE_OTHERS: "@@LIVEBLOCKS/UPDATE_OTHERS"
50
+ };
51
+ const internalPlugin = (options) => {
52
+ if (process.env.NODE_ENV !== "production" && options.client == null) {
53
+ throw missingClient();
54
+ }
55
+ const client = options.client;
56
+ const mapping = validateMapping(options.storageMapping, "storageMapping");
57
+ const presenceMapping = validateMapping(options.presenceMapping || {}, "presenceMapping");
58
+ if (process.env.NODE_ENV !== "production") {
59
+ validateNoDuplicateKeys(mapping, presenceMapping);
60
+ }
61
+ return (createStore) => (reducer, initialState, enhancer2) => {
62
+ let room = null;
63
+ let isPatching = false;
64
+ let storageRoot = null;
65
+ let unsubscribeCallbacks = [];
66
+ const newReducer = (state, action) => {
67
+ switch (action.type) {
68
+ case ACTION_TYPES.PATCH_STORAGE:
69
+ return __spreadValues(__spreadValues({}, state), action.state);
70
+ case ACTION_TYPES.INIT_STORAGE:
71
+ return __spreadProps(__spreadValues(__spreadValues({}, state), action.state), {
72
+ liveblocks: __spreadProps(__spreadValues({}, state.liveblocks), {
73
+ isStorageLoading: false
74
+ })
75
+ });
76
+ case ACTION_TYPES.START_LOADING_STORAGE:
77
+ return __spreadProps(__spreadValues({}, state), {
78
+ liveblocks: __spreadProps(__spreadValues({}, state.liveblocks), {
79
+ isStorageLoading: true
80
+ })
81
+ });
82
+ case ACTION_TYPES.UPDATE_CONNECTION: {
83
+ return __spreadProps(__spreadValues({}, state), {
84
+ liveblocks: __spreadProps(__spreadValues({}, state.liveblocks), {
85
+ connection: action.connection
86
+ })
87
+ });
88
+ }
89
+ case ACTION_TYPES.UPDATE_OTHERS: {
90
+ return __spreadProps(__spreadValues({}, state), {
91
+ liveblocks: __spreadProps(__spreadValues({}, state.liveblocks), {
92
+ others: action.others
93
+ })
94
+ });
95
+ }
96
+ default: {
97
+ const newState = reducer(state, action);
98
+ if (room) {
99
+ isPatching = true;
100
+ updatePresence(room, state, newState, presenceMapping);
101
+ room.batch(() => {
102
+ if (storageRoot) {
103
+ patchLiveblocksStorage(storageRoot, state, newState, mapping);
104
+ }
105
+ });
106
+ isPatching = false;
107
+ }
108
+ if (newState.liveblocks == null) {
109
+ return __spreadProps(__spreadValues({}, newState), {
110
+ liveblocks: {
111
+ others: [],
112
+ isStorageLoading: false,
113
+ connection: "closed"
114
+ }
115
+ });
116
+ }
117
+ return newState;
118
+ }
119
+ }
120
+ };
121
+ const store = createStore(newReducer, initialState, enhancer2);
122
+ function enterRoom2(roomId, storageInitialState = {}, reduxState) {
123
+ if (storageRoot) {
124
+ return;
125
+ }
126
+ room = client.enter(roomId);
127
+ broadcastInitialPresence(room, reduxState, presenceMapping);
128
+ unsubscribeCallbacks.push(room.subscribe("connection", () => {
129
+ store.dispatch({
130
+ type: ACTION_TYPES.UPDATE_CONNECTION,
131
+ connection: room.getConnectionState()
132
+ });
133
+ }));
134
+ unsubscribeCallbacks.push(room.subscribe("others", (others) => {
135
+ store.dispatch({
136
+ type: ACTION_TYPES.UPDATE_OTHERS,
137
+ others: others.toArray()
138
+ });
139
+ }));
140
+ store.dispatch({
141
+ type: ACTION_TYPES.START_LOADING_STORAGE
142
+ });
143
+ room.getStorage().then(({ root }) => {
144
+ const updates = {};
145
+ room.batch(() => {
146
+ for (const key in mapping) {
147
+ const liveblocksStatePart = root.get(key);
148
+ if (liveblocksStatePart == null) {
149
+ updates[key] = storageInitialState[key];
150
+ patchLiveObjectKey(root, key, void 0, storageInitialState[key]);
151
+ } else {
152
+ updates[key] = liveNodeToJson(liveblocksStatePart);
153
+ }
154
+ }
155
+ });
156
+ store.dispatch({
157
+ type: ACTION_TYPES.INIT_STORAGE,
158
+ state: updates
159
+ });
160
+ storageRoot = root;
161
+ unsubscribeCallbacks.push(room.subscribe(root, (updates2) => {
162
+ if (isPatching === false) {
163
+ store.dispatch({
164
+ type: ACTION_TYPES.PATCH_STORAGE,
165
+ state: patchState(store.getState(), updates2, mapping)
166
+ });
167
+ }
168
+ }, { isDeep: true }));
169
+ });
170
+ }
171
+ function leaveRoom2(roomId) {
172
+ for (const unsubscribe of unsubscribeCallbacks) {
173
+ unsubscribe();
174
+ }
175
+ storageRoot = null;
176
+ room = null;
177
+ isPatching = false;
178
+ unsubscribeCallbacks = [];
179
+ client.leave(roomId);
180
+ }
181
+ function newDispatch(action, state) {
182
+ if (action.type === ACTION_TYPES.ENTER) {
183
+ enterRoom2(action.roomId, action.initialState, store.getState());
184
+ } else if (action.type === ACTION_TYPES.LEAVE) {
185
+ leaveRoom2(action.roomId);
186
+ } else {
187
+ store.dispatch(action, state);
188
+ }
189
+ }
190
+ return __spreadProps(__spreadValues({}, store), {
191
+ dispatch: newDispatch
192
+ });
193
+ };
194
+ };
195
+ function enterRoom(roomId, initialState) {
196
+ return {
197
+ type: ACTION_TYPES.ENTER,
198
+ roomId,
199
+ initialState
200
+ };
201
+ }
202
+ function leaveRoom(roomId) {
203
+ return {
204
+ type: ACTION_TYPES.LEAVE,
205
+ roomId
206
+ };
207
+ }
208
+ const enhancer = internalPlugin;
209
+ function patchLiveblocksStorage(root, oldState, newState, mapping) {
210
+ for (const key in mapping) {
211
+ if (process.env.NODE_ENV !== "production" && typeof newState[key] === "function") {
212
+ throw mappingToFunctionIsNotAllowed("value");
213
+ }
214
+ if (oldState[key] !== newState[key]) {
215
+ patchLiveObjectKey(root, key, oldState[key], newState[key]);
216
+ }
217
+ }
218
+ }
219
+ function broadcastInitialPresence(room, state, mapping) {
220
+ for (const key in mapping) {
221
+ room == null ? void 0 : room.updatePresence({ [key]: state[key] });
222
+ }
223
+ }
224
+ function updatePresence(room, oldState, newState, presenceMapping) {
225
+ for (const key in presenceMapping) {
226
+ if (typeof newState[key] === "function") {
227
+ throw mappingToFunctionIsNotAllowed("value");
228
+ }
229
+ if (oldState[key] !== newState[key]) {
230
+ room.updatePresence({ [key]: newState[key] });
231
+ }
232
+ }
233
+ }
234
+ function isObject(value) {
235
+ return Object.prototype.toString.call(value) === "[object Object]";
236
+ }
237
+ function validateNoDuplicateKeys(storageMapping, presenceMapping) {
238
+ for (const key in storageMapping) {
239
+ if (presenceMapping[key] !== void 0) {
240
+ throw mappingShouldNotHaveTheSameKeys(key);
241
+ }
242
+ }
243
+ }
244
+ function patchState(state, updates, mapping) {
245
+ const partialState = {};
246
+ for (const key in mapping) {
247
+ partialState[key] = state[key];
248
+ }
249
+ const patched = patchImmutableObject(partialState, updates);
250
+ const result = {};
251
+ for (const key in mapping) {
252
+ result[key] = patched[key];
253
+ }
254
+ return result;
255
+ }
256
+ function validateMapping(mapping, mappingType) {
257
+ if (process.env.NODE_ENV !== "production") {
258
+ if (mapping == null) {
259
+ throw missingMapping(mappingType);
260
+ }
261
+ if (!isObject(mapping)) {
262
+ throw mappingShouldBeAnObject(mappingType);
263
+ }
264
+ }
265
+ const result = {};
266
+ for (const key in mapping) {
267
+ if (process.env.NODE_ENV !== "production" && typeof mapping[key] !== "boolean") {
268
+ throw mappingValueShouldBeABoolean(mappingType, key);
269
+ }
270
+ if (mapping[key] === true) {
271
+ result[key] = true;
272
+ }
273
+ }
274
+ return result;
275
+ }
276
+
277
+ export { enhancer, enterRoom, leaveRoom };
package/lib/index.d.ts ADDED
@@ -0,0 +1,38 @@
1
+ import { Client, User } from "@liveblocks/client";
2
+ import { StoreEnhancer } from "redux";
3
+ export declare type Mapping<T> = Partial<{
4
+ [Property in keyof T]: boolean;
5
+ }>;
6
+ export declare type LiveblocksState<TState, TPresence = any> = TState & {
7
+ /**
8
+ * Liveblocks extra state attached by the middleware
9
+ */
10
+ readonly liveblocks: {
11
+ /**
12
+ * Other users in the room. Empty no room is currently synced
13
+ */
14
+ readonly others: Array<User<TPresence>>;
15
+ /**
16
+ * Whether or not the room storage is currently loading
17
+ */
18
+ readonly isStorageLoading: boolean;
19
+ /**
20
+ * Connection state of the room
21
+ */
22
+ readonly connection: "closed" | "authenticating" | "unavailable" | "failed" | "open" | "connecting";
23
+ };
24
+ };
25
+ export declare function enterRoom(roomId: string, initialState?: any): {
26
+ type: string;
27
+ roomId: string;
28
+ initialState: any;
29
+ };
30
+ export declare function leaveRoom(roomId: string): {
31
+ type: string;
32
+ roomId: string;
33
+ };
34
+ export declare const enhancer: <T>(options: {
35
+ client: Client;
36
+ storageMapping: Partial<{ [Property in keyof T]: boolean; }>;
37
+ presenceMapping?: Partial<{ [Property in keyof T]: boolean; }> | undefined;
38
+ }) => StoreEnhancer;
package/lib/index.js ADDED
@@ -0,0 +1,377 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var client = require('@liveblocks/client');
6
+
7
+ function _extends() {
8
+ _extends = Object.assign || function (target) {
9
+ for (var i = 1; i < arguments.length; i++) {
10
+ var source = arguments[i];
11
+
12
+ for (var key in source) {
13
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
14
+ target[key] = source[key];
15
+ }
16
+ }
17
+ }
18
+
19
+ return target;
20
+ };
21
+
22
+ return _extends.apply(this, arguments);
23
+ }
24
+
25
+ function _unsupportedIterableToArray(o, minLen) {
26
+ if (!o) return;
27
+ if (typeof o === "string") return _arrayLikeToArray(o, minLen);
28
+ var n = Object.prototype.toString.call(o).slice(8, -1);
29
+ if (n === "Object" && o.constructor) n = o.constructor.name;
30
+ if (n === "Map" || n === "Set") return Array.from(o);
31
+ if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
32
+ }
33
+
34
+ function _arrayLikeToArray(arr, len) {
35
+ if (len == null || len > arr.length) len = arr.length;
36
+
37
+ for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
38
+
39
+ return arr2;
40
+ }
41
+
42
+ function _createForOfIteratorHelperLoose(o, allowArrayLike) {
43
+ var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"];
44
+ if (it) return (it = it.call(o)).next.bind(it);
45
+
46
+ if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") {
47
+ if (it) o = it;
48
+ var i = 0;
49
+ return function () {
50
+ if (i >= o.length) return {
51
+ done: true
52
+ };
53
+ return {
54
+ done: false,
55
+ value: o[i++]
56
+ };
57
+ };
58
+ }
59
+
60
+ throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
61
+ }
62
+
63
+ var ERROR_PREFIX = "Invalid @liveblocks/redux middleware config.";
64
+ function missingClient() {
65
+ return new Error(ERROR_PREFIX + " client is missing");
66
+ }
67
+ function missingMapping(mappingType) {
68
+ return new Error(ERROR_PREFIX + " " + mappingType + " is missing.");
69
+ }
70
+ function mappingShouldBeAnObject(mappingType) {
71
+ return new Error(ERROR_PREFIX + " " + mappingType + " should be an object where the values are boolean.");
72
+ }
73
+ function mappingValueShouldBeABoolean(mappingType, key) {
74
+ return new Error(ERROR_PREFIX + " " + mappingType + "." + key + " value should be a boolean");
75
+ }
76
+ function mappingShouldNotHaveTheSameKeys(key) {
77
+ return new Error(ERROR_PREFIX + " \"" + key + "\" is mapped on presenceMapping and storageMapping. A key shouldn't exist on both mapping.");
78
+ }
79
+ function mappingToFunctionIsNotAllowed(key) {
80
+ return new Error(ERROR_PREFIX + " mapping." + key + " is invalid. Mapping to a function is not allowed.");
81
+ }
82
+
83
+ var ACTION_TYPES = {
84
+ ENTER: "@@LIVEBLOCKS/ENTER",
85
+ LEAVE: "@@LIVEBLOCKS/LEAVE",
86
+ START_LOADING_STORAGE: "@@LIVEBLOCKS/START_LOADING_STORAGE",
87
+ INIT_STORAGE: "@@LIVEBLOCKS/INIT_STORAGE",
88
+ PATCH_STORAGE: "@@LIVEBLOCKS/PATCH_STORAGE",
89
+ UPDATE_CONNECTION: "@@LIVEBLOCKS/UPDATE_CONNECTION",
90
+ UPDATE_OTHERS: "@@LIVEBLOCKS/UPDATE_OTHERS"
91
+ };
92
+
93
+ var internalPlugin = function internalPlugin(options) {
94
+ if (process.env.NODE_ENV !== "production" && options.client == null) {
95
+ throw missingClient();
96
+ }
97
+
98
+ var client$1 = options.client;
99
+ var mapping = validateMapping(options.storageMapping, "storageMapping");
100
+ var presenceMapping = validateMapping(options.presenceMapping || {}, "presenceMapping");
101
+
102
+ if (process.env.NODE_ENV !== "production") {
103
+ validateNoDuplicateKeys(mapping, presenceMapping);
104
+ }
105
+
106
+ return function (createStore) {
107
+ return function (reducer, initialState, enhancer) {
108
+ var room = null;
109
+ var isPatching = false;
110
+ var storageRoot = null;
111
+ var unsubscribeCallbacks = [];
112
+
113
+ var newReducer = function newReducer(state, action) {
114
+ switch (action.type) {
115
+ case ACTION_TYPES.PATCH_STORAGE:
116
+ return _extends({}, state, action.state);
117
+
118
+ case ACTION_TYPES.INIT_STORAGE:
119
+ return _extends({}, state, action.state, {
120
+ liveblocks: _extends({}, state.liveblocks, {
121
+ isStorageLoading: false
122
+ })
123
+ });
124
+
125
+ case ACTION_TYPES.START_LOADING_STORAGE:
126
+ return _extends({}, state, {
127
+ liveblocks: _extends({}, state.liveblocks, {
128
+ isStorageLoading: true
129
+ })
130
+ });
131
+
132
+ case ACTION_TYPES.UPDATE_CONNECTION:
133
+ {
134
+ return _extends({}, state, {
135
+ liveblocks: _extends({}, state.liveblocks, {
136
+ connection: action.connection
137
+ })
138
+ });
139
+ }
140
+
141
+ case ACTION_TYPES.UPDATE_OTHERS:
142
+ {
143
+ return _extends({}, state, {
144
+ liveblocks: _extends({}, state.liveblocks, {
145
+ others: action.others
146
+ })
147
+ });
148
+ }
149
+
150
+ default:
151
+ {
152
+ var newState = reducer(state, action);
153
+
154
+ if (room) {
155
+ isPatching = true;
156
+ updatePresence(room, state, newState, presenceMapping);
157
+ room.batch(function () {
158
+ if (storageRoot) {
159
+ patchLiveblocksStorage(storageRoot, state, newState, mapping);
160
+ }
161
+ });
162
+ isPatching = false;
163
+ }
164
+
165
+ if (newState.liveblocks == null) {
166
+ return _extends({}, newState, {
167
+ liveblocks: {
168
+ others: [],
169
+ isStorageLoading: false,
170
+ connection: "closed"
171
+ }
172
+ });
173
+ }
174
+
175
+ return newState;
176
+ }
177
+ }
178
+ };
179
+
180
+ var store = createStore(newReducer, initialState, enhancer);
181
+
182
+ function enterRoom(roomId, storageInitialState, reduxState) {
183
+ if (storageInitialState === void 0) {
184
+ storageInitialState = {};
185
+ }
186
+
187
+ if (storageRoot) {
188
+ return;
189
+ }
190
+
191
+ room = client$1.enter(roomId);
192
+ broadcastInitialPresence(room, reduxState, presenceMapping);
193
+ unsubscribeCallbacks.push(room.subscribe("connection", function () {
194
+ store.dispatch({
195
+ type: ACTION_TYPES.UPDATE_CONNECTION,
196
+ connection: room.getConnectionState()
197
+ });
198
+ }));
199
+ unsubscribeCallbacks.push(room.subscribe("others", function (others) {
200
+ store.dispatch({
201
+ type: ACTION_TYPES.UPDATE_OTHERS,
202
+ others: others.toArray()
203
+ });
204
+ }));
205
+ store.dispatch({
206
+ type: ACTION_TYPES.START_LOADING_STORAGE
207
+ });
208
+ room.getStorage().then(function (_ref) {
209
+ var root = _ref.root;
210
+ var updates = {};
211
+ room.batch(function () {
212
+ for (var key in mapping) {
213
+ var liveblocksStatePart = root.get(key);
214
+
215
+ if (liveblocksStatePart == null) {
216
+ updates[key] = storageInitialState[key];
217
+ client.patchLiveObjectKey(root, key, undefined, storageInitialState[key]);
218
+ } else {
219
+ updates[key] = client.liveNodeToJson(liveblocksStatePart);
220
+ }
221
+ }
222
+ });
223
+ store.dispatch({
224
+ type: ACTION_TYPES.INIT_STORAGE,
225
+ state: updates
226
+ });
227
+ storageRoot = root;
228
+ unsubscribeCallbacks.push(room.subscribe(root, function (updates) {
229
+ if (isPatching === false) {
230
+ store.dispatch({
231
+ type: ACTION_TYPES.PATCH_STORAGE,
232
+ state: patchState(store.getState(), updates, mapping)
233
+ });
234
+ }
235
+ }, {
236
+ isDeep: true
237
+ }));
238
+ });
239
+ }
240
+
241
+ function leaveRoom(roomId) {
242
+ for (var _iterator = _createForOfIteratorHelperLoose(unsubscribeCallbacks), _step; !(_step = _iterator()).done;) {
243
+ var unsubscribe = _step.value;
244
+ unsubscribe();
245
+ }
246
+
247
+ storageRoot = null;
248
+ room = null;
249
+ isPatching = false;
250
+ unsubscribeCallbacks = [];
251
+ client$1.leave(roomId);
252
+ }
253
+
254
+ function newDispatch(action, state) {
255
+ if (action.type === ACTION_TYPES.ENTER) {
256
+ enterRoom(action.roomId, action.initialState, store.getState());
257
+ } else if (action.type === ACTION_TYPES.LEAVE) {
258
+ leaveRoom(action.roomId);
259
+ } else {
260
+ store.dispatch(action, state);
261
+ }
262
+ }
263
+
264
+ return _extends({}, store, {
265
+ dispatch: newDispatch
266
+ });
267
+ };
268
+ };
269
+ };
270
+
271
+ function enterRoom(roomId, initialState) {
272
+ return {
273
+ type: ACTION_TYPES.ENTER,
274
+ roomId: roomId,
275
+ initialState: initialState
276
+ };
277
+ }
278
+ function leaveRoom(roomId) {
279
+ return {
280
+ type: ACTION_TYPES.LEAVE,
281
+ roomId: roomId
282
+ };
283
+ }
284
+ var enhancer = internalPlugin;
285
+
286
+ function patchLiveblocksStorage(root, oldState, newState, mapping) {
287
+ for (var key in mapping) {
288
+ if (process.env.NODE_ENV !== "production" && typeof newState[key] === "function") {
289
+ throw mappingToFunctionIsNotAllowed("value");
290
+ }
291
+
292
+ if (oldState[key] !== newState[key]) {
293
+ client.patchLiveObjectKey(root, key, oldState[key], newState[key]);
294
+ }
295
+ }
296
+ }
297
+
298
+ function broadcastInitialPresence(room, state, mapping) {
299
+ for (var key in mapping) {
300
+ var _room$updatePresence;
301
+
302
+ room == null ? void 0 : room.updatePresence((_room$updatePresence = {}, _room$updatePresence[key] = state[key], _room$updatePresence));
303
+ }
304
+ }
305
+
306
+ function updatePresence(room, oldState, newState, presenceMapping) {
307
+ for (var key in presenceMapping) {
308
+ if (typeof newState[key] === "function") {
309
+ throw mappingToFunctionIsNotAllowed("value");
310
+ }
311
+
312
+ if (oldState[key] !== newState[key]) {
313
+ var _room$updatePresence2;
314
+
315
+ room.updatePresence((_room$updatePresence2 = {}, _room$updatePresence2[key] = newState[key], _room$updatePresence2));
316
+ }
317
+ }
318
+ }
319
+
320
+ function isObject(value) {
321
+ return Object.prototype.toString.call(value) === "[object Object]";
322
+ }
323
+
324
+ function validateNoDuplicateKeys(storageMapping, presenceMapping) {
325
+ for (var key in storageMapping) {
326
+ if (presenceMapping[key] !== undefined) {
327
+ throw mappingShouldNotHaveTheSameKeys(key);
328
+ }
329
+ }
330
+ }
331
+
332
+ function patchState(state, updates, mapping) {
333
+ var partialState = {};
334
+
335
+ for (var key in mapping) {
336
+ partialState[key] = state[key];
337
+ }
338
+
339
+ var patched = client.patchImmutableObject(partialState, updates);
340
+ var result = {};
341
+
342
+ for (var _key in mapping) {
343
+ result[_key] = patched[_key];
344
+ }
345
+
346
+ return result;
347
+ }
348
+
349
+ function validateMapping(mapping, mappingType) {
350
+ if (process.env.NODE_ENV !== "production") {
351
+ if (mapping == null) {
352
+ throw missingMapping(mappingType);
353
+ }
354
+
355
+ if (!isObject(mapping)) {
356
+ throw mappingShouldBeAnObject(mappingType);
357
+ }
358
+ }
359
+
360
+ var result = {};
361
+
362
+ for (var key in mapping) {
363
+ if (process.env.NODE_ENV !== "production" && typeof mapping[key] !== "boolean") {
364
+ throw mappingValueShouldBeABoolean(mappingType, key);
365
+ }
366
+
367
+ if (mapping[key] === true) {
368
+ result[key] = true;
369
+ }
370
+ }
371
+
372
+ return result;
373
+ }
374
+
375
+ exports.enhancer = enhancer;
376
+ exports.enterRoom = enterRoom;
377
+ exports.leaveRoom = leaveRoom;
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@liveblocks/redux",
3
+ "version": "0.15.0-alpha.3",
4
+ "sideEffects": false,
5
+ "description": "",
6
+ "main": "./lib/index.js",
7
+ "types": "./lib/index.d.ts",
8
+ "files": [
9
+ "lib/"
10
+ ],
11
+ "exports": {
12
+ ".": {
13
+ "types": "./lib/index.d.ts",
14
+ "module": "./lib/esm/index.js",
15
+ "import": "./lib/esm/index.mjs",
16
+ "default": "./lib/index.js"
17
+ }
18
+ },
19
+ "keywords": [
20
+ "redux",
21
+ "react",
22
+ "liveblocks",
23
+ "multiplayer",
24
+ "live-cursors",
25
+ "collaborative"
26
+ ],
27
+ "scripts": {
28
+ "build": "rollup -c",
29
+ "test": "jest --watch",
30
+ "dtslint": "dtslint --localTs node_modules/typescript/lib --expectOnly types"
31
+ },
32
+ "license": "Apache-2.0",
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "https://github.com/liveblocks/liveblocks.git",
36
+ "directory": "packages/liveblocks-redux"
37
+ },
38
+ "peerDependencies": {
39
+ "@liveblocks/client": "0.15.0-alpha.3",
40
+ "redux": "^4"
41
+ },
42
+ "devDependencies": {
43
+ "@babel/core": "^7.16.7",
44
+ "@babel/plugin-transform-typescript": "^7.16.8",
45
+ "@babel/preset-env": "^7.16.8",
46
+ "@definitelytyped/dtslint": "^0.0.103",
47
+ "@reduxjs/toolkit": "^1.7.2",
48
+ "@rollup/plugin-babel": "^5.3.0",
49
+ "@rollup/plugin-node-resolve": "^13.1.3",
50
+ "@rollup/plugin-typescript": "^8.3.0",
51
+ "@testing-library/jest-dom": "^5.16.1",
52
+ "@testing-library/react": "^12.1.2",
53
+ "@testing-library/react-hooks": "^7.0.2",
54
+ "@types/jest": "^27.4.1",
55
+ "esbuild": "0.14.11",
56
+ "jest": "^27.4.7",
57
+ "msw": "^0.36.4",
58
+ "redux": "^4.1.2",
59
+ "rollup": "^2.64.0",
60
+ "rollup-plugin-esbuild": "^4.8.2",
61
+ "whatwg-fetch": "^3.6.2"
62
+ }
63
+ }