playsocketjs 2.0.0 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -13
- package/dist/playsocket-client.js +45 -43
- package/dist/playsocket-server.js +45 -43
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -77,7 +77,7 @@ socket.updateStorage('latestPlayer', 'set', 'Player4'); // Regular synced storag
|
|
|
77
77
|
### Constructor
|
|
78
78
|
|
|
79
79
|
Creates a new PlaySocket instance with a specified ID and configuration options.
|
|
80
|
-
The
|
|
80
|
+
The ID can be set to `null` to let the server pick a unique one.
|
|
81
81
|
|
|
82
82
|
```javascript
|
|
83
83
|
new PlaySocket(id?: string, options: PlaySocketOptions)
|
|
@@ -90,8 +90,8 @@ new PlaySocket(id?: string, options: PlaySocketOptions)
|
|
|
90
90
|
|
|
91
91
|
### Methods
|
|
92
92
|
|
|
93
|
-
- `init()`: Initialize the WebSocket connection – Returns Promise (async) which resolves with the client's
|
|
94
|
-
- `createRoom(initialStorage?: object, maxSize?: number)`: Create a new room and become host – Returns Promise (async) which resolves with the room
|
|
93
|
+
- `init()`: Initialize the WebSocket connection – Returns Promise (async) which resolves with the client's ID
|
|
94
|
+
- `createRoom(initialStorage?: object, maxSize?: number)`: Create a new room and become host – Returns Promise (async) which resolves with the room ID (matches the creator's ID)
|
|
95
95
|
- `joinRoom(hostId: string)`: Join an existing room – Returns Promise (async)
|
|
96
96
|
- `destroy()`: Use this to leave a room and close the connection
|
|
97
97
|
- `updateStorage(key: string, type: 'set' | 'array-add' | 'array-add-unique' | 'array-remove-matching' | 'array-update-matching', value: any, updateValue?: any)`: Update the shared storage. Safely update arrays in storage by adding, removing, or updating items. UpdateValue is only required for the 'array-update-matching' operation type
|
|
@@ -103,9 +103,9 @@ new PlaySocket(id?: string, options: PlaySocketOptions)
|
|
|
103
103
|
- `error`: Error events (returns error `string`)
|
|
104
104
|
- `instanceDestroyed`: Destruction event - triggered by manual .destroy() method invocation or by fatal errors and disconnects
|
|
105
105
|
- `storageUpdated`: Storage state changes (returns storage `object`)
|
|
106
|
-
- `hostMigrated`: Host changes (returns the new host's
|
|
107
|
-
- `clientConnected`: New client connected to the room (returns client
|
|
108
|
-
- `clientDisconnected`: Client disconnected from the room (returns client
|
|
106
|
+
- `hostMigrated`: Host changes (returns the new host's ID `string`)
|
|
107
|
+
- `clientConnected`: New client connected to the room (returns client's ID `string`)
|
|
108
|
+
- `clientDisconnected`: Client disconnected from the room (returns client's ID `string`, room ID (if available) `string`)
|
|
109
109
|
|
|
110
110
|
### Properties (Read-only)
|
|
111
111
|
|
|
@@ -209,13 +209,13 @@ Creates a new PlaySocket Server instance with configuration options.
|
|
|
209
209
|
|
|
210
210
|
#### Event types
|
|
211
211
|
|
|
212
|
-
- `clientRegistered`: Client registered with the server (returns the client's
|
|
213
|
-
- `clientDisconnected`: Client disconnected from the server (returns the client's
|
|
214
|
-
- `clientJoinedRoom`: Client joined a room – note that clients can only leave by disconnecting (returns the client's
|
|
215
|
-
- `roomCreated`: Client created a room (returns room
|
|
216
|
-
- `roomDestroyed`: Room was destroyed, this happens when all participants leave (returns room
|
|
217
|
-
- `roomCreationRequested`: Room creation requested by client (returns `object` containing the client's
|
|
218
|
-
- `storageUpdateRequested`: Room storage property update requested by client (returns `object` containing the client's
|
|
212
|
+
- `clientRegistered`: Client registered with the server (returns the client's ID `string`, customData `object`)
|
|
213
|
+
- `clientDisconnected`: Client disconnected from the server (returns the client's ID `string`)
|
|
214
|
+
- `clientJoinedRoom`: Client joined a room – note that clients can only leave by disconnecting (returns the client's ID `string`, room ID `string`)
|
|
215
|
+
- `roomCreated`: Client created a room (returns room ID `string`)
|
|
216
|
+
- `roomDestroyed`: Room was destroyed, this happens when all participants leave (returns room ID `string`)
|
|
217
|
+
- `roomCreationRequested`: Room creation requested by client (returns `object` containing the client's ID `string`, room ID `string` and the initialStorage `object`) – if you return an `object` in the callback, it will take that as the initial storage instead
|
|
218
|
+
- `storageUpdateRequested`: Room storage property update requested by client (returns `object` containing the client's ID `string`, room ID `string` and the update `object`) – if you return `false` in the callback, the update will be blocked
|
|
219
219
|
|
|
220
220
|
### Properties (Read-only)
|
|
221
221
|
|
|
@@ -1687,7 +1687,7 @@ function requireCrdtManager () {
|
|
|
1687
1687
|
hasRequiredCrdtManager = 1;
|
|
1688
1688
|
// Custom conflict-free replicated data type system with vector clocks
|
|
1689
1689
|
|
|
1690
|
-
const CONSOLE_PREFIX = "CRDT
|
|
1690
|
+
const CONSOLE_PREFIX = "PlaySocket CRDT manager: ";
|
|
1691
1691
|
|
|
1692
1692
|
class CRDTManager {
|
|
1693
1693
|
// Storage
|
|
@@ -1835,50 +1835,53 @@ function requireCrdtManager () {
|
|
|
1835
1835
|
const MIN_GC_DELAY = 1000; // Minimum 1s delay between garbage collection runs
|
|
1836
1836
|
const MIN_AGE_FOR_GC = 5000; // Garbage collect ops that are older than 5s
|
|
1837
1837
|
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1838
|
+
try {
|
|
1839
|
+
if (Date.now() - this.#lastGCCheck < MIN_GC_DELAY) return;
|
|
1840
|
+
this.#lastGCCheck = Date.now();
|
|
1841
|
+
|
|
1842
|
+
for (const [key, operations] of this.#keyOperations.entries()) {
|
|
1843
|
+
if (operations?.length < 5) continue; // Min op amount per key for garbage collection to run (otherwise not worth it)
|
|
1844
|
+
|
|
1845
|
+
let retainCount = operations.length;
|
|
1846
|
+
operations.forEach((op, index) => {
|
|
1847
|
+
// Count how many ops, from last to latest, are older than the min-age in a row
|
|
1848
|
+
if (op.uuid && this.#opUuidTimestamp.has(op.uuid) && (Date.now() - this.#opUuidTimestamp.get(op.uuid)) > MIN_AGE_FOR_GC) {
|
|
1849
|
+
const removeCount = index + 1;
|
|
1850
|
+
retainCount = operations.length - removeCount;
|
|
1851
|
+
this.#opUuidTimestamp.delete(op.uuid); // Remove from timestamps if set to be deleted
|
|
1852
|
+
}
|
|
1853
|
+
});
|
|
1853
1854
|
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1855
|
+
if (retainCount < operations.length) {
|
|
1856
|
+
if (this.#debug) console.log(CONSOLE_PREFIX + `Running garbage collection for key ${key} with current operations:`, operations);
|
|
1857
|
+
const retainOps = operations.slice(-retainCount); // Newest ops
|
|
1858
|
+
const removeOps = operations.slice(0, -retainCount); // Oldest ops (start = idx 0, end = retainCount counted from right side)
|
|
1859
|
+
const baselineVectorClock = removeOps[removeOps.length - 1]?.vectorClock || []; // Use the vector clock from the last operation that we remove/overwrite
|
|
1860
|
+
|
|
1861
|
+
// Calculate the value at the point where retained operations start
|
|
1862
|
+
let baselineValue = null;
|
|
1863
|
+
for (const op of removeOps) {
|
|
1864
|
+
if (!op.data) continue;
|
|
1865
|
+
baselineValue = this.#handleOperation(
|
|
1866
|
+
baselineValue,
|
|
1867
|
+
op.data.type,
|
|
1868
|
+
op.data.value,
|
|
1869
|
+
op.data.updateValue
|
|
1870
|
+
);
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
// Create a compact operation with baseline value and appropriate vector clock
|
|
1874
|
+
const compactOp = this.#createOperation({ type: "set", value: baselineValue }, baselineVectorClock);
|
|
1875
|
+
|
|
1876
|
+
// Debug log
|
|
1877
|
+
if (this.#debug) console.log(CONSOLE_PREFIX + "Operations after garbage collection for this key:", [compactOp, ...retainOps]);
|
|
1878
|
+
|
|
1879
|
+
// Combine compact op with retained operations
|
|
1880
|
+
this.#keyOperations.set(key, [compactOp, ...retainOps]);
|
|
1871
1881
|
}
|
|
1872
|
-
|
|
1873
|
-
// Create a compact operation with baseline value and appropriate vector clock
|
|
1874
|
-
const compactOp = this.#createOperation({ type: "set", value: baselineValue }, baselineVectorClock);
|
|
1875
|
-
|
|
1876
|
-
// Debug log
|
|
1877
|
-
if (this.#debug) console.log(CONSOLE_PREFIX + "Operations after garbage collection for this key:", [compactOp, ...retainOps]);
|
|
1878
|
-
|
|
1879
|
-
// Combine compact op with retained operations
|
|
1880
|
-
this.#keyOperations.set(key, [compactOp, ...retainOps]);
|
|
1881
1882
|
}
|
|
1883
|
+
} catch (error) {
|
|
1884
|
+
console.error(CONSOLE_PREFIX + "Error running garbage collection:", error);
|
|
1882
1885
|
}
|
|
1883
1886
|
}
|
|
1884
1887
|
|
|
@@ -1913,7 +1916,6 @@ function requireCrdtManager () {
|
|
|
1913
1916
|
|
|
1914
1917
|
for (const op of ops) {
|
|
1915
1918
|
if (!op.data) continue;
|
|
1916
|
-
if (op.data.type.startsWith('array') && !Array.isArray(value)) value = []; // Initialize array if required
|
|
1917
1919
|
value = this.#handleOperation(
|
|
1918
1920
|
value,
|
|
1919
1921
|
op.data.type,
|
|
@@ -1813,7 +1813,7 @@ function requireCrdtManager () {
|
|
|
1813
1813
|
hasRequiredCrdtManager = 1;
|
|
1814
1814
|
// Custom conflict-free replicated data type system with vector clocks
|
|
1815
1815
|
|
|
1816
|
-
const CONSOLE_PREFIX = "CRDT
|
|
1816
|
+
const CONSOLE_PREFIX = "PlaySocket CRDT manager: ";
|
|
1817
1817
|
|
|
1818
1818
|
class CRDTManager {
|
|
1819
1819
|
// Storage
|
|
@@ -1961,50 +1961,53 @@ function requireCrdtManager () {
|
|
|
1961
1961
|
const MIN_GC_DELAY = 1000; // Minimum 1s delay between garbage collection runs
|
|
1962
1962
|
const MIN_AGE_FOR_GC = 5000; // Garbage collect ops that are older than 5s
|
|
1963
1963
|
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1964
|
+
try {
|
|
1965
|
+
if (Date.now() - this.#lastGCCheck < MIN_GC_DELAY) return;
|
|
1966
|
+
this.#lastGCCheck = Date.now();
|
|
1967
|
+
|
|
1968
|
+
for (const [key, operations] of this.#keyOperations.entries()) {
|
|
1969
|
+
if (operations?.length < 5) continue; // Min op amount per key for garbage collection to run (otherwise not worth it)
|
|
1970
|
+
|
|
1971
|
+
let retainCount = operations.length;
|
|
1972
|
+
operations.forEach((op, index) => {
|
|
1973
|
+
// Count how many ops, from last to latest, are older than the min-age in a row
|
|
1974
|
+
if (op.uuid && this.#opUuidTimestamp.has(op.uuid) && (Date.now() - this.#opUuidTimestamp.get(op.uuid)) > MIN_AGE_FOR_GC) {
|
|
1975
|
+
const removeCount = index + 1;
|
|
1976
|
+
retainCount = operations.length - removeCount;
|
|
1977
|
+
this.#opUuidTimestamp.delete(op.uuid); // Remove from timestamps if set to be deleted
|
|
1978
|
+
}
|
|
1979
|
+
});
|
|
1979
1980
|
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
}
|
|
1981
|
+
if (retainCount < operations.length) {
|
|
1982
|
+
if (this.#debug) console.log(CONSOLE_PREFIX + `Running garbage collection for key ${key} with current operations:`, operations);
|
|
1983
|
+
const retainOps = operations.slice(-retainCount); // Newest ops
|
|
1984
|
+
const removeOps = operations.slice(0, -retainCount); // Oldest ops (start = idx 0, end = retainCount counted from right side)
|
|
1985
|
+
const baselineVectorClock = removeOps[removeOps.length - 1]?.vectorClock || []; // Use the vector clock from the last operation that we remove/overwrite
|
|
1986
|
+
|
|
1987
|
+
// Calculate the value at the point where retained operations start
|
|
1988
|
+
let baselineValue = null;
|
|
1989
|
+
for (const op of removeOps) {
|
|
1990
|
+
if (!op.data) continue;
|
|
1991
|
+
baselineValue = this.#handleOperation(
|
|
1992
|
+
baselineValue,
|
|
1993
|
+
op.data.type,
|
|
1994
|
+
op.data.value,
|
|
1995
|
+
op.data.updateValue
|
|
1996
|
+
);
|
|
1997
|
+
}
|
|
1998
1998
|
|
|
1999
|
-
|
|
2000
|
-
|
|
1999
|
+
// Create a compact operation with baseline value and appropriate vector clock
|
|
2000
|
+
const compactOp = this.#createOperation({ type: "set", value: baselineValue }, baselineVectorClock);
|
|
2001
2001
|
|
|
2002
|
-
|
|
2003
|
-
|
|
2002
|
+
// Debug log
|
|
2003
|
+
if (this.#debug) console.log(CONSOLE_PREFIX + "Operations after garbage collection for this key:", [compactOp, ...retainOps]);
|
|
2004
2004
|
|
|
2005
|
-
|
|
2006
|
-
|
|
2005
|
+
// Combine compact op with retained operations
|
|
2006
|
+
this.#keyOperations.set(key, [compactOp, ...retainOps]);
|
|
2007
|
+
}
|
|
2007
2008
|
}
|
|
2009
|
+
} catch (error) {
|
|
2010
|
+
console.error(CONSOLE_PREFIX + "Error running garbage collection:", error);
|
|
2008
2011
|
}
|
|
2009
2012
|
}
|
|
2010
2013
|
|
|
@@ -2039,7 +2042,6 @@ function requireCrdtManager () {
|
|
|
2039
2042
|
|
|
2040
2043
|
for (const op of ops) {
|
|
2041
2044
|
if (!op.data) continue;
|
|
2042
|
-
if (op.data.type.startsWith('array') && !Array.isArray(value)) value = []; // Initialize array if required
|
|
2043
2045
|
value = this.#handleOperation(
|
|
2044
2046
|
value,
|
|
2045
2047
|
op.data.type,
|
|
@@ -2495,7 +2497,7 @@ function requireServer () {
|
|
|
2495
2497
|
const requestorRoomId = this.#clientRooms.get(ws.clientId) || null;
|
|
2496
2498
|
this.#triggerEvent("requestReceived", { roomId: requestorRoomId, clientId: ws.clientId, name: data.request.name, data: data.request.data });
|
|
2497
2499
|
break;
|
|
2498
|
-
|
|
2500
|
+
|
|
2499
2501
|
case 'disconnect':
|
|
2500
2502
|
// Client signals to server that it will will willfully disconnect soon
|
|
2501
2503
|
ws.willfulDisconnect = true;
|
|
@@ -2627,7 +2629,7 @@ function requireServer () {
|
|
|
2627
2629
|
}
|
|
2628
2630
|
}
|
|
2629
2631
|
|
|
2630
|
-
this.#triggerEvent("clientDisconnected", ws.clientId);
|
|
2632
|
+
this.#triggerEvent("clientDisconnected", ws.clientId, roomId);
|
|
2631
2633
|
}
|
|
2632
2634
|
|
|
2633
2635
|
/**
|