@kokimoki/app 1.15.1 → 1.15.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.
@@ -1,394 +0,0 @@
1
- import EventEmitter from "events";
2
- import { KOKIMOKI_APP_VERSION } from "./version";
3
- import * as Y from "yjs";
4
- import { KokimokiTransaction } from "./kokimoki-transaction";
5
- import { WsMessageType } from "./ws-message-type";
6
- import { WsMessageWriter } from "./ws-message-writer";
7
- import { WsMessageReader } from "./ws-message-reader";
8
- import { RoomSubscriptionMode } from "./room-subscription-mode";
9
- export class KokimokiClientRefactored extends EventEmitter {
10
- host;
11
- appId;
12
- code;
13
- _wsUrl;
14
- _apiUrl;
15
- _id;
16
- _token;
17
- _apiHeaders;
18
- _serverTimeOffset = 0;
19
- _clientContext;
20
- _ws;
21
- _subscriptions = new Map();
22
- _stores = new Map();
23
- _reqPromises = new Map();
24
- _transactionResolves = new Map();
25
- _roomHashes = new Map();
26
- _roomHashToDoc = new Map();
27
- _connected = false;
28
- _connectPromise;
29
- _messageId = 0;
30
- constructor(host, appId, code = "") {
31
- super();
32
- this.host = host;
33
- this.appId = appId;
34
- this.code = code;
35
- // Set up the URLs
36
- const secure = this.host.indexOf(":") === -1;
37
- this._wsUrl = `ws${secure ? "s" : ""}://${this.host}`;
38
- this._apiUrl = `http${secure ? "s" : ""}://${this.host}`;
39
- }
40
- get id() {
41
- if (!this._id) {
42
- throw new Error("Client not connected");
43
- }
44
- return this._id;
45
- }
46
- get token() {
47
- if (!this._token) {
48
- throw new Error("Client not connected");
49
- }
50
- return this._token;
51
- }
52
- get apiUrl() {
53
- if (!this._apiUrl) {
54
- throw new Error("Client not connected");
55
- }
56
- return this._apiUrl;
57
- }
58
- get apiHeaders() {
59
- if (!this._apiHeaders) {
60
- throw new Error("Client not connected");
61
- }
62
- return this._apiHeaders;
63
- }
64
- get clientContext() {
65
- if (this._clientContext === undefined) {
66
- throw new Error("Client not connected");
67
- }
68
- return this._clientContext;
69
- }
70
- get connected() {
71
- return this._connected;
72
- }
73
- get ws() {
74
- if (!this._ws) {
75
- throw new Error("Not connected");
76
- }
77
- return this._ws;
78
- }
79
- async connect() {
80
- if (this._connectPromise) {
81
- return await this._connectPromise;
82
- }
83
- this._ws = new WebSocket(`${this._wsUrl}/apps/${this.appId}?clientVersion=${KOKIMOKI_APP_VERSION}`);
84
- this._ws.binaryType = "arraybuffer";
85
- this._connectPromise = new Promise((onInit) => {
86
- // Fetch the auth token
87
- let clientToken = localStorage.getItem("KM_TOKEN");
88
- // Send the app token on first connect
89
- this.ws.onopen = () => {
90
- this.ws.send(JSON.stringify({ type: "auth", code: this.code, token: clientToken }));
91
- };
92
- this.ws.onclose = () => {
93
- this._connected = false;
94
- this._connectPromise = undefined;
95
- this._ws = undefined;
96
- // Clean up
97
- this._reqPromises.clear();
98
- this._transactionResolves.clear();
99
- this._roomHashes.clear();
100
- this._roomHashToDoc.clear();
101
- this._subscriptions.clear();
102
- // Emit disconnected event
103
- this.emit("disconnected");
104
- // Attempt to reconnect
105
- // setTimeout(async () => await this.connect(), 2000);
106
- };
107
- this.ws.onmessage = (e) => {
108
- console.log(`Received WS message: ${e.data}`);
109
- // Handle JSON messages
110
- if (typeof e.data === "string") {
111
- const message = JSON.parse(e.data);
112
- switch (message.type) {
113
- case "serverTime": {
114
- this._serverTimeOffset = Date.now() - message.serverTime;
115
- console.log(`Server time offset: ${this._serverTimeOffset}`);
116
- break;
117
- }
118
- case "init":
119
- this.handleInitMessage(message);
120
- onInit();
121
- break;
122
- }
123
- return;
124
- }
125
- // Handle binary messages
126
- this.handleBinaryMessage(e.data);
127
- };
128
- });
129
- await this._connectPromise;
130
- }
131
- handleInitMessage(message) {
132
- localStorage.setItem("KM_TOKEN", message.clientToken);
133
- this._id = message.clientId;
134
- this._token = message.appToken;
135
- this._clientContext = message.clientContext;
136
- // Set up the auth headers
137
- this._apiHeaders = new Headers({
138
- Authorization: `Bearer ${this.token}`,
139
- "Content-Type": "application/json",
140
- });
141
- this._connected = true;
142
- this.emit("connected");
143
- }
144
- handleBinaryMessage(data) {
145
- const reader = new WsMessageReader(data);
146
- const type = reader.readInt32();
147
- switch (type) {
148
- case WsMessageType.SubscribeRes:
149
- this.handleSubscribeResMessage(reader);
150
- break;
151
- case WsMessageType.RoomUpdate:
152
- this.handleRoomUpdateMessage(reader);
153
- break;
154
- case WsMessageType.Error:
155
- this.handleErrorMessage(reader);
156
- break;
157
- }
158
- }
159
- handleErrorMessage(msg) {
160
- const reqId = msg.readInt32();
161
- const error = msg.readString();
162
- const promise = this._reqPromises.get(reqId);
163
- if (promise) {
164
- this._reqPromises.delete(reqId);
165
- promise.reject(error);
166
- }
167
- else {
168
- console.warn(`Received error for unknown request ${reqId}: ${error}`);
169
- }
170
- }
171
- handleSubscribeResMessage(msg) {
172
- const reqId = msg.readInt32();
173
- const roomHash = msg.readUint32();
174
- const promise = this._reqPromises.get(reqId);
175
- if (promise) {
176
- this._reqPromises.delete(reqId);
177
- // In Write mode, no initial state is sent
178
- if (!msg.end) {
179
- promise.resolve(roomHash, msg.readUint8Array());
180
- }
181
- else {
182
- promise.resolve(roomHash);
183
- }
184
- }
185
- }
186
- handleRoomUpdateMessage(msg) {
187
- const appliedId = msg.readInt32();
188
- const roomHash = msg.readUint32();
189
- // Apply update if not in Write mode
190
- if (!msg.end) {
191
- const doc = this._roomHashToDoc.get(roomHash);
192
- if (doc) {
193
- Y.applyUpdate(doc, msg.readUint8Array(), this);
194
- }
195
- else {
196
- console.warn(`Received update for unknown room ${roomHash}`);
197
- }
198
- }
199
- // Check transaction resolves
200
- for (const [transactionId, resolve,] of this._transactionResolves.entries()) {
201
- if (appliedId >= transactionId) {
202
- this._transactionResolves.delete(transactionId);
203
- resolve();
204
- }
205
- }
206
- }
207
- serverTimestamp() {
208
- return Date.now() - this._serverTimeOffset;
209
- }
210
- // Send Y update to room
211
- async patchRoomState(room, update) {
212
- const res = await fetch(`${this._apiUrl}/rooms/${room}`, {
213
- method: "PATCH",
214
- headers: this.apiHeaders,
215
- body: update,
216
- });
217
- return await res.json();
218
- }
219
- // Storage
220
- async createUpload(name, blob, tags) {
221
- const res = await fetch(`${this._apiUrl}/uploads`, {
222
- method: "POST",
223
- headers: this.apiHeaders,
224
- body: JSON.stringify({
225
- name,
226
- size: blob.size,
227
- mimeType: blob.type,
228
- tags,
229
- }),
230
- });
231
- return await res.json();
232
- }
233
- async uploadChunks(blob, chunkSize, signedUrls) {
234
- return await Promise.all(signedUrls.map(async (url, index) => {
235
- const start = index * chunkSize;
236
- const end = Math.min(start + chunkSize, blob.size);
237
- const chunk = blob.slice(start, end);
238
- const res = await fetch(url, { method: "PUT", body: chunk });
239
- return JSON.parse(res.headers.get("ETag") || '""');
240
- }));
241
- }
242
- async completeUpload(id, etags) {
243
- const res = await fetch(`${this._apiUrl}/uploads/${id}`, {
244
- method: "PUT",
245
- headers: this.apiHeaders,
246
- body: JSON.stringify({ etags }),
247
- });
248
- return await res.json();
249
- }
250
- async upload(name, blob, tags = []) {
251
- const { id, chunkSize, urls } = await this.createUpload(name, blob, tags);
252
- const etags = await this.uploadChunks(blob, chunkSize, urls);
253
- return await this.completeUpload(id, etags);
254
- }
255
- async updateUpload(id, update) {
256
- const res = await fetch(`${this._apiUrl}/uploads/${id}`, {
257
- method: "PUT",
258
- headers: this.apiHeaders,
259
- body: JSON.stringify(update),
260
- });
261
- return await res.json();
262
- }
263
- async listUploads(filter = {}, skip = 0, limit = 100) {
264
- const url = new URL("/uploads", this._apiUrl);
265
- url.searchParams.set("skip", skip.toString());
266
- url.searchParams.set("limit", limit.toString());
267
- if (filter.clientId) {
268
- url.searchParams.set("clientId", filter.clientId);
269
- }
270
- if (filter.mimeTypes) {
271
- url.searchParams.set("mimeTypes", filter.mimeTypes.join());
272
- }
273
- if (filter.tags) {
274
- url.searchParams.set("tags", filter.tags.join());
275
- }
276
- const res = await fetch(url.href, {
277
- headers: this.apiHeaders,
278
- });
279
- return await res.json();
280
- }
281
- async deleteUpload(id) {
282
- const res = await fetch(`${this._apiUrl}/uploads/${id}`, {
283
- method: "DELETE",
284
- headers: this.apiHeaders,
285
- });
286
- return await res.json();
287
- }
288
- async exposeScriptingContext(context) {
289
- // @ts-ignore
290
- window.KM_SCRIPTING_CONTEXT = context;
291
- // @ts-ignore
292
- window.dispatchEvent(new CustomEvent("km:scriptingContextExposed"));
293
- }
294
- async sendSubscriptionReq(roomName, mode) {
295
- // Set up sync resolver
296
- const reqId = ++this._messageId;
297
- return await new Promise((resolve, reject) => {
298
- this._reqPromises.set(reqId, {
299
- resolve: (roomHash, initialUpdate) => {
300
- resolve({ roomHash, initialUpdate });
301
- },
302
- reject,
303
- });
304
- // Send subscription request
305
- const msg = new WsMessageWriter();
306
- msg.writeInt32(WsMessageType.SubscribeReq);
307
- msg.writeInt32(reqId);
308
- msg.writeString(roomName);
309
- msg.writeChar(mode);
310
- this.ws.send(msg.getBuffer());
311
- });
312
- }
313
- async join(store, mode = RoomSubscriptionMode.ReadWrite) {
314
- if (this._subscriptions.has(store.roomName)) {
315
- throw new Error(`Already joined room "${store.roomName}"`);
316
- }
317
- this._subscriptions.set(store.roomName, mode);
318
- // Send req
319
- try {
320
- const res = await this.sendSubscriptionReq(store.roomName, mode);
321
- // Set room hash
322
- this._roomHashes.set(store.roomName, res.roomHash);
323
- this._roomHashToDoc.set(res.roomHash, store.doc);
324
- this._stores.set(res.roomHash, store);
325
- // Apply initial state
326
- if (res.initialUpdate) {
327
- Y.applyUpdate(store.doc, res.initialUpdate, this);
328
- }
329
- // Set defaults if doc is empty after sync (only possible in ReadWrite mode)
330
- if (mode === RoomSubscriptionMode.ReadWrite) {
331
- await this.transact((t) => {
332
- for (const key in store.defaultValue) {
333
- // @ts-ignore
334
- if (!store.proxy.hasOwnProperty(key)) {
335
- t.set(store.root[key], store.defaultValue[key]);
336
- }
337
- }
338
- });
339
- }
340
- }
341
- catch (err) {
342
- this._subscriptions.delete(store.roomName);
343
- throw err;
344
- }
345
- }
346
- async transact(handler) {
347
- const transaction = new KokimokiTransaction(this);
348
- handler(transaction);
349
- const updates = await transaction.getUpdates();
350
- if (!updates.length) {
351
- return;
352
- }
353
- // Construct buffer
354
- const writer = new WsMessageWriter();
355
- // Write message type
356
- writer.writeInt32(WsMessageType.Transaction);
357
- // Update and write transaction ID
358
- const transactionId = ++this._messageId;
359
- writer.writeInt32(transactionId);
360
- // Write updates
361
- for (const { roomName, update } of updates) {
362
- const roomHash = this._roomHashes.get(roomName);
363
- if (!roomHash) {
364
- throw new Error(`Cannot send update to "${roomName}" because it hasn't been joined`);
365
- }
366
- writer.writeUint32(roomHash);
367
- writer.writeUint8Array(update);
368
- }
369
- const buffer = writer.getBuffer();
370
- // Wait for server to apply transaction
371
- await new Promise((resolve) => {
372
- this._transactionResolves.set(transactionId, resolve);
373
- // Send update to server
374
- try {
375
- this.ws.send(buffer);
376
- }
377
- catch (e) {
378
- // Not connected
379
- console.log("Failed to send update to server:", e);
380
- // TODO: merge updates or something
381
- throw e;
382
- }
383
- });
384
- /* // Reset doc in Write-only mode
385
- for (const { roomName, update } of updates) {
386
- const mode = this._subscriptions.get(roomName);
387
-
388
- if (mode === RoomSubscriptionMode.Write) {
389
- // @ts-ignore
390
- // this._stores.get(this._roomHashes.get(roomName)!)!.doc = new Y.Doc();
391
- }
392
- } */
393
- }
394
- }
@@ -1,84 +0,0 @@
1
- export declare namespace KokimokiSchema {
2
- abstract class Generic<T> {
3
- abstract get defaultValue(): T;
4
- abstract set defaultValue(value: T);
5
- }
6
- class Number extends Generic<number> {
7
- defaultValue: number;
8
- constructor(defaultValue?: number);
9
- }
10
- function number(defaultValue?: number): Number;
11
- class String extends Generic<string> {
12
- defaultValue: string;
13
- constructor(defaultValue?: string);
14
- }
15
- function string(defaultValue?: string): String;
16
- class Boolean extends Generic<boolean> {
17
- defaultValue: boolean;
18
- constructor(defaultValue?: boolean);
19
- }
20
- function boolean(defaultValue?: boolean): Boolean;
21
- class Struct<Data extends Record<string, Generic<unknown>>> extends Generic<{
22
- [key in keyof Data]: Data[key]["defaultValue"];
23
- }> {
24
- fields: Data;
25
- constructor(fields: Data);
26
- get defaultValue(): {
27
- [key in keyof Data]: Data[key]["defaultValue"];
28
- };
29
- set defaultValue(value: {
30
- [key in keyof Data]: Data[key]["defaultValue"];
31
- });
32
- }
33
- function struct<Data extends Record<string, Generic<unknown>>>(schema: Data): Struct<Data>;
34
- class Dict<T extends Generic<unknown>> {
35
- schema: T;
36
- defaultValue: {
37
- [key: string]: T["defaultValue"];
38
- };
39
- constructor(schema: T, defaultValue?: {
40
- [key: string]: T["defaultValue"];
41
- });
42
- }
43
- function dict<T extends Generic<unknown>>(schema: T, defaultValue?: {
44
- [key: string]: T["defaultValue"];
45
- }): Dict<T>;
46
- class List<T extends Generic<unknown>> extends Generic<T["defaultValue"][]> {
47
- schema: T;
48
- defaultValue: T["defaultValue"][];
49
- constructor(schema: T, defaultValue?: T["defaultValue"][]);
50
- }
51
- function list<T extends Generic<unknown>>(schema: T, defaultValue?: T["defaultValue"][]): List<T>;
52
- /**
53
- * Nullable
54
- */
55
- class Nullable<T extends Generic<unknown>> extends Generic<T["defaultValue"] | null> {
56
- schema: T;
57
- defaultValue: T["defaultValue"] | null;
58
- constructor(schema: T, defaultValue: T["defaultValue"] | null);
59
- }
60
- function nullable<T extends Generic<unknown>>(schema: T, defaultValue?: T["defaultValue"] | null): Nullable<T>;
61
- class StringEnum<T extends readonly string[]> extends Generic<string> {
62
- readonly options: T;
63
- defaultValue: T[number];
64
- constructor(options: T, defaultValue: T[number]);
65
- }
66
- function stringEnum<T extends readonly string[]>(options: T, defaultValue: T[number]): StringEnum<T>;
67
- class StringConst<T extends string> extends Generic<string> {
68
- readonly defaultValue: T;
69
- constructor(defaultValue: T);
70
- }
71
- function stringConst<T extends string>(defaultValue: T): StringConst<T>;
72
- class AnyOf<T extends readonly Generic<unknown>[]> extends Generic<T[number]["defaultValue"]> {
73
- readonly schemas: T;
74
- defaultValue: T[number]["defaultValue"];
75
- constructor(schemas: T, defaultValue: T[number]["defaultValue"]);
76
- }
77
- function anyOf<T extends readonly Generic<unknown>[]>(schemas: T, defaultValue: T[number]["defaultValue"]): AnyOf<T>;
78
- class Optional<T extends Generic<unknown>> extends Generic<T["defaultValue"] | undefined> {
79
- schema: T;
80
- defaultValue: T["defaultValue"] | undefined;
81
- constructor(schema: T, defaultValue?: T["defaultValue"] | undefined);
82
- }
83
- function optional<T extends Generic<unknown>>(schema: T, defaultValue?: T["defaultValue"] | undefined): Optional<T>;
84
- }
@@ -1,163 +0,0 @@
1
- export var KokimokiSchema;
2
- (function (KokimokiSchema) {
3
- class Generic {
4
- }
5
- KokimokiSchema.Generic = Generic;
6
- class Number extends Generic {
7
- defaultValue;
8
- constructor(defaultValue = 0) {
9
- super();
10
- this.defaultValue = defaultValue;
11
- }
12
- }
13
- KokimokiSchema.Number = Number;
14
- function number(defaultValue = 0) {
15
- return new Number(defaultValue);
16
- }
17
- KokimokiSchema.number = number;
18
- class String extends Generic {
19
- defaultValue;
20
- constructor(defaultValue = "") {
21
- super();
22
- this.defaultValue = defaultValue;
23
- }
24
- }
25
- KokimokiSchema.String = String;
26
- function string(defaultValue = "") {
27
- return new String(defaultValue);
28
- }
29
- KokimokiSchema.string = string;
30
- class Boolean extends Generic {
31
- defaultValue;
32
- constructor(defaultValue = false) {
33
- super();
34
- this.defaultValue = defaultValue;
35
- }
36
- }
37
- KokimokiSchema.Boolean = Boolean;
38
- function boolean(defaultValue = false) {
39
- return new Boolean(defaultValue);
40
- }
41
- KokimokiSchema.boolean = boolean;
42
- class Struct extends Generic {
43
- fields;
44
- constructor(fields) {
45
- super();
46
- this.fields = fields;
47
- }
48
- get defaultValue() {
49
- return Object.entries(this.fields).reduce((acc, [key, field]) => {
50
- acc[key] = field.defaultValue;
51
- return acc;
52
- }, {});
53
- }
54
- set defaultValue(value) {
55
- for (const [key, field] of Object.entries(this.fields)) {
56
- field.defaultValue = value[key];
57
- }
58
- }
59
- }
60
- KokimokiSchema.Struct = Struct;
61
- function struct(schema) {
62
- return new Struct(schema);
63
- }
64
- KokimokiSchema.struct = struct;
65
- class Dict {
66
- schema;
67
- defaultValue;
68
- constructor(schema, defaultValue = {}) {
69
- this.schema = schema;
70
- this.defaultValue = defaultValue;
71
- }
72
- }
73
- KokimokiSchema.Dict = Dict;
74
- function dict(schema, defaultValue = {}) {
75
- return new Dict(schema, defaultValue);
76
- }
77
- KokimokiSchema.dict = dict;
78
- class List extends Generic {
79
- schema;
80
- defaultValue;
81
- constructor(schema, defaultValue = []) {
82
- super();
83
- this.schema = schema;
84
- this.defaultValue = defaultValue;
85
- }
86
- }
87
- KokimokiSchema.List = List;
88
- function list(schema, defaultValue = []) {
89
- return new List(schema, defaultValue);
90
- }
91
- KokimokiSchema.list = list;
92
- /**
93
- * Nullable
94
- */
95
- class Nullable extends Generic {
96
- schema;
97
- defaultValue;
98
- constructor(schema, defaultValue) {
99
- super();
100
- this.schema = schema;
101
- this.defaultValue = defaultValue;
102
- }
103
- }
104
- KokimokiSchema.Nullable = Nullable;
105
- function nullable(schema, defaultValue = null) {
106
- return new Nullable(schema, defaultValue);
107
- }
108
- KokimokiSchema.nullable = nullable;
109
- class StringEnum extends Generic {
110
- options;
111
- defaultValue;
112
- constructor(options, defaultValue) {
113
- super();
114
- this.options = options;
115
- this.defaultValue = defaultValue;
116
- }
117
- }
118
- KokimokiSchema.StringEnum = StringEnum;
119
- function stringEnum(options, defaultValue) {
120
- return new StringEnum(options, defaultValue);
121
- }
122
- KokimokiSchema.stringEnum = stringEnum;
123
- class StringConst extends Generic {
124
- defaultValue;
125
- constructor(defaultValue) {
126
- super();
127
- this.defaultValue = defaultValue;
128
- }
129
- }
130
- KokimokiSchema.StringConst = StringConst;
131
- function stringConst(defaultValue) {
132
- return new StringConst(defaultValue);
133
- }
134
- KokimokiSchema.stringConst = stringConst;
135
- class AnyOf extends Generic {
136
- schemas;
137
- defaultValue;
138
- constructor(schemas, defaultValue) {
139
- super();
140
- this.schemas = schemas;
141
- this.defaultValue = defaultValue;
142
- }
143
- }
144
- KokimokiSchema.AnyOf = AnyOf;
145
- function anyOf(schemas, defaultValue) {
146
- return new AnyOf(schemas, defaultValue);
147
- }
148
- KokimokiSchema.anyOf = anyOf;
149
- class Optional extends Generic {
150
- schema;
151
- defaultValue;
152
- constructor(schema, defaultValue = undefined) {
153
- super();
154
- this.schema = schema;
155
- this.defaultValue = defaultValue;
156
- }
157
- }
158
- KokimokiSchema.Optional = Optional;
159
- function optional(schema, defaultValue = undefined) {
160
- return new Optional(schema, defaultValue);
161
- }
162
- KokimokiSchema.optional = optional;
163
- })(KokimokiSchema || (KokimokiSchema = {}));
@@ -1,8 +0,0 @@
1
- import { SyncedStore } from "./synced-store";
2
- export declare class MessageQueue<T> extends SyncedStore<{
3
- messages: T[];
4
- }> {
5
- name: string;
6
- constructor(name: string);
7
- push(message: T): Promise<Uint8Array>;
8
- }
@@ -1,19 +0,0 @@
1
- import EventEmitter from "events";
2
- import { SyncedStore } from "./synced-store";
3
- import Y from "yjs";
4
- export class MessageQueue extends SyncedStore {
5
- name;
6
- constructor(name) {
7
- super({ messages: [] });
8
- this.name = name;
9
- }
10
- async push(message) {
11
- // Construct Y update to push the message to the queue
12
- const id = crypto.randomUUID();
13
- const ydoc = new Y.Doc();
14
- const map = ydoc.getMap("messages");
15
- map.set(id, message);
16
- const update = Y.encodeStateAsUpdate(ydoc);
17
- return update;
18
- }
19
- }