@kokimoki/app 1.16.0 → 1.16.2

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.
@@ -461,7 +461,7 @@ export class KokimokiClient extends EventEmitter {
461
461
  this._subscriptionsByHash.set(res.roomHash, subscription);
462
462
  await subscription.applyInitialResponse(res.roomHash, res.initialUpdate);
463
463
  // Trigger onJoin event
464
- store.onJoin(this);
464
+ await store.onJoin(this);
465
465
  }
466
466
  }
467
467
  async leave(store) {
@@ -7,23 +7,17 @@ export declare class KokimokiStore<T extends object> {
7
7
  readonly defaultValue: T;
8
8
  readonly mode: RoomSubscriptionMode;
9
9
  readonly doc: Y.Doc;
10
- readonly proxy: T & {
11
- _km_connections?: {
12
- [connectionId: string]: string;
13
- };
14
- };
10
+ readonly proxy: T;
15
11
  readonly docRoot: Y.Map<unknown>;
16
12
  readonly connections: {
13
+ connectionIds: Set<string>;
17
14
  clientIds: Set<string>;
18
15
  };
16
+ private _unsubscribeConnectionsHandler;
19
17
  constructor(roomName: string, defaultValue: T, mode?: RoomSubscriptionMode);
20
- get(): Snapshot<T & {
21
- _km_connections?: {
22
- [connectionId: string]: string;
23
- } | undefined;
24
- }>;
18
+ get(): Snapshot<T>;
25
19
  subscribe(set: (value: Snapshot<T>) => void): () => void;
26
- onJoin(client: KokimokiClient): Promise<void>;
20
+ onJoin(client: KokimokiClient<any>): Promise<void>;
27
21
  onBeforeLeave(client: KokimokiClient): Promise<void>;
28
22
  onLeave(client: KokimokiClient): Promise<void>;
29
23
  }
@@ -1,5 +1,5 @@
1
1
  import * as Y from "yjs";
2
- import { proxy, snapshot, subscribe } from "valtio/vanilla";
2
+ import { snapshot, proxy, subscribe } from "valtio/vanilla";
3
3
  import { bind as yjsBind } from "valtio-yjs";
4
4
  import { RoomSubscriptionMode } from "./room-subscription-mode";
5
5
  export class KokimokiStore {
@@ -10,10 +10,15 @@ export class KokimokiStore {
10
10
  proxy;
11
11
  docRoot;
12
12
  connections;
13
+ _unsubscribeConnectionsHandler = () => { };
13
14
  constructor(roomName, defaultValue, mode = RoomSubscriptionMode.ReadWrite) {
14
15
  this.roomName = roomName;
15
16
  this.defaultValue = defaultValue;
16
17
  this.mode = mode;
18
+ // "_connections" is a reserved key for tracking connections
19
+ if ("_connections" in defaultValue) {
20
+ throw new Error(`"_connections" is a reserved key in KokimokiStore`);
21
+ }
17
22
  // Construct Y doc
18
23
  this.doc = new Y.Doc();
19
24
  this.docRoot = this.doc.getMap("root");
@@ -22,16 +27,9 @@ export class KokimokiStore {
22
27
  // @ts-ignore
23
28
  yjsBind(this.proxy, this.docRoot);
24
29
  // Construct connections proxy
25
- this.connections = proxy({ clientIds: new Set() });
26
- // Update connections.clientIds whenever _km_connections changes
27
- subscribe(this.proxy, () => {
28
- const kmConnections = this.proxy._km_connections;
29
- if (kmConnections) {
30
- this.connections.clientIds = new Set(Object.values(kmConnections));
31
- }
32
- else {
33
- this.connections.clientIds = new Set();
34
- }
30
+ this.connections = proxy({
31
+ connectionIds: new Set(),
32
+ clientIds: new Set(),
35
33
  });
36
34
  }
37
35
  get() {
@@ -43,7 +41,77 @@ export class KokimokiStore {
43
41
  set(this.get());
44
42
  return () => this.doc.off("update", handler);
45
43
  }
46
- async onJoin(client) { }
44
+ async onJoin(client) {
45
+ // Update connections whenever _connections changes
46
+ let prevConnectionIds = new Set();
47
+ let prevClientIds = new Set();
48
+ this._unsubscribeConnectionsHandler = subscribe(this.proxy, () => {
49
+ // @ts-ignore
50
+ const newConnectionIds = new Set(
51
+ // @ts-ignore
52
+ Object.keys(this.proxy._connections || {}));
53
+ // Update only if there are changes
54
+ let connectionIdsChanged = false;
55
+ if (newConnectionIds.size !== prevConnectionIds.size) {
56
+ connectionIdsChanged = true;
57
+ }
58
+ if (!connectionIdsChanged) {
59
+ for (const id of newConnectionIds) {
60
+ if (!prevConnectionIds.has(id)) {
61
+ connectionIdsChanged = true;
62
+ break;
63
+ }
64
+ }
65
+ }
66
+ if (!connectionIdsChanged) {
67
+ for (const id of prevConnectionIds) {
68
+ if (!newConnectionIds.has(id)) {
69
+ connectionIdsChanged = true;
70
+ break;
71
+ }
72
+ }
73
+ }
74
+ if (connectionIdsChanged) {
75
+ this.connections.connectionIds = newConnectionIds;
76
+ prevConnectionIds = new Set(newConnectionIds);
77
+ }
78
+ // @ts-ignore
79
+ const newClientIds = new Set(
80
+ // @ts-ignore
81
+ Object.values(this.proxy._connections || {}));
82
+ // Update only if there are changes
83
+ let clientIdsChanged = false;
84
+ if (newClientIds.size !== prevClientIds.size) {
85
+ clientIdsChanged = true;
86
+ }
87
+ if (!clientIdsChanged) {
88
+ for (const id of newClientIds) {
89
+ if (!prevClientIds.has(id)) {
90
+ clientIdsChanged = true;
91
+ break;
92
+ }
93
+ }
94
+ }
95
+ if (!clientIdsChanged) {
96
+ for (const id of prevClientIds) {
97
+ if (!newClientIds.has(id)) {
98
+ clientIdsChanged = true;
99
+ break;
100
+ }
101
+ }
102
+ }
103
+ if (clientIdsChanged) {
104
+ this.connections.clientIds = newClientIds;
105
+ prevClientIds = new Set(newClientIds);
106
+ }
107
+ });
108
+ // Add client to _connections map
109
+ await client.transact([this], ([state]) => {
110
+ state._connections[client.connectionId] = client.id;
111
+ });
112
+ }
47
113
  async onBeforeLeave(client) { }
48
- async onLeave(client) { }
114
+ async onLeave(client) {
115
+ this._unsubscribeConnectionsHandler();
116
+ }
49
117
  }
@@ -36,23 +36,17 @@ declare class KokimokiStore<T extends object> {
36
36
  readonly defaultValue: T;
37
37
  readonly mode: RoomSubscriptionMode;
38
38
  readonly doc: Y.Doc;
39
- readonly proxy: T & {
40
- _km_connections?: {
41
- [connectionId: string]: string;
42
- };
43
- };
39
+ readonly proxy: T;
44
40
  readonly docRoot: Y.Map<unknown>;
45
41
  readonly connections: {
42
+ connectionIds: Set<string>;
46
43
  clientIds: Set<string>;
47
44
  };
45
+ private _unsubscribeConnectionsHandler;
48
46
  constructor(roomName: string, defaultValue: T, mode?: RoomSubscriptionMode);
49
- get(): Snapshot<T & {
50
- _km_connections?: {
51
- [connectionId: string]: string;
52
- } | undefined;
53
- }>;
47
+ get(): Snapshot<T>;
54
48
  subscribe(set: (value: Snapshot<T>) => void): () => void;
55
- onJoin(client: KokimokiClient): Promise<void>;
49
+ onJoin(client: KokimokiClient<any>): Promise<void>;
56
50
  onBeforeLeave(client: KokimokiClient): Promise<void>;
57
51
  onLeave(client: KokimokiClient): Promise<void>;
58
52
  }
@@ -486,7 +486,7 @@ function eventTargetAgnosticAddListener(emitter, name, listener, flags) {
486
486
  var eventsExports = events.exports;
487
487
  var EventEmitter$1 = /*@__PURE__*/getDefaultExportFromCjs(eventsExports);
488
488
 
489
- const KOKIMOKI_APP_VERSION = "1.15.2";
489
+ const KOKIMOKI_APP_VERSION = "1.16.1";
490
490
 
491
491
  /**
492
492
  * Utility module to work with key-value stores.
@@ -12005,10 +12005,15 @@ class KokimokiStore {
12005
12005
  proxy;
12006
12006
  docRoot;
12007
12007
  connections;
12008
+ _unsubscribeConnectionsHandler = () => { };
12008
12009
  constructor(roomName, defaultValue, mode = RoomSubscriptionMode.ReadWrite) {
12009
12010
  this.roomName = roomName;
12010
12011
  this.defaultValue = defaultValue;
12011
12012
  this.mode = mode;
12013
+ // "_connections" is a reserved key for tracking connections
12014
+ if ("_connections" in defaultValue) {
12015
+ throw new Error(`"_connections" is a reserved key in KokimokiStore`);
12016
+ }
12012
12017
  // Construct Y doc
12013
12018
  this.doc = new Doc();
12014
12019
  this.docRoot = this.doc.getMap("root");
@@ -12017,16 +12022,9 @@ class KokimokiStore {
12017
12022
  // @ts-ignore
12018
12023
  bind$1(this.proxy, this.docRoot);
12019
12024
  // Construct connections proxy
12020
- this.connections = proxy({ clientIds: new Set() });
12021
- // Update connections.clientIds whenever _km_connections changes
12022
- subscribe(this.proxy, () => {
12023
- const kmConnections = this.proxy._km_connections;
12024
- if (kmConnections) {
12025
- this.connections.clientIds = new Set(Object.values(kmConnections));
12026
- }
12027
- else {
12028
- this.connections.clientIds = new Set();
12029
- }
12025
+ this.connections = proxy({
12026
+ connectionIds: new Set(),
12027
+ clientIds: new Set(),
12030
12028
  });
12031
12029
  }
12032
12030
  get() {
@@ -12038,9 +12036,79 @@ class KokimokiStore {
12038
12036
  set(this.get());
12039
12037
  return () => this.doc.off("update", handler);
12040
12038
  }
12041
- async onJoin(client) { }
12039
+ async onJoin(client) {
12040
+ // Update connections whenever _connections changes
12041
+ let prevConnectionIds = new Set();
12042
+ let prevClientIds = new Set();
12043
+ this._unsubscribeConnectionsHandler = subscribe(this.proxy, () => {
12044
+ // @ts-ignore
12045
+ const newConnectionIds = new Set(
12046
+ // @ts-ignore
12047
+ Object.keys(this.proxy._connections || {}));
12048
+ // Update only if there are changes
12049
+ let connectionIdsChanged = false;
12050
+ if (newConnectionIds.size !== prevConnectionIds.size) {
12051
+ connectionIdsChanged = true;
12052
+ }
12053
+ if (!connectionIdsChanged) {
12054
+ for (const id of newConnectionIds) {
12055
+ if (!prevConnectionIds.has(id)) {
12056
+ connectionIdsChanged = true;
12057
+ break;
12058
+ }
12059
+ }
12060
+ }
12061
+ if (!connectionIdsChanged) {
12062
+ for (const id of prevConnectionIds) {
12063
+ if (!newConnectionIds.has(id)) {
12064
+ connectionIdsChanged = true;
12065
+ break;
12066
+ }
12067
+ }
12068
+ }
12069
+ if (connectionIdsChanged) {
12070
+ this.connections.connectionIds = newConnectionIds;
12071
+ prevConnectionIds = new Set(newConnectionIds);
12072
+ }
12073
+ // @ts-ignore
12074
+ const newClientIds = new Set(
12075
+ // @ts-ignore
12076
+ Object.values(this.proxy._connections || {}));
12077
+ // Update only if there are changes
12078
+ let clientIdsChanged = false;
12079
+ if (newClientIds.size !== prevClientIds.size) {
12080
+ clientIdsChanged = true;
12081
+ }
12082
+ if (!clientIdsChanged) {
12083
+ for (const id of newClientIds) {
12084
+ if (!prevClientIds.has(id)) {
12085
+ clientIdsChanged = true;
12086
+ break;
12087
+ }
12088
+ }
12089
+ }
12090
+ if (!clientIdsChanged) {
12091
+ for (const id of prevClientIds) {
12092
+ if (!newClientIds.has(id)) {
12093
+ clientIdsChanged = true;
12094
+ break;
12095
+ }
12096
+ }
12097
+ }
12098
+ if (clientIdsChanged) {
12099
+ this.connections.clientIds = newClientIds;
12100
+ prevClientIds = new Set(newClientIds);
12101
+ }
12102
+ });
12103
+ // Add client to _connections map
12104
+ await client.transact([this], ([state]) => {
12105
+ state._connections[client.connectionId] = client.id;
12106
+ });
12107
+ }
12042
12108
  async onBeforeLeave(client) { }
12043
- async onLeave(client) { }
12109
+ async onLeave(client) {
12110
+ this._unsubscribeConnectionsHandler();
12111
+ }
12044
12112
  }
12045
12113
 
12046
12114
  var WsMessageType;
@@ -12173,6 +12241,9 @@ class RoomSubscription {
12173
12241
  // Set defaults if doc is empty after sync (only possible in ReadWrite mode)
12174
12242
  if (this.store.mode === RoomSubscriptionMode.ReadWrite) {
12175
12243
  await this.kmClient.transact([this.store], ([state]) => {
12244
+ if (!("_connections" in state)) {
12245
+ state._connections = {};
12246
+ }
12176
12247
  for (const key in this.store.defaultValue) {
12177
12248
  if (!state.hasOwnProperty(key)) {
12178
12249
  state[key] = this.store.defaultValue[key];
@@ -15242,7 +15313,7 @@ class KokimokiClient extends EventEmitter$1 {
15242
15313
  this._subscriptionsByHash.set(res.roomHash, subscription);
15243
15314
  await subscription.applyInitialResponse(res.roomHash, res.initialUpdate);
15244
15315
  // Trigger onJoin event
15245
- store.onJoin(this);
15316
+ await store.onJoin(this);
15246
15317
  }
15247
15318
  }
15248
15319
  async leave(store) {