@kokimoki/app 1.0.10 → 1.1.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.
@@ -634,7 +634,7 @@ function eventTargetAgnosticAddListener(emitter, name, listener, flags) {
634
634
  var eventsExports = events.exports;
635
635
  var EventEmitter$1 = /*@__PURE__*/getDefaultExportFromCjs(eventsExports);
636
636
 
637
- const KOKIMOKI_APP_VERSION = "1.0.10";
637
+ const KOKIMOKI_APP_VERSION = "1.1.0";
638
638
 
639
639
  /**
640
640
  * Utility module to work with key-value stores.
@@ -11508,6 +11508,8 @@ var WsMessageType;
11508
11508
  WsMessageType[WsMessageType["Error"] = 5] = "Error";
11509
11509
  WsMessageType[WsMessageType["Ping"] = 6] = "Ping";
11510
11510
  WsMessageType[WsMessageType["Pong"] = 7] = "Pong";
11511
+ WsMessageType[WsMessageType["UnsubscribeReq"] = 8] = "UnsubscribeReq";
11512
+ WsMessageType[WsMessageType["UnsubscribeRes"] = 9] = "UnsubscribeRes";
11511
11513
  })(WsMessageType || (WsMessageType = {}));
11512
11514
 
11513
11515
  class WsMessageWriter {
@@ -11655,6 +11657,7 @@ class KokimokiClient extends EventEmitter$1 {
11655
11657
  _wsUrl;
11656
11658
  _apiUrl;
11657
11659
  _id;
11660
+ _connectionId;
11658
11661
  _token;
11659
11662
  _apiHeaders;
11660
11663
  _serverTimeOffset = 0;
@@ -11663,10 +11666,12 @@ class KokimokiClient extends EventEmitter$1 {
11663
11666
  _subscriptionsByName = new Map();
11664
11667
  _subscriptionsByHash = new Map();
11665
11668
  _subscribeReqPromises = new Map();
11669
+ _unsubscribeReqPromises = new Map();
11666
11670
  _transactionPromises = new Map();
11667
11671
  _connected = false;
11668
11672
  _connectPromise;
11669
11673
  _messageId = 0;
11674
+ _autoReconnect = true;
11670
11675
  _reconnectTimeout = 0;
11671
11676
  _pingInterval;
11672
11677
  constructor(host, appId, code = "") {
@@ -11694,6 +11699,12 @@ class KokimokiClient extends EventEmitter$1 {
11694
11699
  }
11695
11700
  return this._id;
11696
11701
  }
11702
+ get connectionId() {
11703
+ if (!this._connectionId) {
11704
+ throw new Error("Client not connected");
11705
+ }
11706
+ return this._connectionId;
11707
+ }
11697
11708
  get token() {
11698
11709
  if (!this._token) {
11699
11710
  throw new Error("Client not connected");
@@ -11739,9 +11750,10 @@ class KokimokiClient extends EventEmitter$1 {
11739
11750
  window.__KOKIMOKI_WS__ = {};
11740
11751
  }
11741
11752
  if (this.appId in window.__KOKIMOKI_WS__) {
11753
+ console.log(`[Kokimoki] Closing previous connection for ${this.appId}`);
11742
11754
  window.__KOKIMOKI_WS__[this.appId].close();
11743
11755
  }
11744
- window.__KOKIMOKI_WS__[this.appId] = this._ws;
11756
+ window.__KOKIMOKI_WS__[this.appId] = this;
11745
11757
  }
11746
11758
  // Wait for connection
11747
11759
  this._connectPromise = new Promise((onInit) => {
@@ -11763,6 +11775,9 @@ class KokimokiClient extends EventEmitter$1 {
11763
11775
  this._subscribeReqPromises.clear();
11764
11776
  this._transactionPromises.clear();
11765
11777
  // Attempt to reconnect
11778
+ if (!this._autoReconnect) {
11779
+ return;
11780
+ }
11766
11781
  console.log(`[Kokimoki] Connection lost, attempting to reconnect in ${this._reconnectTimeout} seconds...`);
11767
11782
  setTimeout(async () => await this.connect(), this._reconnectTimeout * 1000);
11768
11783
  this._reconnectTimeout = Math.min(3, this._reconnectTimeout + 1);
@@ -11811,6 +11826,7 @@ class KokimokiClient extends EventEmitter$1 {
11811
11826
  handleInitMessage(message) {
11812
11827
  localStorage.setItem("KM_TOKEN", message.clientToken);
11813
11828
  this._id = message.clientId;
11829
+ this._connectionId = message.id;
11814
11830
  this._token = message.appToken;
11815
11831
  this._clientContext = message.clientContext;
11816
11832
  // Set up the auth headers
@@ -11826,6 +11842,9 @@ class KokimokiClient extends EventEmitter$1 {
11826
11842
  case WsMessageType.SubscribeRes:
11827
11843
  this.handleSubscribeResMessage(reader);
11828
11844
  break;
11845
+ case WsMessageType.UnsubscribeRes:
11846
+ this.handleUnsubscribeResMessage(reader);
11847
+ break;
11829
11848
  case WsMessageType.RoomUpdate:
11830
11849
  this.handleRoomUpdateMessage(reader);
11831
11850
  break;
@@ -11872,6 +11891,14 @@ class KokimokiClient extends EventEmitter$1 {
11872
11891
  }
11873
11892
  }
11874
11893
  }
11894
+ handleUnsubscribeResMessage(msg) {
11895
+ const reqId = msg.readInt32();
11896
+ const promise = this._unsubscribeReqPromises.get(reqId);
11897
+ if (promise) {
11898
+ this._unsubscribeReqPromises.delete(reqId);
11899
+ promise.resolve();
11900
+ }
11901
+ }
11875
11902
  handleRoomUpdateMessage(msg) {
11876
11903
  const appliedId = msg.readInt32();
11877
11904
  const roomHash = msg.readUint32();
@@ -11980,7 +12007,7 @@ class KokimokiClient extends EventEmitter$1 {
11980
12007
  // @ts-ignore
11981
12008
  window.dispatchEvent(new CustomEvent("km:scriptingContextExposed"));
11982
12009
  }
11983
- async sendSubscriptionReq(roomName, mode) {
12010
+ async sendSubscribeReq(roomName, mode) {
11984
12011
  // Set up sync resolver
11985
12012
  const reqId = ++this._messageId;
11986
12013
  return await new Promise((resolve, reject) => {
@@ -11999,6 +12026,21 @@ class KokimokiClient extends EventEmitter$1 {
11999
12026
  this.ws.send(msg.getBuffer());
12000
12027
  });
12001
12028
  }
12029
+ async sendUnsubscribeReq(roomHash) {
12030
+ const reqId = ++this._messageId;
12031
+ return await new Promise((resolve, reject) => {
12032
+ this._unsubscribeReqPromises.set(reqId, {
12033
+ resolve,
12034
+ reject,
12035
+ });
12036
+ // Send unsubscribe request
12037
+ const msg = new WsMessageWriter();
12038
+ msg.writeInt32(WsMessageType.UnsubscribeReq);
12039
+ msg.writeInt32(reqId);
12040
+ msg.writeUint32(roomHash);
12041
+ this.ws.send(msg.getBuffer());
12042
+ });
12043
+ }
12002
12044
  async join(store) {
12003
12045
  let subscription = this._subscriptionsByName.get(store.roomName);
12004
12046
  if (!subscription) {
@@ -12007,9 +12049,22 @@ class KokimokiClient extends EventEmitter$1 {
12007
12049
  }
12008
12050
  // Send subscription request if connected to server
12009
12051
  if (!subscription.joined) {
12010
- const res = await this.sendSubscriptionReq(store.roomName, store.mode);
12052
+ const res = await this.sendSubscribeReq(store.roomName, store.mode);
12011
12053
  this._subscriptionsByHash.set(res.roomHash, subscription);
12012
12054
  await subscription.applyInitialResponse(res.roomHash, res.initialUpdate);
12055
+ // Trigger onJoin event
12056
+ store.onJoin(this);
12057
+ }
12058
+ }
12059
+ async leave(store) {
12060
+ const subscription = this._subscriptionsByName.get(store.roomName);
12061
+ if (subscription) {
12062
+ await store.onBeforeLeave(this);
12063
+ await this.sendUnsubscribeReq(subscription.roomHash);
12064
+ this._subscriptionsByName.delete(store.roomName);
12065
+ this._subscriptionsByHash.delete(subscription.roomHash);
12066
+ // Trigger onLeave event
12067
+ store.onLeave(this);
12013
12068
  }
12014
12069
  }
12015
12070
  async transact(handler) {
@@ -12072,6 +12127,15 @@ class KokimokiClient extends EventEmitter$1 {
12072
12127
  }
12073
12128
  } */
12074
12129
  }
12130
+ async close() {
12131
+ this._autoReconnect = false;
12132
+ if (this._ws) {
12133
+ this._ws.close();
12134
+ }
12135
+ if (this._pingInterval) {
12136
+ clearInterval(this._pingInterval);
12137
+ }
12138
+ }
12075
12139
  }
12076
12140
 
12077
12141
  var KokimokiSchema;
@@ -12222,6 +12286,20 @@ var KokimokiSchema;
12222
12286
  return new AnyOf(schemas, defaultValue);
12223
12287
  }
12224
12288
  KokimokiSchema.anyOf = anyOf;
12289
+ class Optional extends Generic {
12290
+ schema;
12291
+ defaultValue;
12292
+ constructor(schema, defaultValue = undefined) {
12293
+ super();
12294
+ this.schema = schema;
12295
+ this.defaultValue = defaultValue;
12296
+ }
12297
+ }
12298
+ KokimokiSchema.Optional = Optional;
12299
+ function optional(schema, defaultValue = undefined) {
12300
+ return new Optional(schema, defaultValue);
12301
+ }
12302
+ KokimokiSchema.optional = optional;
12225
12303
  })(KokimokiSchema || (KokimokiSchema = {}));
12226
12304
 
12227
12305
  function parsePath(text) {
@@ -12341,8 +12419,8 @@ class KokimokiStore {
12341
12419
  this.mode = mode;
12342
12420
  // Construct Y doc
12343
12421
  this.doc = new Doc();
12344
- // Construct proxy object
12345
12422
  this.docRoot = this.doc.getMap("root");
12423
+ // Construct proxy object
12346
12424
  this.proxy = proxy();
12347
12425
  // @ts-ignore
12348
12426
  f(this.proxy, this.docRoot);
@@ -12370,11 +12448,17 @@ class KokimokiStore {
12370
12448
  // Set default value
12371
12449
  this.defaultValue = schema.defaultValue;
12372
12450
  }
12373
- subscribe(callback) {
12374
- const handler = () => callback(this.proxy);
12451
+ subscribe(set) {
12452
+ // @ts-ignore
12453
+ const handler = () => set(this.proxy);
12375
12454
  this.doc.on("update", handler);
12455
+ // @ts-ignore
12456
+ set(this.proxy);
12376
12457
  return () => this.doc.off("update", handler);
12377
12458
  }
12459
+ async onJoin(client) { }
12460
+ async onBeforeLeave(client) { }
12461
+ async onLeave(client) { }
12378
12462
  }
12379
12463
 
12380
12464
  class KokimokiQueue extends KokimokiStore {
@@ -12405,5 +12489,67 @@ class KokimokiQueue extends KokimokiStore {
12405
12489
  }
12406
12490
  }
12407
12491
 
12408
- export { BooleanField, ConstField, EnumField, Field, FloatField, Form, FormArray, FormGroup, ImageField, IntegerField, KokimokiClient, KokimokiQueue, KokimokiSchema, KokimokiStore, RoomSubscription, RoomSubscriptionMode, TextField };
12492
+ class KokimokiAwareness
12493
+ // <Data extends S.Generic<unknown>>
12494
+ extends KokimokiStore {
12495
+ _pingInterval = null;
12496
+ _kmClients = new Set();
12497
+ // private _data: Data["defaultValue"];
12498
+ constructor(roomName,
12499
+ // public readonly dataSchema: Data,
12500
+ // initialData: Data["defaultValue"],
12501
+ mode = RoomSubscriptionMode.ReadWrite, pingTimeout = 2500) {
12502
+ super(`/a/${roomName}`, KokimokiSchema.dict(KokimokiSchema.struct({
12503
+ clientId: KokimokiSchema.string(),
12504
+ lastPing: KokimokiSchema.number(),
12505
+ // data: dataSchema,
12506
+ })), mode);
12507
+ // this._data = initialData;
12508
+ this._pingInterval = setInterval(async () => {
12509
+ const kmClients = Array.from(this._kmClients);
12510
+ await Promise.all(kmClients.map(async (client) => {
12511
+ try {
12512
+ await client.transact((t) => {
12513
+ const timestamp = client.serverTimestamp();
12514
+ // Update self last ping
12515
+ t.set(this.root[client.connectionId].lastPing, timestamp);
12516
+ /* if (!this.proxy[client.connectionId]) {
12517
+ t.set(this.root[client.connectionId].data, this._data);
12518
+ } else {
12519
+ this._data = this.proxy[client.connectionId].data;
12520
+ } */
12521
+ // Delete clients that haven't pinged in a while
12522
+ for (const connectionId in this.proxy) {
12523
+ const { lastPing } = this.proxy[connectionId];
12524
+ if (timestamp - lastPing > pingTimeout * 2) {
12525
+ t.delete(this.root[connectionId]);
12526
+ }
12527
+ }
12528
+ });
12529
+ }
12530
+ catch (e) { }
12531
+ }));
12532
+ }, pingTimeout);
12533
+ }
12534
+ async onJoin(client) {
12535
+ this._kmClients.add(client);
12536
+ await client.transact((t) => {
12537
+ t.set(this.root[client.connectionId], {
12538
+ clientId: client.id,
12539
+ lastPing: client.serverTimestamp(),
12540
+ // data: this._data,
12541
+ });
12542
+ });
12543
+ }
12544
+ async onBeforeLeave(client) {
12545
+ await client.transact((t) => {
12546
+ t.delete(this.root[client.connectionId]);
12547
+ });
12548
+ }
12549
+ async onLeave(client) {
12550
+ this._kmClients.delete(client);
12551
+ }
12552
+ }
12553
+
12554
+ export { BooleanField, ConstField, EnumField, Field, FloatField, Form, FormArray, FormGroup, ImageField, IntegerField, KokimokiAwareness, KokimokiClient, KokimokiQueue, KokimokiSchema, KokimokiStore, RoomSubscription, RoomSubscriptionMode, TextField };
12409
12555
  //# sourceMappingURL=kokimoki.min.js.map