simplex-chat 0.2.0 → 0.3.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.
package/dist/index.mjs DELETED
@@ -1,585 +0,0 @@
1
- class Sem {
2
- constructor(permits) {
3
- this.permits = permits;
4
- this.promises = [];
5
- }
6
- signal() {
7
- this.permits += 1;
8
- if (this.promises.length > 0)
9
- this.promises.pop()();
10
- }
11
- async wait() {
12
- if (this.permits === 0 || this.promises.length > 0) {
13
- await new Promise((r) => this.promises.unshift(r));
14
- }
15
- this.permits -= 1;
16
- }
17
- }
18
- const queueClosed = Symbol();
19
- class ABQueueError extends Error {
20
- }
21
- class ABQueue {
22
- constructor(maxSize) {
23
- this.maxSize = maxSize;
24
- this.queue = [];
25
- this.enqClosed = false;
26
- this.deqClosed = false;
27
- this.enq = new Sem(0);
28
- this.deq = new Sem(maxSize);
29
- }
30
- [Symbol.asyncIterator]() {
31
- return this;
32
- }
33
- enqueue(x) {
34
- return this._enqueue(x);
35
- }
36
- async _enqueue(x) {
37
- if (this.enqClosed)
38
- throw new ABQueueError("enqueue: queue closed");
39
- await this.deq.wait();
40
- this.queue.push(x);
41
- this.enq.signal();
42
- }
43
- async dequeue() {
44
- if (this.deqClosed)
45
- throw new ABQueueError("dequeue: queue closed");
46
- this.deq.signal();
47
- await this.enq.wait();
48
- const x = this.queue.shift();
49
- if (x === queueClosed) {
50
- this.deqClosed = true;
51
- throw new ABQueueError("dequeue: queue closed");
52
- }
53
- return x;
54
- }
55
- async close() {
56
- await this._enqueue(queueClosed);
57
- this.enqClosed = true;
58
- }
59
- async next() {
60
- if (this.deqClosed)
61
- return { done: true };
62
- try {
63
- return { value: await this.dequeue() };
64
- }
65
- catch (e) {
66
- if (e instanceof ABQueueError)
67
- return { done: true };
68
- throw e;
69
- }
70
- }
71
- }
72
-
73
- class TransportError extends Error {
74
- }
75
- class Transport {
76
- constructor(qSize) {
77
- this.queue = new ABQueue(qSize);
78
- }
79
- [Symbol.asyncIterator]() {
80
- return this;
81
- }
82
- async read() {
83
- return this.queue.dequeue();
84
- }
85
- async next() {
86
- return this.queue.next();
87
- }
88
- }
89
- class WSTransport extends Transport {
90
- constructor(sock, timeout, qSize) {
91
- super(qSize);
92
- this.sock = sock;
93
- this.timeout = timeout;
94
- }
95
- static connect(url, timeout, qSize) {
96
- const sock = new WebSocket(url);
97
- const t = new WSTransport(sock, timeout, qSize);
98
- sock.onmessage = async ({ data }) => await t.queue.enqueue(data);
99
- sock.onclose = async () => await t.queue.close();
100
- sock.onerror = () => sock.close();
101
- return withTimeout(timeout, () => new Promise((r) => (sock.onopen = () => r(t))));
102
- }
103
- close() {
104
- this.sock.close();
105
- return Promise.resolve();
106
- }
107
- write(data) {
108
- const buffered = this.sock.bufferedAmount;
109
- this.sock.send(data);
110
- return withTimeout(this.timeout, async () => {
111
- while (this.sock.bufferedAmount > buffered)
112
- await delay();
113
- });
114
- }
115
- async readBinary(size) {
116
- const data = await this.read();
117
- if (typeof data == "string")
118
- throw new TransportError("invalid text block: expected binary");
119
- if (data.byteLength !== size)
120
- throw new TransportError("invalid block size");
121
- return data;
122
- }
123
- }
124
- function withTimeout(ms, action) {
125
- return Promise.race([
126
- action(),
127
- (async () => {
128
- await delay(ms);
129
- throw new Error("timeout");
130
- })(),
131
- ]);
132
- }
133
- class ChatResponseError extends Error {
134
- constructor(message, data) {
135
- super(message);
136
- this.message = message;
137
- this.data = data;
138
- }
139
- }
140
- class ChatTransport extends Transport {
141
- constructor(ws, timeout, qSize) {
142
- super(qSize);
143
- this.ws = ws;
144
- this.timeout = timeout;
145
- }
146
- static async connect(srv, timeout, qSize) {
147
- const ws = await WSTransport.connect(`ws://${srv.host}:${srv.port || "80"}`, timeout, qSize);
148
- const c = new ChatTransport(ws, timeout, qSize);
149
- processWSQueue(c, ws).then(noop, noop);
150
- return c;
151
- }
152
- async close() {
153
- await this.ws.close();
154
- }
155
- async write(cmd) {
156
- return this.ws.write(JSON.stringify(cmd));
157
- }
158
- }
159
- function noop() { }
160
- async function processWSQueue(c, ws) {
161
- var _a;
162
- for await (const data of ws) {
163
- const str = data instanceof Promise ? await data : data;
164
- if (typeof str != "string") {
165
- await c.queue.enqueue(new ChatResponseError("websocket data is not a string"));
166
- continue;
167
- }
168
- let resp;
169
- try {
170
- const data = JSON.parse(str);
171
- if (typeof ((_a = data === null || data === void 0 ? void 0 : data.resp) === null || _a === void 0 ? void 0 : _a.type) == "string") {
172
- resp = data;
173
- }
174
- else {
175
- resp = new ChatResponseError("invalid response format", str);
176
- }
177
- }
178
- catch (err) {
179
- resp = new ChatResponseError(err.message, str);
180
- }
181
- await c.queue.enqueue(resp);
182
- }
183
- await c.queue.close();
184
- }
185
- function delay(ms) {
186
- return new Promise((r) => setTimeout(r, ms));
187
- }
188
-
189
- var ChatType;
190
- (function (ChatType) {
191
- ChatType["CTDirect"] = "@";
192
- ChatType["CTGroup"] = "#";
193
- ChatType["CTContactRequest"] = "<@";
194
- })(ChatType || (ChatType = {}));
195
- var DeleteMode;
196
- (function (DeleteMode) {
197
- DeleteMode["DMBroadcast"] = "broadcast";
198
- DeleteMode["DMInternal"] = "internal";
199
- })(DeleteMode || (DeleteMode = {}));
200
- function cmdString(cmd) {
201
- switch (cmd.type) {
202
- case "showActiveUser":
203
- return "/u";
204
- case "createActiveUser":
205
- return `/u ${JSON.stringify(cmd.profile)}`;
206
- case "startChat":
207
- return "/_start";
208
- case "setFilesFolder":
209
- return `/_files_folder ${cmd.filePath}`;
210
- case "apiGetChats":
211
- return "/_get chats";
212
- case "apiGetChat":
213
- return `/_get chat ${cmd.chatType}${cmd.chatId}${paginationStr(cmd.pagination)}`;
214
- case "apiSendMessage":
215
- return `/_send ${cmd.chatType}${cmd.chatId}${tagged("file", cmd.filePath)}${tagged("quoted", cmd.quotedItem)} json ${JSON.stringify(cmd.msgContent)}`;
216
- case "apiUpdateChatItem":
217
- return `/_update item ${cmd.chatType}${cmd.chatId} ${cmd.chatItemId} json ${JSON.stringify(cmd.msgContent)}`;
218
- case "apiDeleteChatItem":
219
- return `/_delete item ${cmd.chatType}${cmd.chatId} ${cmd.chatItemId} ${cmd.deleteMode}`;
220
- case "apiChatRead":
221
- return `/_read chat ${cmd.chatType}${cmd.chatId} from=${cmd.fromItem} to=${cmd.toItem}`;
222
- case "apiDeleteChat":
223
- return `/_delete ${cmd.chatType}${cmd.chatId}`;
224
- case "apiAcceptContact":
225
- return `/_accept ${cmd.contactReqId}`;
226
- case "apiRejectContact":
227
- return `/_reject ${cmd.contactReqId}`;
228
- case "apiUpdateProfile":
229
- return `/_profile ${JSON.stringify(cmd.profile)}`;
230
- case "apiParseMarkdown":
231
- return `/_parse ${cmd.text}`;
232
- case "getUserSMPServers":
233
- return "/smp_servers";
234
- case "setUserSMPServers":
235
- return `/smp_servers ${cmd.servers.join(",") || "default"}`;
236
- case "addContact":
237
- return "/connect";
238
- case "connect":
239
- return `/connect ${cmd.connReq}`;
240
- case "connectSimplex":
241
- return "/simplex";
242
- case "createMyAddress":
243
- return "/address";
244
- case "deleteMyAddress":
245
- return "/delete_address";
246
- case "showMyAddress":
247
- return "/show_address";
248
- case "addressAutoAccept":
249
- return `/auto_accept ${cmd.enable ? "on" : "off"}`;
250
- }
251
- }
252
- function paginationStr(cp) {
253
- const base = "after" in cp ? ` after=${cp.after}` : "before" in cp ? ` before=${cp.before}` : "";
254
- return base + ` count=${cp.count}`;
255
- }
256
- function tagged(tag, x) {
257
- return x ? ` ${tag}=${x}` : "";
258
- }
259
- var CIDeleteMode;
260
- (function (CIDeleteMode) {
261
- CIDeleteMode["Broadcast"] = "broadcast";
262
- CIDeleteMode["Internal"] = "internal";
263
- })(CIDeleteMode || (CIDeleteMode = {}));
264
-
265
- class ChatCommandError extends Error {
266
- constructor(message, response) {
267
- super(message);
268
- this.message = message;
269
- this.response = response;
270
- }
271
- }
272
- class ChatClient {
273
- constructor(server, config, msgQ, client, transport) {
274
- this.server = server;
275
- this.config = config;
276
- this.msgQ = msgQ;
277
- this.client = client;
278
- this.transport = transport;
279
- this._connected = true;
280
- this.clientCorrId = 0;
281
- this.sentCommands = new Map();
282
- }
283
- static async create(server, cfg, msgQ) {
284
- const transport = await ChatTransport.connect(server, cfg.tcpTimeout, cfg.qSize);
285
- const client = runClient().then(noop, noop);
286
- const c = new ChatClient(server, cfg, msgQ, client, transport);
287
- return c;
288
- async function runClient() {
289
- for await (const t of transport) {
290
- const apiResp = t instanceof Promise ? await t : t;
291
- if (apiResp instanceof ChatResponseError) {
292
- console.log("chat response error: ", apiResp);
293
- }
294
- else {
295
- const { corrId, resp } = apiResp;
296
- if (corrId) {
297
- const req = c.sentCommands.get(corrId);
298
- if (req) {
299
- c.sentCommands.delete(corrId);
300
- if (resp.type == "chatCmdError") {
301
- req.reject(resp);
302
- }
303
- else {
304
- req.resolve(resp);
305
- }
306
- }
307
- else {
308
- // TODO send error to errQ?
309
- console.log("no command sent for chat response: ", apiResp);
310
- }
311
- }
312
- else {
313
- await msgQ.enqueue(resp);
314
- }
315
- }
316
- }
317
- c._connected = false;
318
- }
319
- }
320
- sendChatCommand(command) {
321
- const corrId = ++this.clientCorrId;
322
- const t = { corrId, cmd: cmdString(command) };
323
- this.transport.write(t).then(noop, noop);
324
- return new Promise((resolve, reject) => this.sentCommands.set(corrId, { resolve, reject }));
325
- }
326
- async disconnect() {
327
- await this.transport.close();
328
- await this.client;
329
- }
330
- async getActiveUser() {
331
- const r = await this.sendChatCommand({ type: "showActiveUser" });
332
- switch (r.type) {
333
- case "activeUser":
334
- return r.user;
335
- case "chatCmdError":
336
- if (r.chatError.type == "error" && r.chatError.errorType.type == "noActiveUser")
337
- return undefined;
338
- throw new ChatCommandError("unexpected response error", r);
339
- default:
340
- throw new ChatCommandError("unexpected response", r);
341
- }
342
- }
343
- async createActiveUser(profile) {
344
- const r = await this.sendChatCommand({ type: "createActiveUser", profile });
345
- if (r.type == "activeUser")
346
- return r.user;
347
- throw new ChatCommandError("unexpected response", r);
348
- }
349
- async apiStartChat() {
350
- const r = await this.sendChatCommand({ type: "startChat" });
351
- if (r.type != "chatStarted")
352
- throw new ChatCommandError("unexpected response", r);
353
- }
354
- // func apiGetChats() throws -> [Chat] {
355
- // let r = chatSendCmdSync(.apiGetChats)
356
- // if case let .apiChats(chats) = r { return chats.map { Chat.init($0) } }
357
- // throw r
358
- // }
359
- // func apiGetChat(type: ChatType, id: Int64) throws -> Chat {
360
- // let r = chatSendCmdSync(.apiGetChat(type: type, id: id))
361
- // if case let .apiChat(chat) = r { return Chat.init(chat) }
362
- // throw r
363
- // }
364
- // async sendMessage(type: ChatType, id: number, msg: C.MsgContent)
365
- // func apiSendMessage(type: ChatType, id: Int64, quotedItemId: Int64?, msg: MsgContent) async throws -> ChatItem {
366
- // let chatModel = ChatModel.shared
367
- // let cmd: ChatCommand
368
- // if let itemId = quotedItemId {
369
- // cmd = .apiSendMessageQuote(type: type, id: id, itemId: itemId, msg: msg)
370
- // } else {
371
- // cmd = .apiSendMessage(type: type, id: id, msg: msg)
372
- // }
373
- // let r: ChatResponse
374
- // if type == .direct {
375
- // var cItem: ChatItem!
376
- // let endTask = beginBGTask({ if cItem != nil { chatModel.messageDelivery.removeValue(forKey: cItem.id) } })
377
- // r = await chatSendCmd(cmd, bgTask: false)
378
- // if case let .newChatItem(aChatItem) = r {
379
- // cItem = aChatItem.chatItem
380
- // chatModel.messageDelivery[cItem.id] = endTask
381
- // return cItem
382
- // }
383
- // endTask()
384
- // } else {
385
- // r = await chatSendCmd(cmd, bgDelay: msgDelay)
386
- // if case let .newChatItem(aChatItem) = r {
387
- // return aChatItem.chatItem
388
- // }
389
- // }
390
- // throw r
391
- // }
392
- // func apiUpdateChatItem(type: ChatType, id: Int64, itemId: Int64, msg: MsgContent) async throws -> ChatItem {
393
- // let r = await chatSendCmd(.apiUpdateChatItem(type: type, id: id, itemId: itemId, msg: msg), bgDelay: msgDelay)
394
- // if case let .chatItemUpdated(aChatItem) = r { return aChatItem.chatItem }
395
- // throw r
396
- // }
397
- // func apiDeleteChatItem(type: ChatType, id: Int64, itemId: Int64, mode: CIDeleteMode) async throws -> ChatItem {
398
- // let r = await chatSendCmd(.apiDeleteChatItem(type: type, id: id, itemId: itemId, mode: mode), bgDelay: msgDelay)
399
- // if case let .chatItemDeleted(_, toChatItem) = r { return toChatItem.chatItem }
400
- // throw r
401
- // }
402
- // func getUserSMPServers() throws -> [String] {
403
- // let r = chatSendCmdSync(.getUserSMPServers)
404
- // if case let .userSMPServers(smpServers) = r { return smpServers }
405
- // throw r
406
- // }
407
- // func setUserSMPServers(smpServers: [String]) async throws {
408
- // let r = await chatSendCmd(.setUserSMPServers(smpServers: smpServers))
409
- // if case .cmdOk = r { return }
410
- // throw r
411
- // }
412
- // func apiAddContact() throws -> String {
413
- // let r = chatSendCmdSync(.addContact, bgTask: false)
414
- // if case let .invitation(connReqInvitation) = r { return connReqInvitation }
415
- // throw r
416
- // }
417
- // func apiConnect(connReq: String) async throws -> Bool {
418
- // let r = await chatSendCmd(.connect(connReq: connReq))
419
- // let am = AlertManager.shared
420
- // switch r {
421
- // case .sentConfirmation: return true
422
- // case .sentInvitation: return true
423
- // case let .contactAlreadyExists(contact):
424
- // am.showAlertMsg(
425
- // title: "Contact already exists",
426
- // message: "You are already connected to \(contact.displayName) via this link."
427
- // )
428
- // return false
429
- // case .chatCmdError(.error(.invalidConnReq)):
430
- // am.showAlertMsg(
431
- // title: "Invalid connection link",
432
- // message: "Please check that you used the correct link or ask your contact to send you another one."
433
- // )
434
- // return false
435
- // case .chatCmdError(.errorAgent(.BROKER(.TIMEOUT))):
436
- // am.showAlertMsg(
437
- // title: "Connection timeout",
438
- // message: "Please check your network connection and try again."
439
- // )
440
- // return false
441
- // case .chatCmdError(.errorAgent(.BROKER(.NETWORK))):
442
- // am.showAlertMsg(
443
- // title: "Connection error",
444
- // message: "Please check your network connection and try again."
445
- // )
446
- // return false
447
- // default: throw r
448
- // }
449
- // }
450
- // func apiDeleteChat(type: ChatType, id: Int64) async throws {
451
- // let r = await chatSendCmd(.apiDeleteChat(type: type, id: id), bgTask: false)
452
- // if case .contactDeleted = r { return }
453
- // throw r
454
- // }
455
- // func apiUpdateProfile(profile: Profile) async throws -> Profile? {
456
- // let r = await chatSendCmd(.apiUpdateProfile(profile: profile))
457
- // switch r {
458
- // case .userProfileNoChange: return nil
459
- // case let .userProfileUpdated(_, toProfile): return toProfile
460
- // default: throw r
461
- // }
462
- // }
463
- // func apiParseMarkdown(text: String) throws -> [FormattedText]? {
464
- // let r = chatSendCmdSync(.apiParseMarkdown(text: text))
465
- // if case let .apiParsedMarkdown(formattedText) = r { return formattedText }
466
- // throw r
467
- // }
468
- // func apiCreateUserAddress() async throws -> String {
469
- // let r = await chatSendCmd(.createMyAddress)
470
- // if case let .userContactLinkCreated(connReq) = r { return connReq }
471
- // throw r
472
- // }
473
- // func apiDeleteUserAddress() async throws {
474
- // let r = await chatSendCmd(.deleteMyAddress)
475
- // if case .userContactLinkDeleted = r { return }
476
- // throw r
477
- // }
478
- // func apiGetUserAddress() async throws -> String? {
479
- // let r = await chatSendCmd(.showMyAddress)
480
- // switch r {
481
- // case let .userContactLink(connReq):
482
- // return connReq
483
- // case .chatCmdError(chatError: .errorStore(storeError: .userContactLinkNotFound)):
484
- // return nil
485
- // default: throw r
486
- // }
487
- // }
488
- // func apiAcceptContactRequest(contactReqId: Int64) async throws -> Contact {
489
- // let r = await chatSendCmd(.apiAcceptContact(contactReqId: contactReqId))
490
- // if case let .acceptingContactRequest(contact) = r { return contact }
491
- // throw r
492
- // }
493
- // func apiRejectContactRequest(contactReqId: Int64) async throws {
494
- // let r = await chatSendCmd(.apiRejectContact(contactReqId: contactReqId))
495
- // if case .contactRequestRejected = r { return }
496
- // throw r
497
- // }
498
- // func apiChatRead(type: ChatType, id: Int64, itemRange: (Int64, Int64)) async throws {
499
- // let r = await chatSendCmd(.apiChatRead(type: type, id: id, itemRange: itemRange))
500
- // if case .cmdOk = r { return }
501
- // throw r
502
- // }
503
- // func acceptContactRequest(_ contactRequest: UserContactRequest) async {
504
- // do {
505
- // let contact = try await apiAcceptContactRequest(contactReqId: contactRequest.apiId)
506
- // let chat = Chat(chatInfo: ChatInfo.direct(contact: contact), chatItems: [])
507
- // DispatchQueue.main.async { ChatModel.shared.replaceChat(contactRequest.id, chat) }
508
- // } catch let error {
509
- // logger.error("acceptContactRequest error: \(error.localizedDescription)")
510
- // }
511
- // }
512
- // func rejectContactRequest(_ contactRequest: UserContactRequest) async {
513
- // do {
514
- // try await apiRejectContactRequest(contactReqId: contactRequest.apiId)
515
- // DispatchQueue.main.async { ChatModel.shared.removeChat(contactRequest.id) }
516
- // } catch let error {
517
- // logger.error("rejectContactRequest: \(error.localizedDescription)")
518
- // }
519
- // }
520
- // func markChatRead(_ chat: Chat) async {
521
- // do {
522
- // let minItemId = chat.chatStats.minUnreadItemId
523
- // let itemRange = (minItemId, chat.chatItems.last?.id ?? minItemId)
524
- // let cInfo = chat.chatInfo
525
- // try await apiChatRead(type: cInfo.chatType, id: cInfo.apiId, itemRange: itemRange)
526
- // DispatchQueue.main.async { ChatModel.shared.markChatItemsRead(cInfo) }
527
- // } catch {
528
- // logger.error("markChatRead apiChatRead error: \(error.localizedDescription)")
529
- // }
530
- // }
531
- // func markChatItemRead(_ cInfo: ChatInfo, _ cItem: ChatItem) async {
532
- // do {
533
- // try await apiChatRead(type: cInfo.chatType, id: cInfo.apiId, itemRange: (cItem.id, cItem.id))
534
- // DispatchQueue.main.async { ChatModel.shared.markChatItemRead(cInfo, cItem) }
535
- // } catch {
536
- // logger.error("markChatItemRead apiChatRead error: \(error.localizedDescription)")
537
- // }
538
- // }
539
- // async createSMPQueue(rcvKey: SignKey, rcvPubKey: PublicKey<KeyType.Verify>): Promise<SMP.IDS> {
540
- // const pubKeyStr = new Uint8Array(await C.encodePubKey(rcvPubKey))
541
- // const resp = await this.sendSMPCommand(rcvKey, B.empty, SMP.cNEW(pubKeyStr))
542
- // if (resp.cmd === "IDS") return resp
543
- // throw new Error("unexpected response")
544
- // }
545
- // subscribeSMPQueue(rcvKey: SignKey, queueId: Uint8Array): Promise<void> {
546
- // return this.msgSMPCommand(rcvKey, queueId, SMP.cSUB())
547
- // }
548
- // async secureSMPQueue(rcvKey: SignKey, queueId: Uint8Array, sndPubKey: PublicKey<KeyType.Verify>): Promise<void> {
549
- // const pubKeyStr = new Uint8Array(await C.encodePubKey(sndPubKey))
550
- // return this.okSMPCommand(rcvKey, queueId, SMP.cKEY(pubKeyStr))
551
- // }
552
- // async sendSMPMessage(sndKey: SignKey | undefined, queueId: Uint8Array, msg: Uint8Array): Promise<void> {
553
- // const resp = await this.sendSMPCommand(sndKey, queueId, SMP.cSEND(msg))
554
- // if (resp.cmd !== "OK") throw new Error("unexpected response")
555
- // }
556
- // ackSMPMessage(rcvKey: SignKey, queueId: Uint8Array): Promise<void> {
557
- // return this.msgSMPCommand(rcvKey, queueId, SMP.cACK())
558
- // }
559
- // suspendSMPQueue(rcvKey: SignKey, queueId: Uint8Array): Promise<void> {
560
- // return this.okSMPCommand(rcvKey, queueId, SMP.cOFF())
561
- // }
562
- // deleteSMPQueue(rcvKey: SignKey, queueId: Uint8Array): Promise<void> {
563
- // return this.okSMPCommand(rcvKey, queueId, SMP.cDEL())
564
- // }
565
- // private async msgSMPCommand(rcvKey: SignKey, queueId: Uint8Array, command: SMPCommand<Client>): Promise<void> {
566
- // const resp = await this.sendSMPCommand(rcvKey, queueId, command)
567
- // switch (resp.cmd) {
568
- // case "OK":
569
- // return
570
- // case "MSG":
571
- // return this.msgQ.enqueue({server: this.server, queueId, command: resp})
572
- // default:
573
- // throw new Error("unexpected response")
574
- // }
575
- // }
576
- // private async okChatCommand(command: ChatCommand): Promise<void> {
577
- // const resp = await this.sendChatCommand(command)
578
- // if (resp.type !== "cmdOk") throw new ChatCommandError("unexpected response", resp)
579
- // }
580
- get connected() {
581
- return this._connected;
582
- }
583
- }
584
-
585
- export { ChatClient };