diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt index 0615f6e5dd..52cb2b338b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt @@ -874,10 +874,10 @@ private fun ChatListSearchBar(listState: LazyListState, searchText: MutableState } else { val candidate = nameSearchCandidate(it.trim()) connectNameCandidate.value = candidate - // clear the previous match immediately so the list falls back to text search during the debounce, - // instead of showing a stale filtered chat while the new search runs - searchChatFilteredBySimplexLink.value = emptySet() if (candidate != null) { + // clear the previous match immediately so the list falls back to text search during the debounce, + // instead of showing a stale filtered chat while the new search runs + searchChatFilteredBySimplexLink.value = emptySet() // resolve the name locally on each keystroke, debounced; collectLatest cancels the in-flight // search when the next keystroke arrives. A bare name can be a contact or a channel, so search // both and filter every known chat found; drop the row only when both types are already known. diff --git a/docs/protocol/agent-protocols.md b/docs/protocol/agent-protocols.md new file mode 100644 index 0000000000..d4e828d677 --- /dev/null +++ b/docs/protocol/agent-protocols.md @@ -0,0 +1,472 @@ +Revision 1, 2026-10-19, draft for comments + +Evgeny Poberezkin + +# Agent protocols over SimpleX + +## Table of contents + +- [Abstract](#abstract) +- [Conventions](#conventions) +- [Overview](#overview) +- [Addresses in llms.txt](#addresses-in-llmstxt) +- [Profile declarations](#profile-declarations) + - [protocols](#protocols) + - [Public group audience and update frequency](#public-group-audience-and-update-frequency) + - [Contact address peers](#contact-address-peers) + - [Protocol names](#protocol-names) +- [Chat transport](#chat-transport) + - [Commands](#commands) + - [Replies](#replies) + - [Message size](#message-size) +- [Service RPC transport](#service-rpc-transport) + - [Exchange](#exchange) + - [Request](#request) + - [Response](#response) + - [Signed requests](#signed-requests) +- [MCP binding](#mcp-binding) + - [Messages](#messages) + - [Versions and capabilities](#versions-and-capabilities) + - [Examples](#examples) +- [Security considerations](#security-considerations) +- [Privacy considerations](#privacy-considerations) +- [Implementations](#implementations) +- [References](#references) + +## Abstract + +This document defines how software agents find SimpleX addresses and call the tools of other agents and bots over SimpleX: + +- an `llms.txt` section with communication addresses, beside the link relation type and the well-known document of [Publishing SimpleX addresses on websites](./web-discovery.md); +- declarations: the protocols a profile serves and their transports, the peer types a channel or a contact address is made for, and the update frequency of a channel; +- the chat transport: the command `/_call` in a conversation; +- the Service RPC transport: one request and one response, without a conversation, in a generic envelope; +- the binding of the Model Context Protocol (MCP) [MCP] to both transports. + +## Conventions + +The key words "MUST", "MUST NOT", "SHOULD", "SHOULD NOT" and "MAY" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals. + +JSON schemas use JSON Type Definition [RFC8927]. Whitespace in JSON examples is for readability. + +**Bot**: a SimpleX profile operated by software: a bot of the bot library, or an AI agent through `@simplex-chat/mcp`. + +## Overview + +A website owner publishes channels for updates and contact addresses for requests [WEB]. An agent: + +1. reads the addresses from the website: link relations, `/.well-known/simplex.json` or `llms.txt`; +2. retrieves the link data with the link, without connecting; +3. reads the declarations; +4. subscribes to a channel, connects to a contact address, or sends a Service RPC request to it. + +```mermaid +sequenceDiagram + participant A as Agent + participant W as Website + participant R as SMP router + participant B as Bot + + A->>W: GET /.well-known/simplex.json + W-->>A: channels and contacts + A->>R: retrieve link data + R-->>A: link data with declarations + alt chat transport + A->>B: connect, then /_call messages + B-->>A: replies + else Service RPC transport + A->>B: request in the connection request + B-->>A: response to the reply queue + end +``` + +Two transports carry protocol messages: + +- **chat**: a conversation in a direct chat or a group. The caller connects once and sends commands as messages. The conversation persists between calls. +- **rpc**: Service RPC. The request is sent in the connection request to a contact address, and the response to the reply queue of that request. Both sides then delete the connection. A call needs one round trip, and each call uses a new connection. + +## Addresses in llms.txt + +An `llms.txt` file [LLMSTXT] lists communication addresses in a section with the heading `## Communications`. The section is a file list: each item is a Markdown link, then a colon and notes. + +The notes begin with the network and the type of the address, then the audience and, for updates, the update frequency or, for contacts, the protocols. Free text follows: + +``` + updates for [, ]. + group for . + [business ]contact for [, ]*. +``` + +- ``: the name of the network, `SimpleX` for SimpleX links. +- `updates`: an address that publishes updates. `group`: an address of a group, where members converse. `contact`: an address that accepts conversations and requests. `business contact`: a business address [BUSINESS]. +- ``: `people`, `agents`, or `people and agents`. +- ``: a `changefreq` value of the Sitemaps protocol [SITEMAPS]. +- ``: the name of a protocol the address serves ([protocol names](#protocol-names)), in any letter case. +- ``: what the address is about. + +The network is named in the notes because the host in a SimpleX link is the router that hosts the address, and that router can have any domain name. + +For SimpleX entries, the type `updates` requires a channel link, the type `group` a group link, and the types `contact` and `business contact` a contact address link [WEB]. The link text is the title of the address. + +``` +## Communications + +- [Acme news](https://smp12.simplex.im/c#…): SimpleX updates for people and agents, weekly. Product news and offers. +- [Acme customers](https://smp12.simplex.im/g#…): SimpleX group for people. Questions and tips from customers. +- [Ask Acme](https://smp12.simplex.im/a#…): SimpleX business contact for agents, MCP. Orders and availability. +- [Acme support](mailto:support@acme.example): Email contact for people. +``` + +## Profile declarations + +A profile or a group declares the protocols it serves in its preferences. A channel or a public group declares its audience in its public group record, and a channel also its update frequency. A contact address declares the peers it serves in its link data. + +### protocols + +`protocols` is a key of the preferences: `preferences` of a user or contact profile, and `groupPreferences` of a group profile [CHAT]. Clients store the preferences of other profiles as received, so a client that does not know the key decodes it after an upgrade. + +JSON Type Definition schema [RFC8927] of the `protocols` key: + +```json +{ + "values": { + "properties": { + "versions": {"elements": {"type": "string"}}, + "transports": { + "optionalProperties": { + "chat": {"type": "boolean"}, + "rpc": {"type": "boolean"} + }, + "additionalProperties": true + } + }, + "additionalProperties": true + } +} +``` + +- Keys: protocol names. +- `versions`: required. The protocol versions the bot serves, in the bot's order of preference. +- `transports`: required. The transports on which the bot serves the protocol. +- `transports.chat`: the bot answers `/_call` for the protocol in a conversation. +- `transports.rpc`: the bot answers Service RPC requests for the protocol on its contact address. It is valid in `preferences` only. +- An absent transport is false. +- Clients MUST ignore unknown keys. + +A declaration in `preferences` applies to direct chats with the profile and to Service RPC requests to its contact address. A declaration in `groupPreferences` states that the group's owners serve the protocol in the group chat. Other members can serve a protocol in a group without a declaration, because the core removes preferences from member profiles in groups. In a business chat, `protocols` of the business profile MUST be copied into the chat's `groupPreferences`, as `commands` are. + +`commands` remains the command menu for people. `/_call` is not added to it. The underscore marks a command for software, as in the API of the core, and leaves `/call` free for bots' commands for people. + +Example, the `preferences` of a contact profile: + +```json +{ + "protocols": { + "mcp": { + "versions": ["2026-07-28"], + "transports": {"chat": true, "rpc": true} + } + } +} +``` + +### Public group audience and update frequency + +Two fields of the public group record, `publicGroup` of the group profile of a channel or a public group [CHAT]. JSON Type Definition schema [RFC8927]: + +```json +{ + "optionalProperties": { + "audience": {"elements": {"type": "string"}}, + "changeFrequency": {"enum": ["always", "hourly", "daily", "weekly", "monthly", "yearly", "never"]} + } +} +``` + +- `audience`: the peer types the channel or the group is made for. The values are the values of a profile's `peerType`: `human`, `bot`, `business`, `agent`. An absent `audience` is `["human"]`. Agents also read content made for people, so a channel or a group declares `agent` when it is made for agents, or has dedicated support for them. +- `changeFrequency`: in a channel only, how often the channel is updated. The values are the `changefreq` values of the Sitemaps protocol [SITEMAPS]: `always`, `hourly`, `daily`, `weekly`, `monthly`, `yearly`, `never`. + +### Contact address peers + +One field of the data published with a contact address link, beside `business`, which marks a business address [BUSINESS]. JSON Type Definition schema [RFC8927]: + +```json +{ + "optionalProperties": { + "serves": {"elements": {"type": "string"}} + } +} +``` + +- `serves`: the peer types the address is made for, with the values and the default of `audience`. + +Clients MUST ignore unknown values of `audience` and `serves`. The values in the public group record and in the address link data take precedence over the values on a website [WEB]. + +### Protocol names + +A protocol name consists of lowercase ASCII letters, digits and hyphens. + +A binding defines the protocol's request and response messages, including how, and whether, a peer discovers the protocol's capabilities. Both transports carry these messages unchanged. + +| Name | Binding | +|---|---| +| `mcp` | [MCP binding](#mcp-binding) | + +## Chat transport + +### Commands + +``` +/_call +/_call @ +``` + +- ``: the name of a protocol the recipient serves. +- ``: the protocol's request message, a JSON object. +- `@`: optional, in a group: a mention of the called member [CHAT]. +- In a group, a command with a mention is answered by the mentioned member. A command without a mention is a broadcast: each member that serves the protocol answers it. +- A command is the text of a text message. One space separates its parts. +- A bot that declares `transports.chat` for a protocol MUST answer each `/_call` for it that mentions the bot or mentions nobody. + +### Replies + +- A bot sends one reply to each command it answers. The reply quotes the command, with a truncated quote. Its text is the protocol's response message, a JSON object. +- A caller matches each reply to its command by the quoted message. Replies can arrive in any order. +- In a group, a broadcast can receive several replies, one from each member that serves the protocol. A caller collects them all, each with its sender. +- In a direct chat, a caller accepts replies only from the recipient. In a group, a caller of a command with a mention accepts only the mentioned member's reply. +- Commands and replies are ordinary messages: people see them in the conversation. + +### Message size + +A command and a reply are one message each. The core refuses a message whose encoded JSON exceeds 15,602 bytes [CHAT]. The command or the reply is a string inside that JSON, so its escape characters count towards the limit. A bot whose reply exceeds the limit replies with the binding's error instead. + +## Service RPC transport + +### Exchange + +The requester's core sends the request with: + +``` +/_service_request [timeout=] [sign_key=] +``` + +- ``: a contact address link, or the SimpleX name of a contact address [NAMES]. +- `timeout`: how long the requester waits for the response. The default is 30 seconds. +- `sign_key`: the requester's Ed25519 private key, for a [signed request](#signed-requests). +- ``: the [request](#request), a JSON object. +- The command returns `serviceResponse` with `responseData`, the [response](#response). + +The responder's core reports the request as the event `serviceRequest`, with `requestId`, `signerKey` and `requestData`. The responder sends the response with: + +``` +/_service_response +``` + +Requirements: + +- The responder's core MUST run with service requests enabled, the option `service_requests=on` of `/_start`. Otherwise the core drops each request, and the requester waits until its timeout. +- The responder's contact address MUST have ratchet keys. The core adds them to an address created or updated with the option `pq_ratchet=on` or `pq_ratchet=off` (`/_address`, `/_short_link_address`, `/_address_settings`). A request to an address without ratchet keys fails at once. +- The core accepts a response within 180 seconds of receiving the request. A requester that expects a slow response sets `timeout` accordingly. +- Before encryption, the agent pads a request or a response, its own framing included, to 11,106 bytes, and a longer one fails. A request and a response SHOULD NOT exceed 10,000 bytes of JSON. + +### Request + +JSON Type Definition schema [RFC8927] of the request: + +```json +{ + "properties": { + "protocol": {"type": "string"}, + "request": {} + }, + "optionalProperties": { + "version": {}, + "requestKey": {"type": "string"} + }, + "additionalProperties": true +} +``` + +- `protocol`: the protocol name. +- `version`: the protocol version, for a protocol whose messages do not include one. Its type is defined by the binding. +- `requestKey`: the requester's Ed25519 public key, in the encoding of the core's `signerKey`: base64url of the X.509 DER encoding of the key. A request with `requestKey` MUST be signed with that key. +- `request`: the protocol's request message, the same as after `/_call ` in a conversation. The fields of the envelope are never added to it. + +### Response + +JSON Type Definition schema [RFC8927] of the response: + +```json +{ + "optionalProperties": { + "response": {}, + "error": { + "properties": { + "code": {"type": "string"} + }, + "optionalProperties": { + "message": {"type": "string"}, + "retryAfter": {"type": "uint32"} + }, + "additionalProperties": true + } + }, + "additionalProperties": true +} +``` + +- A response has exactly one of `response` and `error`. +- `response`: the protocol's response message, the same as the reply in a conversation. +- `error`: the request was not passed to the protocol. +- `message`: a description for the requester's logs. +- `retryAfter`: the number of seconds after which a repeated request can succeed. + +Error codes: + +- `unsupported_protocol`: the responder does not serve the protocol over Service RPC. +- `unsupported_version`: the responder does not serve the requested version. +- `bad_request`: the envelope is invalid, or `requestKey` differs from the verified signer key. +- `conversation_required`: the request needs a conversation. The requester connects to the address and uses the chat transport. +- `too_large`: the response exceeds the size limit. +- `rate_limited`: the requester repeats the request after `retryAfter`. +- `internal`: the responder failed. + +A requester treats an unknown code as final, unless the error has `retryAfter`. + +### Signed requests + +- A signed request has the requester's Ed25519 signature over the request payload, bound to the request's ratchet session: the agent signs `SHA3-256("SimpleXService" ‖ AD) ‖ payload`, where AD is the ratchet's associated data. The responder's agent verifies the signature, and the core reports the key as `signerKey`. A signature copied into another session fails verification. +- A responder MUST answer `bad_request` to a request whose `requestKey` differs from `signerKey`, and to an unsigned request with `requestKey`. +- A requester signs when the responder needs a persistent identity, for example an account or a purchase. All requests signed with one key are linkable to each other. A requester SHOULD use a separate key for each responder. +- An unsigned request has only the payload: the responder cannot link it to other requests by a key. + +## MCP binding + +Protocol name: `mcp`. + +### Messages + +- The request is one JSON-RPC request of MCP revision 2026-07-28 or later, with its `_meta` envelope [MCP]. In a conversation, it follows `/_call mcp`. Over Service RPC, it is the envelope's `request`. +- The response is the JSON-RPC response to the request: a result or a JSON-RPC error. In a conversation, it is the text of the reply. Over Service RPC, it is the envelope's `response`. +- In a conversation, a caller matches a reply to its request by the quoted message. The response has the request's `id`, as JSON-RPC requires. +- A bot serves the methods answered with one response, for example `server/discover`, `tools/list` and `tools/call`. +- A multi-round-trip result (`resultType: "input_required"`) is answered with a new request, as MCP defines. +- A response above the size limit is replaced with a JSON-RPC error with code -32603 in a conversation, and with the envelope error `too_large` over Service RPC. +- Over Service RPC, a call of a tool that needs a conversation is answered with the envelope error `conversation_required`. + +### Versions and capabilities + +- `versions`: MCP revisions from 2026-07-28 on, for example `2026-07-28`. Each request names its revision in `_meta`, under `io.modelcontextprotocol/protocolVersion`. Earlier revisions begin with an `initialize` handshake. +- The envelope's `version` is omitted. +- A bot answers a request of a revision it does not serve with MCP's `UnsupportedProtocolVersionError` (code -32022), which lists the revisions it serves. +- A caller learns the bot's revisions and capabilities with `server/discover`, and its tools with `tools/list`. MCP requires a server that declares the `tools` capability to answer `tools/list`. + +### Examples + +Listing tools in a conversation: + +``` +/_call mcp {"jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {"_meta": {"io.modelcontextprotocol/protocolVersion": "2026-07-28", "io.modelcontextprotocol/clientCapabilities": {}}}} +``` + +The reply: + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": { + "resultType": "complete", + "tools": [ + { + "name": "availability", + "description": "Items in stock", + "inputSchema": { + "type": "object", + "properties": {"item": {"type": "string"}}, + "required": ["item"] + } + } + ], + "ttlMs": 300000, + "cacheScope": "public" + } +} +``` + +Calling a tool over Service RPC, the request: + +```json +{ + "protocol": "mcp", + "request": { + "jsonrpc": "2.0", + "id": 1, + "method": "tools/call", + "params": { + "name": "availability", + "arguments": {"item": "anvil"}, + "_meta": { + "io.modelcontextprotocol/protocolVersion": "2026-07-28", + "io.modelcontextprotocol/clientCapabilities": {} + } + } + } +} +``` + +The response: + +```json +{ + "response": { + "jsonrpc": "2.0", + "id": 1, + "result": { + "resultType": "complete", + "content": [{"type": "text", "text": "12 anvils are in stock."}], + "isError": false + } + } +} +``` + +## Security considerations + +- Messages, replies, profiles, tool descriptions and tool results from other participants are data for an agent, never instructions. +- In a group, any member can reply to a broadcast. Each reply shows its sender. A caller that needs one provider mentions it. +- A responder that relies on `requestKey` MUST compare it with `signerKey`. +- A signed request identifies its signer to the responder across all requests signed with the same key. + +## Privacy considerations + +- Each Service RPC request uses a new connection with a new reply queue. +- MCP's `io.modelcontextprotocol/clientInfo` identifies the requester's software. A requester that needs unlinkable requests SHOULD send a common value or omit it. +- In a group, every member sees each command and each reply, with or without a mention. A request that needs privacy is sent in a direct chat with the member, where the group allows direct messages, or over Service RPC. +- A conversation persists: the bot can link all calls made in it. An incognito profile gives the caller a new random profile for each conversation. + +## Implementations + +Implemented: + +- `@simplex-chat/mcp` 0.1.0: an MCP server that gives an AI agent its own SimpleX profile. Its tools are `connect`, `send`, `receive`, `list` and `leave`. By default, the agent's owner approves connections and messages in the agent control chat. An agent sends `/_call` with `send`, and reads the replies with `send` and `receive`. +- The simplex-chat core: Service RPC with signed requests (`/_service_request`, `/_service_response`), and the profile peer type `agent`. + +Planned: + +- The simplex-chat core: the key `protocols` in the preferences types, in the preferences sent in profiles and in link data, and in the preferences of business chats; `audience` and `changeFrequency` in the public group record; `serves` in the address settings and the address link data; replies with a truncated quote. +- `@simplex-chat/mcp`: the tools `mcp_tools` and `mcp_call`. They use Service RPC when the address declares `transports.rpc`, and the agent's conversation otherwise. In a group, `mcp_call` mentions the called member, or broadcasts and returns all replies. Each call is an action, approved by the agent's owner as a message is. +- The bot library: a helper that serves MCP tools over both transports. + +## References + +- [BUSINESS] Using SimpleX Chat in business: [BUSINESS.md](../BUSINESS.md) +- [CHAT] SimpleX Chat protocol: [simplex-chat.md](./simplex-chat.md) +- [LLMSTXT] The /llms.txt file: https://llmstxt.org +- [MCP] Model Context Protocol specification, revision 2026-07-28: https://modelcontextprotocol.io/specification/2026-07-28 +- [NAMES] SimpleX public names for channels and businesses: [names-overview.md](./names-overview.md) +- [RFC2119] Key words for use in RFCs to Indicate Requirement Levels: https://www.rfc-editor.org/rfc/rfc2119 +- [RFC8174] Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words: https://www.rfc-editor.org/rfc/rfc8174 +- [RFC8927] JSON Type Definition: https://www.rfc-editor.org/rfc/rfc8927 +- [SITEMAPS] Sitemaps XML format: https://www.sitemaps.org/protocol.html +- [WEB] Publishing SimpleX addresses on websites: [web-discovery.md](./web-discovery.md) diff --git a/docs/protocol/web-discovery.md b/docs/protocol/web-discovery.md new file mode 100644 index 0000000000..cc35a73ddc --- /dev/null +++ b/docs/protocol/web-discovery.md @@ -0,0 +1,251 @@ +Revision 1, 2026-10-19, draft + +Evgeny Poberezkin + +# Publishing SimpleX addresses on websites + +## Table of contents + +- [Abstract](#abstract) +- [Conventions](#conventions) +- [Terms](#terms) +- [Link relation type](#link-relation-type) + - [simplex](#simplex) + - [Owner's and described addresses](#owners-and-described-addresses) + - [Target attributes](#target-attributes) + - [HTML](#html) + - [HTTP](#http) +- [Well-known URI](#well-known-uri) + - [Retrieval](#retrieval) + - [Format](#format) + - [Example](#example) +- [Processing](#processing) +- [Security considerations](#security-considerations) +- [Privacy considerations](#privacy-considerations) +- [IANA considerations](#iana-considerations) + - [Link relation type registration](#link-relation-type-registration) + - [Well-known URI registration](#well-known-uri-registration) +- [HTML link type extensions](#html-link-type-extensions) +- [References](#references) + +## Abstract + +This document defines how a website publishes SimpleX channels, groups and contact addresses: + +- the link relation type `simplex`, for the HTML elements `link`, `a` and `area`, and for the HTTP `Link` header field. Its targets are the addresses of the website's owner, or the addresses a page describes, such as the entries of a directory; +- the well-known URI `simplex.json`: a JSON document with the owner's channels, groups and contact addresses, their audience, the update frequency of each channel, and the kind and the protocols of each contact address. + +People and software agents subscribe to a channel to receive updates, join a group to take part in its conversations, and connect to a contact address to send requests. [Agent protocols over SimpleX](./agent-protocols.md) defines the requests an agent sends to a contact address, and an `llms.txt` section with the same addresses. + +## Conventions + +The key words "MUST", "MUST NOT", "SHOULD", "SHOULD NOT" and "MAY" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals. + +JSON schemas use JSON Type Definition [RFC8927]. + +## Terms + +- **SimpleX link**: a short connection link of the SimpleX Agent protocol, with the `https` or the `simplex` scheme [AGENT]. The type in the link is the kind of the address. +- **Channel link**: a SimpleX link of type `c`, for example `https://smp12.simplex.im/c#`. +- **Group link**: a SimpleX link of type `g`, for example `https://smp12.simplex.im/g#`. +- **Contact address link**: a SimpleX link of type `a`, for example `https://smp12.simplex.im/a#`. +- **SimpleX name**: a name in the SimpleX name service [NAMES]. `#example.simplex` is the name of a channel, `@example.simplex` the name of a contact address. +- **Owner**: the person or organization that publishes the website. + +## Link relation type + +### simplex + +The target of a `simplex` link is a SimpleX channel, group or contact address of the owner of the link context, or described by the link context. The target MUST be a channel link, a group link or a contact address link. The type in the link tells which. + +### Owner's and described addresses + +A page that describes the addresses of others, such as a directory or a catalogue, uses the relation type on its entries. The owner's own addresses are listed in the [well-known document](#well-known-uri). + +### Target attributes + +- `title`: the name of the channel, the group or the address, for display. In HTTP, a title with non-ASCII characters is sent as `title*` [RFC8187]. +- `hreflang`: the language of the channel's posts, or of the conversations [BCP47]. + +A link context MAY have several `simplex` links of one kind. Their order is the owner's order of preference: document order in HTML, and the order of link values in HTTP. + +### HTML + +In the document head: + +```html + + +``` + +In the document body, the relation type is used on visible links, for example on the entries of a directory: + +```html +Chat with us +Acme customers +``` + +### HTTP + +``` +Link: ; rel="simplex"; hreflang="en"; title="Acme news" +Link: ; rel="simplex"; hreflang="en"; title="Ask Acme" +``` + +## Well-known URI + +### Retrieval + +The document is located at the path `/.well-known/simplex.json` of an origin [RFC8615]. A client retrieves it with an HTTPS `GET` request. A server responds with status 200, and SHOULD send the media type `application/json`. A client parses the response body as JSON, whatever media type the server sends. + +The entries of the document are the channels, groups and contact addresses of the origin's owner. A site that controls only a path of its origin publishes link relations instead. + +### Format + +The document MUST conform to this JSON Type Definition schema [RFC8927]: + +```json +{ + "optionalProperties": { + "channels": {"elements": {"ref": "address"}}, + "groups": {"elements": {"ref": "address"}}, + "contacts": {"elements": {"ref": "address"}} + }, + "additionalProperties": true, + "definitions": { + "address": { + "properties": { + "link": {"type": "string"} + }, + "optionalProperties": { + "name": {"type": "string"}, + "title": {"type": "string"}, + "lang": {"type": "string"}, + "audience": {"elements": {"type": "string"}}, + "changeFrequency": {"enum": ["always", "hourly", "daily", "weekly", "monthly", "yearly", "never"]}, + "business": {"type": "boolean"}, + "protocols": {"elements": {"type": "string"}} + }, + "additionalProperties": true + } + } +} +``` + +- `channels`: the owner's channels, in the owner's order of preference. +- `groups`: the owner's groups, in the owner's order of preference. +- `contacts`: the owner's contact addresses, in the owner's order of preference. +- `link`: required. A channel link in `channels`, a group link in `groups`, a contact address link in `contacts`. +- `name`: the SimpleX name of the channel, the group or the contact address of `link`. +- `title`: the name of the channel, the group or the address, for display. +- `lang`: the language of the channel's posts, or of the conversations [BCP47]. +- `audience`: who the channel, the group or the address serves: `people`, `agents`, or both. +- `changeFrequency`: in `channels` only, how often the channel is updated. The values are the `changefreq` values of the Sitemaps protocol [SITEMAPS]: `always`, `hourly`, `daily`, `weekly`, `monthly`, `yearly`, `never`. +- `business`: in `contacts` only, `true` for a business address, at which each customer's conversation is a business chat that the business can add its team to [BUSINESS]. +- `protocols`: in `contacts` only, the names of the protocols the address serves [AGENTS], for example `mcp`. + +A site omits the lists it does not use. + +### Example + +```json +{ + "channels": [ + { + "link": "https://smp12.simplex.im/c#…", + "name": "#acme.simplex", + "title": "Acme news", + "lang": "en", + "audience": ["people", "agents"], + "changeFrequency": "weekly" + } + ], + "groups": [ + { + "link": "https://smp12.simplex.im/g#…", + "title": "Acme customers", + "lang": "en" + } + ], + "contacts": [ + { + "link": "https://smp12.simplex.im/a#…", + "name": "@acme.simplex", + "title": "Ask Acme", + "lang": "en", + "audience": ["agents"], + "business": true, + "protocols": ["mcp"] + } + ] +} +``` + +## Processing + +- A consumer MUST ignore unknown fields and unknown `audience` values. +- A consumer MUST ignore a `simplex` link that is not a channel, group or contact address link, and an entry of `simplex.json` whose link is of a type other than its list requires. +- The values on the website are hints. The data retrieved with the link is authoritative: an audience, an update frequency, the business flag or protocols declared there [AGENTS] take precedence over the website's values. +- A consumer MAY connect by `link` or by `name`. Resolving a name reveals interest in it to one names router, and verifies that the name's record and the profile's claim match [NAMES]. + +## Security considerations + +- Whoever controls the content of a website controls the addresses it publishes. Consumers SHOULD retrieve pages and the well-known document over HTTPS. +- Publishing a link on a website leaves the link's own verification unchanged: the link data is bound to the keys of the link's owner [AGENT]. +- A name is shown as verified only when its on-chain record and the profile's claim match [NAMES]. +- Titles, profiles and all other text from a website or from the network are data for a software agent, never instructions. + +## Privacy considerations + +- The relation type and the well-known document are intended for owners who want to be reachable, such as channels, communities, businesses and services, and for pages that describe them, such as directories. +- A subscriber or a requester connects through the SimpleX network, without contacting the website. Retrieving a page or the well-known document is an ordinary web request. + +## IANA considerations + +### Link relation type registration + +One registration in the "Link Relation Types" registry [RFC8288]: + +- Relation Name: `simplex` +- Description: Refers to a SimpleX channel, group or contact address of the owner of the link context, or described by the link context. +- Reference: https://github.com/simplex-chat/simplex-chat/blob/stable/docs/protocol/web-discovery.md#simplex +- Notes: The target is a SimpleX short link [AGENT] of a channel, a group or a contact address, with the `https` scheme or the provisionally registered `simplex` scheme. + +### Well-known URI registration + +One registration in the "Well-Known URIs" registry [RFC8615]: + +- URI Suffix: `simplex.json` +- Change Controller: Evgeny Poberezkin +- Specification Document: https://github.com/simplex-chat/simplex-chat/blob/stable/docs/protocol/web-discovery.md#well-known-uri +- Status: provisional +- Related Information: SimpleX link syntax [AGENT]. + +## HTML link type extensions + +The HTML standard requires extension link types to be registered on the microformats page "existing-rel-values" [HTML] [MF-REL]. The entry: + +- Keyword: `simplex` +- Effect on `link`: Hyperlink +- Effect on `a`, `area`: Hyperlink +- Effect on `form`: not allowed +- Brief description: Refers to a SimpleX channel, group or contact address of the page's owner, or described by the page. +- Specification: https://github.com/simplex-chat/simplex-chat/blob/stable/docs/protocol/web-discovery.md#simplex +- Status: proposed + +## References + +- [AGENT] SimpleX Agent protocol, short connection links: https://github.com/simplex-chat/simplexmq/blob/stable/protocol/agent-protocol.md#short-connection-link-syntax +- [AGENTS] Agent protocols over SimpleX: [agent-protocols.md](./agent-protocols.md) +- [BCP47] Tags for Identifying Languages: https://www.rfc-editor.org/info/bcp47 +- [BUSINESS] Using SimpleX Chat in business: [BUSINESS.md](../BUSINESS.md) +- [HTML] HTML Living Standard, other link types: https://html.spec.whatwg.org/multipage/links.html#other-link-types +- [MF-REL] Microformats, existing rel values: https://microformats.org/wiki/existing-rel-values +- [NAMES] SimpleX public names for channels and businesses: [names-overview.md](./names-overview.md) +- [RFC2119] Key words for use in RFCs to Indicate Requirement Levels: https://www.rfc-editor.org/rfc/rfc2119 +- [RFC8174] Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words: https://www.rfc-editor.org/rfc/rfc8174 +- [RFC8187] Indicating Character Encoding and Language for HTTP Header Field Parameters: https://www.rfc-editor.org/rfc/rfc8187 +- [RFC8288] Web Linking: https://www.rfc-editor.org/rfc/rfc8288 +- [RFC8615] Well-Known Uniform Resource Identifiers (URIs): https://www.rfc-editor.org/rfc/rfc8615 +- [RFC8927] JSON Type Definition: https://www.rfc-editor.org/rfc/rfc8927 +- [SITEMAPS] Sitemaps XML format: https://www.sitemaps.org/protocol.html diff --git a/packages/simplex-chat-nodejs/README.md b/packages/simplex-chat-nodejs/README.md index 6ccd26fc05..31338c5142 100644 --- a/packages/simplex-chat-nodejs/README.md +++ b/packages/simplex-chat-nodejs/README.md @@ -66,6 +66,33 @@ You can run it with: `npx ts-node ./examples/squaring-bot.ts` A larger example, with a business address and commands as keys: [SimpleX Calculator](../../apps/simplex-calculator-bot/). +## Live messages + +Live messages are shown to recipients while they are written, e.g. a streamed reply of a language model. + +`onMessage` handler for `bot.run` that streams a reply: + +```javascript +const {api, util} = require("simplex-chat") + +async function onMessage(ci, content, chat) { + const {chatType, chatId} = util.chatInfoRef(ci.chatInfo) + const live = new api.LiveMessage(chat, chatType, chatId) + let text = "" + for await (const delta of streamReply(content.text)) { + text += delta + live.update(text) + } + await live.end() +} +``` + +`streamReply` stands for your function that returns text fragments as an async iterable. + +`update` sends the first text at once, and then updates the message at most once per 3 seconds (`interval` parameter). `end` finalizes the message; a message without text is deleted. + +The underlying commands: `apiSendMessages` with `liveMessage = true` sends a live message, `apiUpdateChatItem` with `liveMessage = true` updates it, and with `liveMessage = false` finalizes it. + ## Native library `libsimplex` and the prebuilt Node.js add-on are downloaded on first use into the user cache (`$XDG_CACHE_HOME/simplex-chat` or `~/.cache/simplex-chat` on Linux, `~/Library/Caches/simplex-chat` on macOS, `%LOCALAPPDATA%\simplex-chat` on Windows), shared with the Python library. To download them ahead of time, for example in a Dockerfile, run as the user that runs the app: @@ -121,10 +148,27 @@ Library provides types and functions to: - create and accept invitations or connect with the contacts. - create and manage long-term user address, accepting connection requests automatically. - send, receive, delete and update messages, and add message reactions. +- send live messages, e.g. streamed replies. - create, join and manage group. - send and receive files. - etc. +## Tests + +```sh +npm install +npm run build +npm test +``` + +To run one test file: `npm test -- tests/core.test.ts`. + +A local SMP server is started for the API and bot tests. On Linux x86-64 and arm64, `smp-server` is downloaded from [simplexmq releases](https://github.com/simplex-chat/simplexmq/releases) to `node_modules/.cache/smp-server`. To use another build, or on other systems, set `SMP_SERVER` to the path of `smp-server`: + +```sh +SMP_SERVER=/path/to/smp-server npm test +``` + ## License [AGPL v3](./LICENSE) diff --git a/packages/simplex-chat-nodejs/docs/Namespace.api.md b/packages/simplex-chat-nodejs/docs/Namespace.api.md index 5771b8450e..14689c9279 100644 --- a/packages/simplex-chat-nodejs/docs/Namespace.api.md +++ b/packages/simplex-chat-nodejs/docs/Namespace.api.md @@ -17,6 +17,7 @@ You need to use it in bot event handlers, and for any other use cases. - [ChatApi](api.Class.ChatApi.md) - [ChatCommandError](api.Class.ChatCommandError.md) +- [LiveMessage](api.Class.LiveMessage.md) ## Interfaces diff --git a/packages/simplex-chat-nodejs/docs/api.Class.ChatApi.md b/packages/simplex-chat-nodejs/docs/api.Class.ChatApi.md index cf293f55d9..bfd2034d94 100644 --- a/packages/simplex-chat-nodejs/docs/api.Class.ChatApi.md +++ b/packages/simplex-chat-nodejs/docs/api.Class.ChatApi.md @@ -6,7 +6,7 @@ # Class: ChatApi -Defined in: [src/api.ts:95](../src/api.ts#L95) +Defined in: [src/api.ts:100](../api.ts#L100) Main API class for interacting with the chat core library. @@ -16,7 +16,7 @@ Main API class for interacting with the chat core library. > `protected` **ctrl\_**: `bigint` \| `undefined` -Defined in: [src/api.ts:101](../src/api.ts#L101) +Defined in: [src/api.ts:106](../api.ts#L106) ## Accessors @@ -26,7 +26,7 @@ Defined in: [src/api.ts:101](../src/api.ts#L101) > **get** **ctrl**(): `bigint` -Defined in: [src/api.ts:343](../src/api.ts#L343) +Defined in: [src/api.ts:345](../api.ts#L345) Chat controller reference @@ -42,7 +42,7 @@ Chat controller reference > **get** **initialized**(): `boolean` -Defined in: [src/api.ts:329](../src/api.ts#L329) +Defined in: [src/api.ts:331](../api.ts#L331) Chat controller is initialized @@ -58,7 +58,7 @@ Chat controller is initialized > **get** **started**(): `boolean` -Defined in: [src/api.ts:336](../src/api.ts#L336) +Defined in: [src/api.ts:338](../api.ts#L338) Chat controller is started @@ -72,7 +72,7 @@ Chat controller is started > **apiAcceptContactRequest**(`contactReqId`): `Promise`\<`Contact`\> -Defined in: [src/api.ts:770](../src/api.ts#L770) +Defined in: [src/api.ts:773](../api.ts#L773) Accept contact request. Network usage: interactive. @@ -93,7 +93,7 @@ Network usage: interactive. > **apiAcceptMember**(`groupId`, `groupMemberId`, `memberRole`): `Promise`\<`GroupMember`\> -Defined in: [src/api.ts:590](../src/api.ts#L590) +Defined in: [src/api.ts:593](../api.ts#L593) Accept group member. Requires Admin role. Network usage: background. @@ -122,7 +122,7 @@ Network usage: background. > **apiAddMember**(`groupId`, `contactId`, `memberRole`): `Promise`\<`GroupMember`\> -Defined in: [src/api.ts:560](../src/api.ts#L560) +Defined in: [src/api.ts:563](../api.ts#L563) Add contact to group. Requires bot to have Admin role. Network usage: interactive. @@ -151,7 +151,7 @@ Network usage: interactive. > **apiAddMemberViaGroup**(`groupId`, `viaGroupId`, `groupMemberId`, `memberRole`): `Promise`\<`GroupMember`\> -Defined in: [src/api.ts:570](../src/api.ts#L570) +Defined in: [src/api.ts:573](../api.ts#L573) Add member of another group to group. Requires bot to have Admin role in the group, and direct messages allowed for its role in another group. Network usage: interactive. @@ -184,7 +184,7 @@ Network usage: interactive. > **apiBlockMembersForAll**(`groupId`, `groupMemberIds`, `blocked`): `Promise`\<`void`\> -Defined in: [src/api.ts:610](../src/api.ts#L610) +Defined in: [src/api.ts:613](../api.ts#L613) Block members. Requires Moderator role. Network usage: background. @@ -213,7 +213,7 @@ Network usage: background. > **apiCancelFile**(`fileId`): `Promise`\<`void`\> -Defined in: [src/api.ts:550](../src/api.ts#L550) +Defined in: [src/api.ts:553](../api.ts#L553) Cancel file. Network usage: background. @@ -234,7 +234,7 @@ Network usage: background. > **apiChatItemReaction**(`chatType`, `chatId`, `chatItemId`, `add`, `reaction`): `Promise`\<`ACIReaction`\> -Defined in: [src/api.ts:523](../src/api.ts#L523) +Defined in: [src/api.ts:526](../api.ts#L526) Add/remove message reaction. Network usage: background. @@ -271,7 +271,7 @@ Network usage: background. > **apiConnect**(`userId`, `incognito`, `preparedLink`): `Promise`\<[`ConnReqType`](api.Enumeration.ConnReqType.md)\> -Defined in: [src/api.ts:739](../src/api.ts#L739) +Defined in: [src/api.ts:742](../api.ts#L742) Connect via prepared SimpleX link. The link can be 1-time invitation link, contact address or group link Network usage: interactive. @@ -300,7 +300,7 @@ Network usage: interactive. > **apiConnectActiveUser**(`connLink`): `Promise`\<[`ConnReqType`](api.Enumeration.ConnReqType.md)\> -Defined in: [src/api.ts:748](../src/api.ts#L748) +Defined in: [src/api.ts:751](../api.ts#L751) Connect via SimpleX link as string in the active user profile. Network usage: interactive. @@ -321,7 +321,7 @@ Network usage: interactive. > **apiConnectPlan**(`userId`, `connectionLink`): `Promise`\<\[`ConnectionPlan`, `CreatedConnLink`\]\> -Defined in: [src/api.ts:729](../src/api.ts#L729) +Defined in: [src/api.ts:732](../api.ts#L732) Determine SimpleX link type and if the bot is already connected via this link. Network usage: interactive. @@ -346,7 +346,7 @@ Network usage: interactive. > **apiCreateActiveUser**(`profile?`): `Promise`\<`User`\> -Defined in: [src/api.ts:907](../src/api.ts#L907) +Defined in: [src/api.ts:910](../api.ts#L910) Create new user profile Network usage: no. @@ -367,7 +367,7 @@ Network usage: no. > **apiCreateGroupLink**(`groupId`, `memberRole`): `Promise`\<`string`\> -Defined in: [src/api.ts:670](../src/api.ts#L670) +Defined in: [src/api.ts:673](../api.ts#L673) Create group link. Network usage: interactive. @@ -392,7 +392,7 @@ Network usage: interactive. > **apiCreateLink**(`userId`): `Promise`\<`string`\> -Defined in: [src/api.ts:716](../src/api.ts#L716) +Defined in: [src/api.ts:719](../api.ts#L719) Create 1-time invitation link. Network usage: interactive. @@ -413,7 +413,7 @@ Network usage: interactive. > **apiCreateMemberContact**(`groupId`, `groupMemberId`): `Promise`\<`Contact`\> -Defined in: [src/api.ts:973](../src/api.ts#L973) +Defined in: [src/api.ts:976](../api.ts#L976) Create a direct message contact with a group member. Returns the created contact. @@ -439,7 +439,7 @@ Network usage: interactive. > **apiCreateUserAddress**(`userId`): `Promise`\<`CreatedConnLink`\> -Defined in: [src/api.ts:360](../src/api.ts#L360) +Defined in: [src/api.ts:362](../api.ts#L362) Create bot address. Network usage: interactive. @@ -460,7 +460,7 @@ Network usage: interactive. > **apiDeleteChat**(`chatType`, `chatId`, `deleteMode?`): `Promise`\<`void`\> -Defined in: [src/api.ts:829](../src/api.ts#L829) +Defined in: [src/api.ts:832](../api.ts#L832) Delete chat. Network usage: background. @@ -489,7 +489,7 @@ Network usage: background. > **apiDeleteChatItems**(`chatType`, `chatId`, `chatItemIds`, `deleteMode`): `Promise`\<`ChatItemDeletion`[]\> -Defined in: [src/api.ts:498](../src/api.ts#L498) +Defined in: [src/api.ts:501](../api.ts#L501) Delete message. Network usage: background. @@ -522,7 +522,7 @@ Network usage: background. > **apiDeleteGroupLink**(`groupId`): `Promise`\<`void`\> -Defined in: [src/api.ts:692](../src/api.ts#L692) +Defined in: [src/api.ts:695](../api.ts#L695) Delete group link. Network usage: background. @@ -543,7 +543,7 @@ Network usage: background. > **apiDeleteMemberChatItem**(`groupId`, `chatItemIds`): `Promise`\<`ChatItemDeletion`[]\> -Defined in: [src/api.ts:513](../src/api.ts#L513) +Defined in: [src/api.ts:516](../api.ts#L516) Moderate message. Requires Moderator role (and higher than message author's). Network usage: background. @@ -568,7 +568,7 @@ Network usage: background. > **apiDeleteUser**(`userId`, `delSMPQueues`, `viewPwd?`): `Promise`\<`void`\> -Defined in: [src/api.ts:937](../src/api.ts#L937) +Defined in: [src/api.ts:940](../api.ts#L940) Delete user profile. Network usage: background. @@ -597,7 +597,7 @@ Network usage: background. > **apiDeleteUserAddress**(`userId`): `Promise`\<`void`\> -Defined in: [src/api.ts:370](../src/api.ts#L370) +Defined in: [src/api.ts:372](../api.ts#L372) Deletes a user address. Network usage: background. @@ -618,7 +618,7 @@ Network usage: background. > **apiGetActiveUser**(): `Promise`\<`User` \| `undefined`\> -Defined in: [src/api.ts:887](../src/api.ts#L887) +Defined in: [src/api.ts:890](../api.ts#L890) Get active user profile Network usage: no. @@ -633,7 +633,7 @@ Network usage: no. > **apiGetChat**(`chatType`, `chatId`, `count`): `Promise`\<`any`\> -Defined in: [src/api.ts:877](../src/api.ts#L877) +Defined in: [src/api.ts:880](../api.ts#L880) Get chat items. Network usage: no. @@ -662,7 +662,7 @@ Network usage: no. > **apiGetChats**(`userId`, `pagination`, `query?`, `pendingConnections?`): `Promise`\<`AChat`[]\> -Defined in: [src/api.ts:814](../src/api.ts#L814) +Defined in: [src/api.ts:817](../api.ts#L817) Get chat previews (paginated). Network usage: no. @@ -699,7 +699,7 @@ on large databases. > **apiGetGroupLink**(`groupId`): `Promise`\<`GroupLink`\> -Defined in: [src/api.ts:701](../src/api.ts#L701) +Defined in: [src/api.ts:704](../api.ts#L704) Get group link. Network usage: no. @@ -720,7 +720,7 @@ Network usage: no. > **apiGetGroupLinkStr**(`groupId`): `Promise`\<`string`\> -Defined in: [src/api.ts:707](../src/api.ts#L707) +Defined in: [src/api.ts:710](../api.ts#L710) #### Parameters @@ -738,7 +738,7 @@ Defined in: [src/api.ts:707](../src/api.ts#L707) > **apiGetUserAddress**(`userId`): `Promise`\<`UserContactLink` \| `undefined`\> -Defined in: [src/api.ts:380](../src/api.ts#L380) +Defined in: [src/api.ts:382](../api.ts#L382) Get bot address and settings. Network usage: no. @@ -759,7 +759,7 @@ Network usage: no. > **apiJoinGroup**(`groupId`): `Promise`\<`GroupInfo`\> -Defined in: [src/api.ts:580](../src/api.ts#L580) +Defined in: [src/api.ts:583](../api.ts#L583) Join group. Network usage: interactive. @@ -780,7 +780,7 @@ Network usage: interactive. > **apiLeaveGroup**(`groupId`): `Promise`\<`GroupInfo`\> -Defined in: [src/api.ts:630](../src/api.ts#L630) +Defined in: [src/api.ts:633](../api.ts#L633) Leave group. Network usage: background. @@ -801,7 +801,7 @@ Network usage: background. > **apiListContacts**(`userId`): `Promise`\<`Contact`[]\> -Defined in: [src/api.ts:790](../src/api.ts#L790) +Defined in: [src/api.ts:793](../api.ts#L793) Get contacts. Network usage: no. @@ -822,7 +822,7 @@ Network usage: no. > **apiListGroups**(`userId`, `contactId?`, `search?`): `Promise`\<`GroupInfo`[]\> -Defined in: [src/api.ts:800](../src/api.ts#L800) +Defined in: [src/api.ts:803](../api.ts#L803) Get groups. Network usage: no. @@ -851,7 +851,7 @@ Network usage: no. > **apiListMembers**(`groupId`): `Promise`\<`GroupMember`[]\> -Defined in: [src/api.ts:640](../src/api.ts#L640) +Defined in: [src/api.ts:643](../api.ts#L643) Get group members. Network usage: no. @@ -872,7 +872,7 @@ Network usage: no. > **apiListUsers**(): `Promise`\<`UserInfo`[]\> -Defined in: [src/api.ts:917](../src/api.ts#L917) +Defined in: [src/api.ts:920](../api.ts#L920) Get all user profiles Network usage: no. @@ -887,7 +887,7 @@ Network usage: no. > **apiNewGroup**(`userId`, `groupProfile`): `Promise`\<`GroupInfo`\> -Defined in: [src/api.ts:650](../src/api.ts#L650) +Defined in: [src/api.ts:653](../api.ts#L653) Create group. Network usage: no. @@ -912,7 +912,7 @@ Network usage: no. > **apiReceiveFile**(`fileId`): `Promise`\<`AChatItem`\> -Defined in: [src/api.ts:539](../src/api.ts#L539) +Defined in: [src/api.ts:542](../api.ts#L542) Receive file. Network usage: no. @@ -933,7 +933,7 @@ Network usage: no. > **apiRejectContactRequest**(`contactReqId`): `Promise`\<`void`\> -Defined in: [src/api.ts:780](../src/api.ts#L780) +Defined in: [src/api.ts:783](../api.ts#L783) Reject contact request. The user who sent the request is **not notified**. Network usage: no. @@ -954,7 +954,7 @@ Network usage: no. > **apiRemoveMembers**(`groupId`, `memberIds`, `withMessages?`): `Promise`\<`GroupMember`[]\> -Defined in: [src/api.ts:620](../src/api.ts#L620) +Defined in: [src/api.ts:623](../api.ts#L623) Remove members. Requires Admin role. Network usage: background. @@ -983,7 +983,7 @@ Network usage: background. > **apiSendMemberContactInvitation**(`contactId`, `message?`): `Promise`\<`Contact`\> -Defined in: [src/api.ts:984](../src/api.ts#L984) +Defined in: [src/api.ts:987](../api.ts#L987) Send a direct message invitation to a group member contact. The contact must have been created with [apiCreateMemberContact](#apicreatemembercontact). @@ -1009,7 +1009,7 @@ Network usage: interactive. > **apiSendMessages**(`chat`, `messages`, `liveMessage?`): `Promise`\<`AChatItem`[]\> -Defined in: [src/api.ts:442](../src/api.ts#L442) +Defined in: [src/api.ts:444](../api.ts#L444) Send messages. Network usage: background. @@ -1038,7 +1038,7 @@ Network usage: background. > **apiSendTextMessage**(`chat`, `text`, `inReplyTo?`): `Promise`\<`AChatItem`[]\> -Defined in: [src/api.ts:465](../src/api.ts#L465) +Defined in: [src/api.ts:467](../api.ts#L467) Send text message. Network usage: background. @@ -1067,7 +1067,7 @@ Network usage: background. > **apiSendTextReply**(`chatItem`, `text`): `Promise`\<`AChatItem`[]\> -Defined in: [src/api.ts:473](../src/api.ts#L473) +Defined in: [src/api.ts:475](../api.ts#L475) Send text message in reply to received message. Network usage: background. @@ -1092,7 +1092,7 @@ Network usage: background. > **apiSetActiveUser**(`userId`, `viewPwd?`): `Promise`\<`User`\> -Defined in: [src/api.ts:927](../src/api.ts#L927) +Defined in: [src/api.ts:930](../api.ts#L930) Set active user profile Network usage: no. @@ -1117,7 +1117,7 @@ Network usage: no. > **apiSetAddressSettings**(`userId`, `__namedParameters`): `Promise`\<`void`\> -Defined in: [src/api.ts:414](../src/api.ts#L414) +Defined in: [src/api.ts:416](../api.ts#L416) Set bot address settings. Network usage: interactive. @@ -1142,7 +1142,7 @@ Network usage: interactive. > **apiSetAutoAcceptMemberContacts**(`userId`, `onOff`): `Promise`\<`void`\> -Defined in: [src/api.ts:866](../src/api.ts#L866) +Defined in: [src/api.ts:869](../api.ts#L869) Set auto-accept member contacts. Network usage: no. @@ -1167,7 +1167,7 @@ Network usage: no. > **apiSetContactCustomData**(`contactId`, `customData?`): `Promise`\<`void`\> -Defined in: [src/api.ts:856](../src/api.ts#L856) +Defined in: [src/api.ts:859](../api.ts#L859) Set contact custom data. Network usage: no. @@ -1192,7 +1192,7 @@ Network usage: no. > **apiSetContactPrefs**(`contactId`, `preferences`): `Promise`\<`void`\> -Defined in: [src/api.ts:963](../src/api.ts#L963) +Defined in: [src/api.ts:966](../api.ts#L966) Configure chat preference overrides for the contact. Network usage: background. @@ -1217,7 +1217,7 @@ Network usage: background. > **apiSetGroupCustomData**(`groupId`, `customData?`): `Promise`\<`void`\> -Defined in: [src/api.ts:846](../src/api.ts#L846) +Defined in: [src/api.ts:849](../api.ts#L849) Set group custom data. Network usage: no. @@ -1242,7 +1242,7 @@ Network usage: no. > **apiSetGroupLinkMemberRole**(`groupId`, `memberRole`): `Promise`\<`void`\> -Defined in: [src/api.ts:683](../src/api.ts#L683) +Defined in: [src/api.ts:686](../api.ts#L686) Set member role for group link. Network usage: no. @@ -1267,7 +1267,7 @@ Network usage: no. > **apiSetMembersRole**(`groupId`, `groupMemberIds`, `memberRole`): `Promise`\<`void`\> -Defined in: [src/api.ts:600](../src/api.ts#L600) +Defined in: [src/api.ts:603](../api.ts#L603) Set members role. Requires Admin role. Network usage: background. @@ -1296,7 +1296,7 @@ Network usage: background. > **apiSetProfileAddress**(`userId`, `enable`): `Promise`\<`UserProfileUpdateSummary`\> -Defined in: [src/api.ts:398](../src/api.ts#L398) +Defined in: [src/api.ts:400](../api.ts#L400) Add address to bot profile. Network usage: interactive. @@ -1321,7 +1321,7 @@ Network usage: interactive. > **apiSetUserDomain**(`userId`, `simplexDomain?`): `Promise`\<`User`\> -Defined in: [src/api.ts:427](../src/api.ts#L427) +Defined in: [src/api.ts:429](../api.ts#L429) #### Parameters @@ -1341,11 +1341,12 @@ Defined in: [src/api.ts:427](../src/api.ts#L427) ### apiUpdateChatItem() -> **apiUpdateChatItem**(`chatType`, `chatId`, `chatItemId`, `msgContent`, `liveMessage`): `Promise`\<`ChatItem`\> +> **apiUpdateChatItem**(`chatType`, `chatId`, `chatItemId`, `msgContent`, `liveMessage?`): `Promise`\<`ChatItem`\> -Defined in: [src/api.ts:481](../src/api.ts#L481) +Defined in: [src/api.ts:484](../api.ts#L484) Update message. +A live message stays live with `liveMessage = true` and is finalized with `liveMessage = false`. Network usage: background. #### Parameters @@ -1366,9 +1367,9 @@ Network usage: background. `MsgContent` -##### liveMessage +##### liveMessage? -`false` +`boolean` = `false` #### Returns @@ -1380,7 +1381,7 @@ Network usage: background. > **apiUpdateGroupProfile**(`groupId`, `groupProfile`): `Promise`\<`GroupInfo`\> -Defined in: [src/api.ts:660](../src/api.ts#L660) +Defined in: [src/api.ts:663](../api.ts#L663) Update group profile. Network usage: background. @@ -1405,7 +1406,7 @@ Network usage: background. > **apiUpdateProfile**(`userId`, `profile`): `Promise`\<`UserProfileUpdateSummary` \| `undefined`\> -Defined in: [src/api.ts:947](../src/api.ts#L947) +Defined in: [src/api.ts:950](../api.ts#L950) Update user profile. Network usage: background. @@ -1430,7 +1431,7 @@ Network usage: background. > **close**(): `Promise`\<`void`\> -Defined in: [src/api.ts:157](../src/api.ts#L157) +Defined in: [src/api.ts:162](../api.ts#L162) Stop chat controller and close chat database. The database is not closed if stopping fails. @@ -1446,7 +1447,7 @@ Usually doesn't need to be called in chat bots. > **off**\<`K`\>(`event`, `subscriber?`): `void` -Defined in: [src/api.ts:301](../src/api.ts#L301) +Defined in: [src/api.ts:303](../api.ts#L303) Unsubscribe all or a specific handler from a specific event. @@ -1466,7 +1467,7 @@ The event type to unsubscribe from. ##### subscriber? -[`EventSubscriberFunc`](api.TypeAlias.EventSubscriberFunc.md)\<`K`\> \| `undefined` +[`EventSubscriberFunc`](api.TypeAlias.EventSubscriberFunc.md)\<`K`\> \| `undefined` = `undefined` An optional subscriber function for the event. @@ -1480,7 +1481,7 @@ An optional subscriber function for the event. > **offAny**(`receiver?`): `void` -Defined in: [src/api.ts:317](../src/api.ts#L317) +Defined in: [src/api.ts:319](../api.ts#L319) Unsubscribe all or a specific handler from any events. @@ -1488,7 +1489,7 @@ Unsubscribe all or a specific handler from any events. ##### receiver? -[`EventSubscriberFunc`](api.TypeAlias.EventSubscriberFunc.md)\<`Tag`\> \| `undefined` +[`EventSubscriberFunc`](api.TypeAlias.EventSubscriberFunc.md)\<`Tag`\> \| `undefined` = `undefined` An optional subscriber function for the event. @@ -1504,7 +1505,7 @@ An optional subscriber function for the event. > **on**\<`K`\>(`subscribers`): `void` -Defined in: [src/api.ts:211](../src/api.ts#L211) +Defined in: [src/api.ts:211](../api.ts#L211) Subscribe multiple event handlers at once. @@ -1534,7 +1535,7 @@ If the same function is subscribed to event. > **on**\<`K`\>(`event`, `subscriber`): `void` -Defined in: [src/api.ts:219](../src/api.ts#L219) +Defined in: [src/api.ts:219](../api.ts#L219) Subscribe a handler to a specific event. @@ -1572,7 +1573,7 @@ If the same function is subscribed to event. > **onAny**(`receiver`): `void` -Defined in: [src/api.ts:242](../src/api.ts#L242) +Defined in: [src/api.ts:242](../api.ts#L242) Subscribe a handler to any event. @@ -1598,7 +1599,7 @@ If the same function is subscribed to event. > **once**\<`K`\>(`event`, `subscriber`): `void` -Defined in: [src/api.ts:253](../src/api.ts#L253) +Defined in: [src/api.ts:253](../api.ts#L253) Subscribe a handler to a specific event to be delivered one time. @@ -1636,7 +1637,7 @@ If the same function is subscribed to event. > **recvChatEvent**(`wait?`): `Promise`\<`ChatEvent` \| `undefined`\> -Defined in: [src/api.ts:352](../src/api.ts#L352) +Defined in: [src/api.ts:354](../api.ts#L354) #### Parameters @@ -1654,7 +1655,7 @@ Defined in: [src/api.ts:352](../src/api.ts#L352) > **sendChatCmd**(`cmd`): `Promise`\<`ChatResponse`\> -Defined in: [src/api.ts:348](../src/api.ts#L348) +Defined in: [src/api.ts:350](../api.ts#L350) #### Parameters @@ -1672,7 +1673,7 @@ Defined in: [src/api.ts:348](../src/api.ts#L348) > **startChat**(): `Promise`\<`void`\> -Defined in: [src/api.ts:123](../src/api.ts#L123) +Defined in: [src/api.ts:128](../api.ts#L128) Start chat controller. Must be called with the existing user profile. @@ -1686,7 +1687,7 @@ Start chat controller. Must be called with the existing user profile. > **stopChat**(): `Promise`\<`void`\> -Defined in: [src/api.ts:146](../src/api.ts#L146) +Defined in: [src/api.ts:151](../api.ts#L151) Stop chat controller. `close` calls it before closing the database. @@ -1704,7 +1705,7 @@ Usually doesn't need to be called in chat bots. > **wait**\<`K`\>(`event`): `Promise`\<`ChatEvent` & `object`\> -Defined in: [src/api.ts:261](../src/api.ts#L261) +Defined in: [src/api.ts:261](../api.ts#L261) Waits for specific event, with an optional predicate. Returns `undefined` on timeout if specified. @@ -1729,7 +1730,7 @@ Returns `undefined` on timeout if specified. > **wait**\<`K`\>(`event`, `predicate`): `Promise`\<`ChatEvent` & `object`\> -Defined in: [src/api.ts:262](../src/api.ts#L262) +Defined in: [src/api.ts:262](../api.ts#L262) Waits for specific event, with an optional predicate. Returns `undefined` on timeout if specified. @@ -1758,7 +1759,7 @@ Returns `undefined` on timeout if specified. > **wait**\<`K`\>(`event`, `timeout`): `Promise`\ -Defined in: [src/api.ts:263](../src/api.ts#L263) +Defined in: [src/api.ts:263](../api.ts#L263) Waits for specific event, with an optional predicate. Returns `undefined` on timeout if specified. @@ -1787,7 +1788,7 @@ Returns `undefined` on timeout if specified. > **wait**\<`K`\>(`event`, `predicate`, `timeout`): `Promise`\ -Defined in: [src/api.ts:264](../src/api.ts#L264) +Defined in: [src/api.ts:264](../api.ts#L264) Waits for specific event, with an optional predicate. Returns `undefined` on timeout if specified. @@ -1822,7 +1823,7 @@ Returns `undefined` on timeout if specified. > `static` **init**(`db`, `confirm?`, `queueSize?`): `Promise`\<`ChatApi`\> -Defined in: [src/api.ts:109](../src/api.ts#L109) +Defined in: [src/api.ts:114](../api.ts#L114) Initializes the ChatApi, loading libsimplex for `db.type` (downloaded on first use). diff --git a/packages/simplex-chat-nodejs/docs/api.Class.ChatCommandError.md b/packages/simplex-chat-nodejs/docs/api.Class.ChatCommandError.md index 5ec1d40540..5d6e0a50fb 100644 --- a/packages/simplex-chat-nodejs/docs/api.Class.ChatCommandError.md +++ b/packages/simplex-chat-nodejs/docs/api.Class.ChatCommandError.md @@ -6,7 +6,7 @@ # Class: ChatCommandError -Defined in: [src/api.ts:5](../src/api.ts#L5) +Defined in: [src/api.ts:5](../api.ts#L5) ## Extends @@ -18,7 +18,7 @@ Defined in: [src/api.ts:5](../src/api.ts#L5) > **new ChatCommandError**(`message`, `response`): `ChatCommandError` -Defined in: [src/api.ts:6](../src/api.ts#L6) +Defined in: [src/api.ts:6](../api.ts#L6) #### Parameters @@ -44,7 +44,7 @@ Defined in: [src/api.ts:6](../src/api.ts#L6) > **message**: `string` -Defined in: [src/api.ts:6](../src/api.ts#L6) +Defined in: [src/api.ts:6](../api.ts#L6) #### Inherited from @@ -56,7 +56,7 @@ Defined in: [src/api.ts:6](../src/api.ts#L6) > **name**: `string` -Defined in: [node\_modules/typescript/lib/lib.es5.d.ts:1076](../node_modules/typescript/lib/lib.es5.d.ts#L1076) +Defined in: [node\_modules/typescript/lib/lib.es5.d.ts:1076](../../node_modules/typescript/lib/lib.es5.d.ts#L1076) #### Inherited from @@ -68,7 +68,7 @@ Defined in: [node\_modules/typescript/lib/lib.es5.d.ts:1076](../node_modules/typ > **response**: `ChatResponse` -Defined in: [src/api.ts:6](../src/api.ts#L6) +Defined in: [src/api.ts:6](../api.ts#L6) *** @@ -76,7 +76,7 @@ Defined in: [src/api.ts:6](../src/api.ts#L6) > `optional` **stack?**: `string` -Defined in: [node\_modules/typescript/lib/lib.es5.d.ts:1078](../node_modules/typescript/lib/lib.es5.d.ts#L1078) +Defined in: [node\_modules/typescript/lib/lib.es5.d.ts:1078](../../node_modules/typescript/lib/lib.es5.d.ts#L1078) #### Inherited from @@ -88,7 +88,7 @@ Defined in: [node\_modules/typescript/lib/lib.es5.d.ts:1078](../node_modules/typ > `static` **stackTraceLimit**: `number` -Defined in: [node\_modules/@types/node/globals.d.ts:67](../node_modules/@types/node/globals.d.ts#L67) +Defined in: [node\_modules/@types/node/globals.d.ts:67](../../node_modules/@types/node/globals.d.ts#L67) The `Error.stackTraceLimit` property specifies the number of stack frames collected by a stack trace (whether generated by `new Error().stack` or @@ -110,7 +110,7 @@ not capture any frames. > `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void` -Defined in: [node\_modules/@types/node/globals.d.ts:51](../node_modules/@types/node/globals.d.ts#L51) +Defined in: [node\_modules/@types/node/globals.d.ts:51](../../node_modules/@types/node/globals.d.ts#L51) Creates a `.stack` property on `targetObject`, which when accessed returns a string representing the location in the code at which @@ -180,7 +180,7 @@ a(); > `static` **prepareStackTrace**(`err`, `stackTraces`): `any` -Defined in: [node\_modules/@types/node/globals.d.ts:55](../node_modules/@types/node/globals.d.ts#L55) +Defined in: [node\_modules/@types/node/globals.d.ts:55](../../node_modules/@types/node/globals.d.ts#L55) #### Parameters diff --git a/packages/simplex-chat-nodejs/docs/api.Class.LiveMessage.md b/packages/simplex-chat-nodejs/docs/api.Class.LiveMessage.md new file mode 100644 index 0000000000..9f7f57dd5c --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/api.Class.LiveMessage.md @@ -0,0 +1,98 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [api](Namespace.api.md) / LiveMessage + +# Class: LiveMessage + +Defined in: [src/api.ts:1005](../api.ts#L1005) + +Text shown as a live message while it is written, e.g. a streamed reply. + +## Constructors + +### Constructor + +> **new LiveMessage**(`chat`, `chatType`, `chatId`, `interval?`, `onError?`): `LiveMessage` + +Defined in: [src/api.ts:1019](../api.ts#L1019) + +#### Parameters + +##### chat + +`Pick`\<[`ChatApi`](api.Class.ChatApi.md), `"apiSendMessages"` \| `"apiUpdateChatItem"` \| `"apiDeleteChatItems"`\> + +Chat API, or an object with its methods to send, update and delete messages. + +##### chatType + +`ChatType` + +Chat type. + +##### chatId + +`number` + +Contact or group ID. + +##### interval? + +`number` = `3000` + +Minimum interval between updates of the live message, in milliseconds. + +##### onError? + +(`error`) => `void` + +Error handler for sending, updating and deleting messages. Errors are logged when it is omitted. + +#### Returns + +`LiveMessage` + +## Methods + +### end() + +> **end**(`text?`): `Promise`\<`void`\> + +Defined in: [src/api.ts:1049](../api.ts#L1049) + +Finalize the live message with the text, or with the last text when omitted. +A message without text is deleted. The next update starts a new live message. +The returned promise resolves after all queued commands complete. + +#### Parameters + +##### text? + +`string` = `...` + +#### Returns + +`Promise`\<`void`\> + +*** + +### update() + +> **update**(`text`): `void` + +Defined in: [src/api.ts:1031](../api.ts#L1031) + +Set the text of the live message. +The first text is sent at once; later texts update the message at most once per interval. + +#### Parameters + +##### text + +`string` + +#### Returns + +`void` diff --git a/packages/simplex-chat-nodejs/docs/api.Enumeration.ConnReqType.md b/packages/simplex-chat-nodejs/docs/api.Enumeration.ConnReqType.md index dcabb85b67..dc5fb0d63e 100644 --- a/packages/simplex-chat-nodejs/docs/api.Enumeration.ConnReqType.md +++ b/packages/simplex-chat-nodejs/docs/api.Enumeration.ConnReqType.md @@ -6,7 +6,7 @@ # Enumeration: ConnReqType -Defined in: [src/api.ts:15](../src/api.ts#L15) +Defined in: [src/api.ts:15](../api.ts#L15) Connection request types. @@ -16,7 +16,7 @@ Connection request types. > **Contact**: `"contact"` -Defined in: [src/api.ts:17](../src/api.ts#L17) +Defined in: [src/api.ts:17](../api.ts#L17) *** @@ -24,4 +24,4 @@ Defined in: [src/api.ts:17](../src/api.ts#L17) > **Invitation**: `"invitation"` -Defined in: [src/api.ts:16](../src/api.ts#L16) +Defined in: [src/api.ts:16](../api.ts#L16) diff --git a/packages/simplex-chat-nodejs/docs/api.Interface.BotAddressSettings.md b/packages/simplex-chat-nodejs/docs/api.Interface.BotAddressSettings.md index 23c5e35326..f0c9521c6b 100644 --- a/packages/simplex-chat-nodejs/docs/api.Interface.BotAddressSettings.md +++ b/packages/simplex-chat-nodejs/docs/api.Interface.BotAddressSettings.md @@ -6,7 +6,7 @@ # Interface: BotAddressSettings -Defined in: [src/api.ts:23](../src/api.ts#L23) +Defined in: [src/api.ts:23](../api.ts#L23) Bot address settings. @@ -16,7 +16,7 @@ Bot address settings. > `optional` **autoAccept?**: `boolean` -Defined in: [src/api.ts:28](../src/api.ts#L28) +Defined in: [src/api.ts:28](../api.ts#L28) Automatically accept contact requests. @@ -32,7 +32,7 @@ true > `optional` **businessAddress?**: `boolean` -Defined in: [src/api.ts:41](../src/api.ts#L41) +Defined in: [src/api.ts:41](../api.ts#L41) Business contact address. For all requests business chats will be created where other participants can be added. @@ -49,7 +49,7 @@ false > `optional` **welcomeMessage?**: `string` \| `MsgContent` -Defined in: [src/api.ts:34](../src/api.ts#L34) +Defined in: [src/api.ts:34](../api.ts#L34) Optional welcome message to show before connection to the users. diff --git a/packages/simplex-chat-nodejs/docs/api.TypeAlias.DbConfig.md b/packages/simplex-chat-nodejs/docs/api.TypeAlias.DbConfig.md index 26934a9c6b..114392d8ba 100644 --- a/packages/simplex-chat-nodejs/docs/api.TypeAlias.DbConfig.md +++ b/packages/simplex-chat-nodejs/docs/api.TypeAlias.DbConfig.md @@ -8,7 +8,7 @@ > **DbConfig** = \{ `encryptionKey?`: `string`; `filePrefix`: `string`; `type`: `"sqlite"`; \} \| \{ `connectionString`: `string`; `schemaPrefix?`: `string`; `type`: `"postgres"`; \} -Defined in: [src/api.ts:63](../src/api.ts#L63) +Defined in: [src/api.ts:63](../api.ts#L63) Database configuration. `type` selects the libsimplex backend loaded by `ChatApi.init`; one backend per process. diff --git a/packages/simplex-chat-nodejs/docs/api.TypeAlias.EventSubscriberFunc.md b/packages/simplex-chat-nodejs/docs/api.TypeAlias.EventSubscriberFunc.md index 13efcec7dd..103ee471e0 100644 --- a/packages/simplex-chat-nodejs/docs/api.TypeAlias.EventSubscriberFunc.md +++ b/packages/simplex-chat-nodejs/docs/api.TypeAlias.EventSubscriberFunc.md @@ -8,7 +8,7 @@ > **EventSubscriberFunc**\<`K`\> = (`event`, `chat`) => `void` \| `Promise`\<`void`\> -Defined in: [src/api.ts:50](../src/api.ts#L50) +Defined in: [src/api.ts:50](../api.ts#L50) ## Type Parameters diff --git a/packages/simplex-chat-nodejs/docs/api.TypeAlias.EventSubscribers.md b/packages/simplex-chat-nodejs/docs/api.TypeAlias.EventSubscribers.md index 3b63d11bd4..6f694c79aa 100644 --- a/packages/simplex-chat-nodejs/docs/api.TypeAlias.EventSubscribers.md +++ b/packages/simplex-chat-nodejs/docs/api.TypeAlias.EventSubscribers.md @@ -8,4 +8,4 @@ > **EventSubscribers** = `{ [K in CEvt.Tag]?: EventSubscriberFunc }` -Defined in: [src/api.ts:52](../src/api.ts#L52) +Defined in: [src/api.ts:52](../api.ts#L52) diff --git a/packages/simplex-chat-nodejs/docs/api.Variable.defaultBotAddressSettings.md b/packages/simplex-chat-nodejs/docs/api.Variable.defaultBotAddressSettings.md index f076ee0fad..29352adcfc 100644 --- a/packages/simplex-chat-nodejs/docs/api.Variable.defaultBotAddressSettings.md +++ b/packages/simplex-chat-nodejs/docs/api.Variable.defaultBotAddressSettings.md @@ -8,4 +8,4 @@ > `const` **defaultBotAddressSettings**: [`BotAddressSettings`](api.Interface.BotAddressSettings.md) -Defined in: [src/api.ts:44](../src/api.ts#L44) +Defined in: [src/api.ts:44](../api.ts#L44) diff --git a/packages/simplex-chat-nodejs/docs/bot.Function.run.md b/packages/simplex-chat-nodejs/docs/bot.Function.run.md index bc31ad01a8..3a9ab5feb4 100644 --- a/packages/simplex-chat-nodejs/docs/bot.Function.run.md +++ b/packages/simplex-chat-nodejs/docs/bot.Function.run.md @@ -8,7 +8,7 @@ > **run**(`__namedParameters`): `Promise`\<\[[`ChatApi`](api.Class.ChatApi.md), `User`, `UserContactLink` \| `undefined`\]\> -Defined in: [src/bot.ts:49](../src/bot.ts#L49) +Defined in: [src/bot.ts:49](../bot.ts#L49) ## Parameters diff --git a/packages/simplex-chat-nodejs/docs/bot.Function.subscribeChatItems.md b/packages/simplex-chat-nodejs/docs/bot.Function.subscribeChatItems.md index 0bcc49cbc6..c66aa388ac 100644 --- a/packages/simplex-chat-nodejs/docs/bot.Function.subscribeChatItems.md +++ b/packages/simplex-chat-nodejs/docs/bot.Function.subscribeChatItems.md @@ -8,7 +8,7 @@ > **subscribeChatItems**(`bot`, `onMessage`, `commands`): `void` -Defined in: [src/bot.ts:110](../src/bot.ts#L110) +Defined in: [src/bot.ts:110](../bot.ts#L110) ## Parameters @@ -22,6 +22,8 @@ Defined in: [src/bot.ts:110](../src/bot.ts#L110) ### commands +\{\[`key`: `string`\]: ((`chatItem`, `command`, `chat`) => `void` \| `Promise`\<`void`\>) \| `undefined`; \} + ## Returns `void` diff --git a/packages/simplex-chat-nodejs/docs/bot.Interface.BotConfig.md b/packages/simplex-chat-nodejs/docs/bot.Interface.BotConfig.md index 036afe64d4..ddb2106627 100644 --- a/packages/simplex-chat-nodejs/docs/bot.Interface.BotConfig.md +++ b/packages/simplex-chat-nodejs/docs/bot.Interface.BotConfig.md @@ -6,7 +6,7 @@ # Interface: BotConfig -Defined in: [src/bot.ts:36](../src/bot.ts#L36) +Defined in: [src/bot.ts:36](../bot.ts#L36) ## Properties @@ -14,7 +14,7 @@ Defined in: [src/bot.ts:36](../src/bot.ts#L36) > **dbOpts**: [`BotDbOpts`](bot.TypeAlias.BotDbOpts.md) -Defined in: [src/bot.ts:39](../src/bot.ts#L39) +Defined in: [src/bot.ts:39](../bot.ts#L39) *** @@ -22,7 +22,7 @@ Defined in: [src/bot.ts:39](../src/bot.ts#L39) > `optional` **events?**: [`EventSubscribers`](api.TypeAlias.EventSubscribers.md) -Defined in: [src/bot.ts:46](../src/bot.ts#L46) +Defined in: [src/bot.ts:46](../bot.ts#L46) *** @@ -30,7 +30,7 @@ Defined in: [src/bot.ts:46](../src/bot.ts#L46) > `optional` **onCommands?**: `object` -Defined in: [src/bot.ts:43](../src/bot.ts#L43) +Defined in: [src/bot.ts:43](../bot.ts#L43) #### Index Signature @@ -42,7 +42,7 @@ Defined in: [src/bot.ts:43](../src/bot.ts#L43) > `optional` **onMessage?**: (`chatItem`, `content`, `chat`) => `void` \| `Promise`\<`void`\> -Defined in: [src/bot.ts:41](../src/bot.ts#L41) +Defined in: [src/bot.ts:41](../bot.ts#L41) #### Parameters @@ -68,7 +68,7 @@ Defined in: [src/bot.ts:41](../src/bot.ts#L41) > **options**: [`BotOptions`](bot.Interface.BotOptions.md) -Defined in: [src/bot.ts:40](../src/bot.ts#L40) +Defined in: [src/bot.ts:40](../bot.ts#L40) *** @@ -76,7 +76,7 @@ Defined in: [src/bot.ts:40](../src/bot.ts#L40) > **profile**: `Profile` -Defined in: [src/bot.ts:37](../src/bot.ts#L37) +Defined in: [src/bot.ts:37](../bot.ts#L37) *** @@ -84,4 +84,4 @@ Defined in: [src/bot.ts:37](../src/bot.ts#L37) > `optional` **simplexDomain?**: `string` \| `null` -Defined in: [src/bot.ts:38](../src/bot.ts#L38) +Defined in: [src/bot.ts:38](../bot.ts#L38) diff --git a/packages/simplex-chat-nodejs/docs/bot.Interface.BotOptions.md b/packages/simplex-chat-nodejs/docs/bot.Interface.BotOptions.md index c5d5df5193..74e5fd6295 100644 --- a/packages/simplex-chat-nodejs/docs/bot.Interface.BotOptions.md +++ b/packages/simplex-chat-nodejs/docs/bot.Interface.BotOptions.md @@ -6,7 +6,7 @@ # Interface: BotOptions -Defined in: [src/bot.ts:12](../src/bot.ts#L12) +Defined in: [src/bot.ts:12](../bot.ts#L12) ## Properties @@ -14,7 +14,7 @@ Defined in: [src/bot.ts:12](../src/bot.ts#L12) > `optional` **addressSettings?**: [`BotAddressSettings`](api.Interface.BotAddressSettings.md) -Defined in: [src/bot.ts:16](../src/bot.ts#L16) +Defined in: [src/bot.ts:16](../bot.ts#L16) *** @@ -22,7 +22,7 @@ Defined in: [src/bot.ts:16](../src/bot.ts#L16) > `optional` **allowFiles?**: `boolean` -Defined in: [src/bot.ts:17](../src/bot.ts#L17) +Defined in: [src/bot.ts:17](../bot.ts#L17) *** @@ -30,7 +30,7 @@ Defined in: [src/bot.ts:17](../src/bot.ts#L17) > `optional` **commands?**: `ChatBotCommand`[] -Defined in: [src/bot.ts:18](../src/bot.ts#L18) +Defined in: [src/bot.ts:18](../bot.ts#L18) *** @@ -38,7 +38,7 @@ Defined in: [src/bot.ts:18](../src/bot.ts#L18) > `optional` **createAddress?**: `boolean` -Defined in: [src/bot.ts:13](../src/bot.ts#L13) +Defined in: [src/bot.ts:13](../bot.ts#L13) *** @@ -46,7 +46,7 @@ Defined in: [src/bot.ts:13](../src/bot.ts#L13) > `optional` **logContacts?**: `boolean` -Defined in: [src/bot.ts:20](../src/bot.ts#L20) +Defined in: [src/bot.ts:20](../bot.ts#L20) *** @@ -54,7 +54,7 @@ Defined in: [src/bot.ts:20](../src/bot.ts#L20) > `optional` **logNetwork?**: `boolean` -Defined in: [src/bot.ts:21](../src/bot.ts#L21) +Defined in: [src/bot.ts:21](../bot.ts#L21) *** @@ -62,7 +62,7 @@ Defined in: [src/bot.ts:21](../src/bot.ts#L21) > `optional` **updateAddress?**: `boolean` -Defined in: [src/bot.ts:14](../src/bot.ts#L14) +Defined in: [src/bot.ts:14](../bot.ts#L14) *** @@ -70,7 +70,7 @@ Defined in: [src/bot.ts:14](../src/bot.ts#L14) > `optional` **updateProfile?**: `boolean` -Defined in: [src/bot.ts:15](../src/bot.ts#L15) +Defined in: [src/bot.ts:15](../bot.ts#L15) *** @@ -78,4 +78,4 @@ Defined in: [src/bot.ts:15](../src/bot.ts#L15) > `optional` **useBotProfile?**: `boolean` -Defined in: [src/bot.ts:19](../src/bot.ts#L19) +Defined in: [src/bot.ts:19](../bot.ts#L19) diff --git a/packages/simplex-chat-nodejs/docs/bot.TypeAlias.BotDbOpts.md b/packages/simplex-chat-nodejs/docs/bot.TypeAlias.BotDbOpts.md index 5aeb298a09..d616c5a0f2 100644 --- a/packages/simplex-chat-nodejs/docs/bot.TypeAlias.BotDbOpts.md +++ b/packages/simplex-chat-nodejs/docs/bot.TypeAlias.BotDbOpts.md @@ -8,7 +8,7 @@ > **BotDbOpts** = [`DbConfig`](api.TypeAlias.DbConfig.md) & `object` -Defined in: [src/bot.ts:7](../src/bot.ts#L7) +Defined in: [src/bot.ts:7](../bot.ts#L7) ## Type Declaration diff --git a/packages/simplex-chat-nodejs/docs/core.Class.ChatAPIError.md b/packages/simplex-chat-nodejs/docs/core.Class.ChatAPIError.md index 64e3e764df..19e6343198 100644 --- a/packages/simplex-chat-nodejs/docs/core.Class.ChatAPIError.md +++ b/packages/simplex-chat-nodejs/docs/core.Class.ChatAPIError.md @@ -6,7 +6,7 @@ # Class: ChatAPIError -Defined in: [src/core.ts:117](../src/core.ts#L117) +Defined in: [src/core.ts:117](../core.ts#L117) ## Extends @@ -18,7 +18,7 @@ Defined in: [src/core.ts:117](../src/core.ts#L117) > **new ChatAPIError**(`message`, `chatError?`): `ChatAPIError` -Defined in: [src/core.ts:118](../src/core.ts#L118) +Defined in: [src/core.ts:118](../core.ts#L118) #### Parameters @@ -28,7 +28,7 @@ Defined in: [src/core.ts:118](../src/core.ts#L118) ##### chatError? -`ChatError` \| `undefined` +`ChatError` \| `undefined` = `undefined` #### Returns @@ -44,7 +44,7 @@ Defined in: [src/core.ts:118](../src/core.ts#L118) > **chatError**: `ChatError` \| `undefined` = `undefined` -Defined in: [src/core.ts:118](../src/core.ts#L118) +Defined in: [src/core.ts:118](../core.ts#L118) *** @@ -52,7 +52,7 @@ Defined in: [src/core.ts:118](../src/core.ts#L118) > **message**: `string` -Defined in: [src/core.ts:118](../src/core.ts#L118) +Defined in: [src/core.ts:118](../core.ts#L118) #### Inherited from @@ -64,7 +64,7 @@ Defined in: [src/core.ts:118](../src/core.ts#L118) > **name**: `string` -Defined in: [node\_modules/typescript/lib/lib.es5.d.ts:1076](../node_modules/typescript/lib/lib.es5.d.ts#L1076) +Defined in: [node\_modules/typescript/lib/lib.es5.d.ts:1076](../../node_modules/typescript/lib/lib.es5.d.ts#L1076) #### Inherited from @@ -76,7 +76,7 @@ Defined in: [node\_modules/typescript/lib/lib.es5.d.ts:1076](../node_modules/typ > `optional` **stack?**: `string` -Defined in: [node\_modules/typescript/lib/lib.es5.d.ts:1078](../node_modules/typescript/lib/lib.es5.d.ts#L1078) +Defined in: [node\_modules/typescript/lib/lib.es5.d.ts:1078](../../node_modules/typescript/lib/lib.es5.d.ts#L1078) #### Inherited from @@ -88,7 +88,7 @@ Defined in: [node\_modules/typescript/lib/lib.es5.d.ts:1078](../node_modules/typ > `static` **stackTraceLimit**: `number` -Defined in: [node\_modules/@types/node/globals.d.ts:67](../node_modules/@types/node/globals.d.ts#L67) +Defined in: [node\_modules/@types/node/globals.d.ts:67](../../node_modules/@types/node/globals.d.ts#L67) The `Error.stackTraceLimit` property specifies the number of stack frames collected by a stack trace (whether generated by `new Error().stack` or @@ -110,7 +110,7 @@ not capture any frames. > `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void` -Defined in: [node\_modules/@types/node/globals.d.ts:51](../node_modules/@types/node/globals.d.ts#L51) +Defined in: [node\_modules/@types/node/globals.d.ts:51](../../node_modules/@types/node/globals.d.ts#L51) Creates a `.stack` property on `targetObject`, which when accessed returns a string representing the location in the code at which @@ -180,7 +180,7 @@ a(); > `static` **prepareStackTrace**(`err`, `stackTraces`): `any` -Defined in: [node\_modules/@types/node/globals.d.ts:55](../node_modules/@types/node/globals.d.ts#L55) +Defined in: [node\_modules/@types/node/globals.d.ts:55](../../node_modules/@types/node/globals.d.ts#L55) #### Parameters diff --git a/packages/simplex-chat-nodejs/docs/core.Class.ChatInitError.md b/packages/simplex-chat-nodejs/docs/core.Class.ChatInitError.md index aefc4b9073..22fc7607cb 100644 --- a/packages/simplex-chat-nodejs/docs/core.Class.ChatInitError.md +++ b/packages/simplex-chat-nodejs/docs/core.Class.ChatInitError.md @@ -6,7 +6,7 @@ # Class: ChatInitError -Defined in: [src/core.ts:141](../src/core.ts#L141) +Defined in: [src/core.ts:141](../core.ts#L141) ## Extends @@ -18,7 +18,7 @@ Defined in: [src/core.ts:141](../src/core.ts#L141) > **new ChatInitError**(`message`, `dbMigrationError`): `ChatInitError` -Defined in: [src/core.ts:142](../src/core.ts#L142) +Defined in: [src/core.ts:142](../core.ts#L142) #### Parameters @@ -44,7 +44,7 @@ Defined in: [src/core.ts:142](../src/core.ts#L142) > **dbMigrationError**: [`DBMigrationError`](core.TypeAlias.DBMigrationError.md) -Defined in: [src/core.ts:142](../src/core.ts#L142) +Defined in: [src/core.ts:142](../core.ts#L142) *** @@ -52,7 +52,7 @@ Defined in: [src/core.ts:142](../src/core.ts#L142) > **message**: `string` -Defined in: [src/core.ts:142](../src/core.ts#L142) +Defined in: [src/core.ts:142](../core.ts#L142) #### Inherited from @@ -64,7 +64,7 @@ Defined in: [src/core.ts:142](../src/core.ts#L142) > **name**: `string` -Defined in: [node\_modules/typescript/lib/lib.es5.d.ts:1076](../node_modules/typescript/lib/lib.es5.d.ts#L1076) +Defined in: [node\_modules/typescript/lib/lib.es5.d.ts:1076](../../node_modules/typescript/lib/lib.es5.d.ts#L1076) #### Inherited from @@ -76,7 +76,7 @@ Defined in: [node\_modules/typescript/lib/lib.es5.d.ts:1076](../node_modules/typ > `optional` **stack?**: `string` -Defined in: [node\_modules/typescript/lib/lib.es5.d.ts:1078](../node_modules/typescript/lib/lib.es5.d.ts#L1078) +Defined in: [node\_modules/typescript/lib/lib.es5.d.ts:1078](../../node_modules/typescript/lib/lib.es5.d.ts#L1078) #### Inherited from @@ -88,7 +88,7 @@ Defined in: [node\_modules/typescript/lib/lib.es5.d.ts:1078](../node_modules/typ > `static` **stackTraceLimit**: `number` -Defined in: [node\_modules/@types/node/globals.d.ts:67](../node_modules/@types/node/globals.d.ts#L67) +Defined in: [node\_modules/@types/node/globals.d.ts:67](../../node_modules/@types/node/globals.d.ts#L67) The `Error.stackTraceLimit` property specifies the number of stack frames collected by a stack trace (whether generated by `new Error().stack` or @@ -110,7 +110,7 @@ not capture any frames. > `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void` -Defined in: [node\_modules/@types/node/globals.d.ts:51](../node_modules/@types/node/globals.d.ts#L51) +Defined in: [node\_modules/@types/node/globals.d.ts:51](../../node_modules/@types/node/globals.d.ts#L51) Creates a `.stack` property on `targetObject`, which when accessed returns a string representing the location in the code at which @@ -180,7 +180,7 @@ a(); > `static` **prepareStackTrace**(`err`, `stackTraces`): `any` -Defined in: [node\_modules/@types/node/globals.d.ts:55](../node_modules/@types/node/globals.d.ts#L55) +Defined in: [node\_modules/@types/node/globals.d.ts:55](../../node_modules/@types/node/globals.d.ts#L55) #### Parameters diff --git a/packages/simplex-chat-nodejs/docs/core.DBMigrationError.Interface.ErrorMigration.md b/packages/simplex-chat-nodejs/docs/core.DBMigrationError.Interface.ErrorMigration.md index 9b6ad3c099..d894205865 100644 --- a/packages/simplex-chat-nodejs/docs/core.DBMigrationError.Interface.ErrorMigration.md +++ b/packages/simplex-chat-nodejs/docs/core.DBMigrationError.Interface.ErrorMigration.md @@ -6,7 +6,7 @@ # Interface: ErrorMigration -Defined in: [src/core.ts:174](../src/core.ts#L174) +Defined in: [src/core.ts:174](../core.ts#L174) ## Extends @@ -18,7 +18,7 @@ Defined in: [src/core.ts:174](../src/core.ts#L174) > **dbFile**: `string` -Defined in: [src/core.ts:176](../src/core.ts#L176) +Defined in: [src/core.ts:176](../core.ts#L176) *** @@ -26,7 +26,7 @@ Defined in: [src/core.ts:176](../src/core.ts#L176) > **migrationError**: [`MigrationError`](core.TypeAlias.MigrationError.md) -Defined in: [src/core.ts:177](../src/core.ts#L177) +Defined in: [src/core.ts:177](../core.ts#L177) *** @@ -34,7 +34,7 @@ Defined in: [src/core.ts:177](../src/core.ts#L177) > **type**: `"errorMigration"` -Defined in: [src/core.ts:175](../src/core.ts#L175) +Defined in: [src/core.ts:175](../core.ts#L175) #### Overrides diff --git a/packages/simplex-chat-nodejs/docs/core.DBMigrationError.Interface.ErrorNotADatabase.md b/packages/simplex-chat-nodejs/docs/core.DBMigrationError.Interface.ErrorNotADatabase.md index d54f59003f..9944389b3b 100644 --- a/packages/simplex-chat-nodejs/docs/core.DBMigrationError.Interface.ErrorNotADatabase.md +++ b/packages/simplex-chat-nodejs/docs/core.DBMigrationError.Interface.ErrorNotADatabase.md @@ -6,7 +6,7 @@ # Interface: ErrorNotADatabase -Defined in: [src/core.ts:169](../src/core.ts#L169) +Defined in: [src/core.ts:169](../core.ts#L169) ## Extends @@ -18,7 +18,7 @@ Defined in: [src/core.ts:169](../src/core.ts#L169) > **dbFile**: `string` -Defined in: [src/core.ts:171](../src/core.ts#L171) +Defined in: [src/core.ts:171](../core.ts#L171) *** @@ -26,7 +26,7 @@ Defined in: [src/core.ts:171](../src/core.ts#L171) > **type**: `"errorNotADatabase"` -Defined in: [src/core.ts:170](../src/core.ts#L170) +Defined in: [src/core.ts:170](../core.ts#L170) #### Overrides diff --git a/packages/simplex-chat-nodejs/docs/core.DBMigrationError.Interface.ErrorSQL.md b/packages/simplex-chat-nodejs/docs/core.DBMigrationError.Interface.ErrorSQL.md index e76de932f6..108e5e226a 100644 --- a/packages/simplex-chat-nodejs/docs/core.DBMigrationError.Interface.ErrorSQL.md +++ b/packages/simplex-chat-nodejs/docs/core.DBMigrationError.Interface.ErrorSQL.md @@ -6,7 +6,7 @@ # Interface: ErrorSQL -Defined in: [src/core.ts:180](../src/core.ts#L180) +Defined in: [src/core.ts:180](../core.ts#L180) ## Extends @@ -18,7 +18,7 @@ Defined in: [src/core.ts:180](../src/core.ts#L180) > **dbFile**: `string` -Defined in: [src/core.ts:182](../src/core.ts#L182) +Defined in: [src/core.ts:182](../core.ts#L182) *** @@ -26,7 +26,7 @@ Defined in: [src/core.ts:182](../src/core.ts#L182) > **migrationSQLError**: `string` -Defined in: [src/core.ts:183](../src/core.ts#L183) +Defined in: [src/core.ts:183](../core.ts#L183) *** @@ -34,7 +34,7 @@ Defined in: [src/core.ts:183](../src/core.ts#L183) > **type**: `"errorSQL"` -Defined in: [src/core.ts:181](../src/core.ts#L181) +Defined in: [src/core.ts:181](../core.ts#L181) #### Overrides diff --git a/packages/simplex-chat-nodejs/docs/core.DBMigrationError.Interface.InvalidConfirmation.md b/packages/simplex-chat-nodejs/docs/core.DBMigrationError.Interface.InvalidConfirmation.md index 30157c808d..a1abcdf993 100644 --- a/packages/simplex-chat-nodejs/docs/core.DBMigrationError.Interface.InvalidConfirmation.md +++ b/packages/simplex-chat-nodejs/docs/core.DBMigrationError.Interface.InvalidConfirmation.md @@ -6,7 +6,7 @@ # Interface: InvalidConfirmation -Defined in: [src/core.ts:161](../src/core.ts#L161) +Defined in: [src/core.ts:161](../core.ts#L161) ## Extends @@ -18,7 +18,7 @@ Defined in: [src/core.ts:161](../src/core.ts#L161) > **type**: `"invalidConfirmation"` -Defined in: [src/core.ts:162](../src/core.ts#L162) +Defined in: [src/core.ts:162](../core.ts#L162) #### Overrides diff --git a/packages/simplex-chat-nodejs/docs/core.DBMigrationError.Interface.InvalidQueueSize.md b/packages/simplex-chat-nodejs/docs/core.DBMigrationError.Interface.InvalidQueueSize.md index 5264e4977f..f59284b129 100644 --- a/packages/simplex-chat-nodejs/docs/core.DBMigrationError.Interface.InvalidQueueSize.md +++ b/packages/simplex-chat-nodejs/docs/core.DBMigrationError.Interface.InvalidQueueSize.md @@ -6,7 +6,7 @@ # Interface: InvalidQueueSize -Defined in: [src/core.ts:165](../src/core.ts#L165) +Defined in: [src/core.ts:165](../core.ts#L165) ## Extends @@ -18,7 +18,7 @@ Defined in: [src/core.ts:165](../src/core.ts#L165) > **type**: `"invalidQueueSize"` -Defined in: [src/core.ts:166](../src/core.ts#L166) +Defined in: [src/core.ts:166](../core.ts#L166) #### Overrides diff --git a/packages/simplex-chat-nodejs/docs/core.DBMigrationError.TypeAlias.Tag.md b/packages/simplex-chat-nodejs/docs/core.DBMigrationError.TypeAlias.Tag.md index d94d5893ad..c83f29a130 100644 --- a/packages/simplex-chat-nodejs/docs/core.DBMigrationError.TypeAlias.Tag.md +++ b/packages/simplex-chat-nodejs/docs/core.DBMigrationError.TypeAlias.Tag.md @@ -8,4 +8,4 @@ > **Tag** = `"invalidConfirmation"` \| `"invalidQueueSize"` \| `"errorNotADatabase"` \| `"errorMigration"` \| `"errorSQL"` -Defined in: [src/core.ts:155](../src/core.ts#L155) +Defined in: [src/core.ts:155](../core.ts#L155) diff --git a/packages/simplex-chat-nodejs/docs/core.Enumeration.MigrationConfirmation.md b/packages/simplex-chat-nodejs/docs/core.Enumeration.MigrationConfirmation.md index cc2dea3901..a0df879c4c 100644 --- a/packages/simplex-chat-nodejs/docs/core.Enumeration.MigrationConfirmation.md +++ b/packages/simplex-chat-nodejs/docs/core.Enumeration.MigrationConfirmation.md @@ -6,7 +6,7 @@ # Enumeration: MigrationConfirmation -Defined in: [src/core.ts:126](../src/core.ts#L126) +Defined in: [src/core.ts:126](../core.ts#L126) Migration confirmation mode @@ -16,7 +16,7 @@ Migration confirmation mode > **Console**: `"console"` -Defined in: [src/core.ts:129](../src/core.ts#L129) +Defined in: [src/core.ts:129](../core.ts#L129) *** @@ -24,7 +24,7 @@ Defined in: [src/core.ts:129](../src/core.ts#L129) > **Error**: `"error"` -Defined in: [src/core.ts:130](../src/core.ts#L130) +Defined in: [src/core.ts:130](../core.ts#L130) *** @@ -32,7 +32,7 @@ Defined in: [src/core.ts:130](../src/core.ts#L130) > **YesUp**: `"yesUp"` -Defined in: [src/core.ts:127](../src/core.ts#L127) +Defined in: [src/core.ts:127](../core.ts#L127) *** @@ -40,4 +40,4 @@ Defined in: [src/core.ts:127](../src/core.ts#L127) > **YesUpDown**: `"yesUpDown"` -Defined in: [src/core.ts:128](../src/core.ts#L128) +Defined in: [src/core.ts:128](../core.ts#L128) diff --git a/packages/simplex-chat-nodejs/docs/core.Function.chatCloseStore.md b/packages/simplex-chat-nodejs/docs/core.Function.chatCloseStore.md index 69be845ed3..8902ba5653 100644 --- a/packages/simplex-chat-nodejs/docs/core.Function.chatCloseStore.md +++ b/packages/simplex-chat-nodejs/docs/core.Function.chatCloseStore.md @@ -8,7 +8,7 @@ > **chatCloseStore**(`ctrl`): `Promise`\<`void`\> -Defined in: [src/core.ts:42](../src/core.ts#L42) +Defined in: [src/core.ts:42](../core.ts#L42) Close chat store diff --git a/packages/simplex-chat-nodejs/docs/core.Function.chatDecryptFile.md b/packages/simplex-chat-nodejs/docs/core.Function.chatDecryptFile.md index a2a901ddc5..6d1a43d5a6 100644 --- a/packages/simplex-chat-nodejs/docs/core.Function.chatDecryptFile.md +++ b/packages/simplex-chat-nodejs/docs/core.Function.chatDecryptFile.md @@ -8,7 +8,7 @@ > **chatDecryptFile**(`fromPath`, `__namedParameters`, `toPath`): `Promise`\<`void`\> -Defined in: [src/core.ts:98](../src/core.ts#L98) +Defined in: [src/core.ts:98](../core.ts#L98) Decrypt file diff --git a/packages/simplex-chat-nodejs/docs/core.Function.chatEncryptFile.md b/packages/simplex-chat-nodejs/docs/core.Function.chatEncryptFile.md index 8f4c7815c4..b99ff277e0 100644 --- a/packages/simplex-chat-nodejs/docs/core.Function.chatEncryptFile.md +++ b/packages/simplex-chat-nodejs/docs/core.Function.chatEncryptFile.md @@ -8,7 +8,7 @@ > **chatEncryptFile**(`ctrl`, `fromPath`, `toPath`): `Promise`\<[`CryptoArgs`](core.Interface.CryptoArgs.md)\> -Defined in: [src/core.ts:90](../src/core.ts#L90) +Defined in: [src/core.ts:90](../core.ts#L90) Encrypt file diff --git a/packages/simplex-chat-nodejs/docs/core.Function.chatMigrateInit.md b/packages/simplex-chat-nodejs/docs/core.Function.chatMigrateInit.md index c6ad146aa1..1a12285c7a 100644 --- a/packages/simplex-chat-nodejs/docs/core.Function.chatMigrateInit.md +++ b/packages/simplex-chat-nodejs/docs/core.Function.chatMigrateInit.md @@ -8,7 +8,7 @@ > **chatMigrateInit**(`dbPath`, `dbKey`, `confirm`, `queueSize?`): `Promise`\<`bigint`\> -Defined in: [src/core.ts:30](../src/core.ts#L30) +Defined in: [src/core.ts:30](../core.ts#L30) Initialize chat controller diff --git a/packages/simplex-chat-nodejs/docs/core.Function.chatReadFile.md b/packages/simplex-chat-nodejs/docs/core.Function.chatReadFile.md index f9413c4d74..b94cb8b3f9 100644 --- a/packages/simplex-chat-nodejs/docs/core.Function.chatReadFile.md +++ b/packages/simplex-chat-nodejs/docs/core.Function.chatReadFile.md @@ -8,7 +8,7 @@ > **chatReadFile**(`path`, `__namedParameters`): `Promise`\<`Buffer`\<`ArrayBufferLike`\>\> -Defined in: [src/core.ts:83](../src/core.ts#L83) +Defined in: [src/core.ts:83](../core.ts#L83) Read buffer from encrypted file diff --git a/packages/simplex-chat-nodejs/docs/core.Function.chatRecvMsgWait.md b/packages/simplex-chat-nodejs/docs/core.Function.chatRecvMsgWait.md index 8aaa2e1590..40cfb9d182 100644 --- a/packages/simplex-chat-nodejs/docs/core.Function.chatRecvMsgWait.md +++ b/packages/simplex-chat-nodejs/docs/core.Function.chatRecvMsgWait.md @@ -8,7 +8,7 @@ > **chatRecvMsgWait**(`ctrl`, `wait`): `Promise`\<`ChatEvent` \| `undefined`\> -Defined in: [src/core.ts:62](../src/core.ts#L62) +Defined in: [src/core.ts:62](../core.ts#L62) Receive chat event diff --git a/packages/simplex-chat-nodejs/docs/core.Function.chatSendCmd.md b/packages/simplex-chat-nodejs/docs/core.Function.chatSendCmd.md index 9f8c34be0f..a86cdd23d5 100644 --- a/packages/simplex-chat-nodejs/docs/core.Function.chatSendCmd.md +++ b/packages/simplex-chat-nodejs/docs/core.Function.chatSendCmd.md @@ -8,7 +8,7 @@ > **chatSendCmd**(`ctrl`, `cmd`): `Promise`\<`ChatResponse`\> -Defined in: [src/core.ts:50](../src/core.ts#L50) +Defined in: [src/core.ts:50](../core.ts#L50) Send chat command as string diff --git a/packages/simplex-chat-nodejs/docs/core.Function.chatWriteFile.md b/packages/simplex-chat-nodejs/docs/core.Function.chatWriteFile.md index bd22e9d2c6..327f605347 100644 --- a/packages/simplex-chat-nodejs/docs/core.Function.chatWriteFile.md +++ b/packages/simplex-chat-nodejs/docs/core.Function.chatWriteFile.md @@ -8,7 +8,7 @@ > **chatWriteFile**(`ctrl`, `path`, `buffer`): `Promise`\<[`CryptoArgs`](core.Interface.CryptoArgs.md)\> -Defined in: [src/core.ts:75](../src/core.ts#L75) +Defined in: [src/core.ts:75](../core.ts#L75) Write buffer to encrypted file diff --git a/packages/simplex-chat-nodejs/docs/core.Function.loadLibrary.md b/packages/simplex-chat-nodejs/docs/core.Function.loadLibrary.md index a2034b953d..557bb0c80e 100644 --- a/packages/simplex-chat-nodejs/docs/core.Function.loadLibrary.md +++ b/packages/simplex-chat-nodejs/docs/core.Function.loadLibrary.md @@ -8,7 +8,7 @@ > **loadLibrary**(`backend`): `Promise`\<`void`\> -Defined in: [src/core.ts:13](../src/core.ts#L13) +Defined in: [src/core.ts:13](../core.ts#L13) Resolve (downloading on first use) and load libsimplex for the backend. One libsimplex per process: the Haskell runtime is initialized once, and another backend is rejected. diff --git a/packages/simplex-chat-nodejs/docs/core.Interface.APIResult.md b/packages/simplex-chat-nodejs/docs/core.Interface.APIResult.md index 63b0caba85..3c03930c57 100644 --- a/packages/simplex-chat-nodejs/docs/core.Interface.APIResult.md +++ b/packages/simplex-chat-nodejs/docs/core.Interface.APIResult.md @@ -6,7 +6,7 @@ # Interface: APIResult\ -Defined in: [src/core.ts:112](../src/core.ts#L112) +Defined in: [src/core.ts:112](../core.ts#L112) ## Type Parameters @@ -20,7 +20,7 @@ Defined in: [src/core.ts:112](../src/core.ts#L112) > `optional` **error?**: `ChatError` -Defined in: [src/core.ts:114](../src/core.ts#L114) +Defined in: [src/core.ts:114](../core.ts#L114) *** @@ -28,4 +28,4 @@ Defined in: [src/core.ts:114](../src/core.ts#L114) > `optional` **result?**: `R` -Defined in: [src/core.ts:113](../src/core.ts#L113) +Defined in: [src/core.ts:113](../core.ts#L113) diff --git a/packages/simplex-chat-nodejs/docs/core.Interface.CryptoArgs.md b/packages/simplex-chat-nodejs/docs/core.Interface.CryptoArgs.md index 939b3f7430..3531bad332 100644 --- a/packages/simplex-chat-nodejs/docs/core.Interface.CryptoArgs.md +++ b/packages/simplex-chat-nodejs/docs/core.Interface.CryptoArgs.md @@ -6,7 +6,7 @@ # Interface: CryptoArgs -Defined in: [src/core.ts:136](../src/core.ts#L136) +Defined in: [src/core.ts:136](../core.ts#L136) File encryption key and nonce @@ -16,7 +16,7 @@ File encryption key and nonce > **fileKey**: `string` -Defined in: [src/core.ts:137](../src/core.ts#L137) +Defined in: [src/core.ts:137](../core.ts#L137) *** @@ -24,4 +24,4 @@ Defined in: [src/core.ts:137](../src/core.ts#L137) > **fileNonce**: `string` -Defined in: [src/core.ts:138](../src/core.ts#L138) +Defined in: [src/core.ts:138](../core.ts#L138) diff --git a/packages/simplex-chat-nodejs/docs/core.Interface.UpMigration.md b/packages/simplex-chat-nodejs/docs/core.Interface.UpMigration.md index 30a7f0b060..061cb346ac 100644 --- a/packages/simplex-chat-nodejs/docs/core.Interface.UpMigration.md +++ b/packages/simplex-chat-nodejs/docs/core.Interface.UpMigration.md @@ -6,7 +6,7 @@ # Interface: UpMigration -Defined in: [src/core.ts:215](../src/core.ts#L215) +Defined in: [src/core.ts:215](../core.ts#L215) ## Properties @@ -14,7 +14,7 @@ Defined in: [src/core.ts:215](../src/core.ts#L215) > **upName**: `string` -Defined in: [src/core.ts:216](../src/core.ts#L216) +Defined in: [src/core.ts:216](../core.ts#L216) *** @@ -22,4 +22,4 @@ Defined in: [src/core.ts:216](../src/core.ts#L216) > **withDown**: `boolean` -Defined in: [src/core.ts:217](../src/core.ts#L217) +Defined in: [src/core.ts:217](../core.ts#L217) diff --git a/packages/simplex-chat-nodejs/docs/core.MTRError.Interface.MTREDifferent.md b/packages/simplex-chat-nodejs/docs/core.MTRError.Interface.MTREDifferent.md index aaaa506f1e..406e9a054b 100644 --- a/packages/simplex-chat-nodejs/docs/core.MTRError.Interface.MTREDifferent.md +++ b/packages/simplex-chat-nodejs/docs/core.MTRError.Interface.MTREDifferent.md @@ -6,7 +6,7 @@ # Interface: MTREDifferent -Defined in: [src/core.ts:236](../src/core.ts#L236) +Defined in: [src/core.ts:236](../core.ts#L236) ## Extends @@ -18,7 +18,7 @@ Defined in: [src/core.ts:236](../src/core.ts#L236) > **downMigrations**: `string`[] -Defined in: [src/core.ts:238](../src/core.ts#L238) +Defined in: [src/core.ts:238](../core.ts#L238) *** @@ -26,7 +26,7 @@ Defined in: [src/core.ts:238](../src/core.ts#L238) > **type**: `"different"` -Defined in: [src/core.ts:237](../src/core.ts#L237) +Defined in: [src/core.ts:237](../core.ts#L237) #### Overrides diff --git a/packages/simplex-chat-nodejs/docs/core.MTRError.Interface.MTRENoDown.md b/packages/simplex-chat-nodejs/docs/core.MTRError.Interface.MTRENoDown.md index e088536be9..8cdb1141ce 100644 --- a/packages/simplex-chat-nodejs/docs/core.MTRError.Interface.MTRENoDown.md +++ b/packages/simplex-chat-nodejs/docs/core.MTRError.Interface.MTRENoDown.md @@ -6,7 +6,7 @@ # Interface: MTRENoDown -Defined in: [src/core.ts:231](../src/core.ts#L231) +Defined in: [src/core.ts:231](../core.ts#L231) ## Extends @@ -18,7 +18,7 @@ Defined in: [src/core.ts:231](../src/core.ts#L231) > **dbMigrations**: `string`[] -Defined in: [src/core.ts:233](../src/core.ts#L233) +Defined in: [src/core.ts:233](../core.ts#L233) *** @@ -26,7 +26,7 @@ Defined in: [src/core.ts:233](../src/core.ts#L233) > **type**: `"noDown"` -Defined in: [src/core.ts:232](../src/core.ts#L232) +Defined in: [src/core.ts:232](../core.ts#L232) #### Overrides diff --git a/packages/simplex-chat-nodejs/docs/core.MTRError.TypeAlias.Tag.md b/packages/simplex-chat-nodejs/docs/core.MTRError.TypeAlias.Tag.md index 058c64d396..5d92b7dc67 100644 --- a/packages/simplex-chat-nodejs/docs/core.MTRError.TypeAlias.Tag.md +++ b/packages/simplex-chat-nodejs/docs/core.MTRError.TypeAlias.Tag.md @@ -8,4 +8,4 @@ > **Tag** = `"noDown"` \| `"different"` -Defined in: [src/core.ts:225](../src/core.ts#L225) +Defined in: [src/core.ts:225](../core.ts#L225) diff --git a/packages/simplex-chat-nodejs/docs/core.MigrationError.Interface.MEDowngrade.md b/packages/simplex-chat-nodejs/docs/core.MigrationError.Interface.MEDowngrade.md index 523fe8fe1e..d8dadcf2da 100644 --- a/packages/simplex-chat-nodejs/docs/core.MigrationError.Interface.MEDowngrade.md +++ b/packages/simplex-chat-nodejs/docs/core.MigrationError.Interface.MEDowngrade.md @@ -6,7 +6,7 @@ # Interface: MEDowngrade -Defined in: [src/core.ts:204](../src/core.ts#L204) +Defined in: [src/core.ts:204](../core.ts#L204) ## Extends @@ -18,7 +18,7 @@ Defined in: [src/core.ts:204](../src/core.ts#L204) > **downMigrations**: `string`[] -Defined in: [src/core.ts:206](../src/core.ts#L206) +Defined in: [src/core.ts:206](../core.ts#L206) *** @@ -26,7 +26,7 @@ Defined in: [src/core.ts:206](../src/core.ts#L206) > **type**: `"downgrade"` -Defined in: [src/core.ts:205](../src/core.ts#L205) +Defined in: [src/core.ts:205](../core.ts#L205) #### Overrides diff --git a/packages/simplex-chat-nodejs/docs/core.MigrationError.Interface.MEUpgrade.md b/packages/simplex-chat-nodejs/docs/core.MigrationError.Interface.MEUpgrade.md index c5856f1943..7414f4309d 100644 --- a/packages/simplex-chat-nodejs/docs/core.MigrationError.Interface.MEUpgrade.md +++ b/packages/simplex-chat-nodejs/docs/core.MigrationError.Interface.MEUpgrade.md @@ -6,7 +6,7 @@ # Interface: MEUpgrade -Defined in: [src/core.ts:199](../src/core.ts#L199) +Defined in: [src/core.ts:199](../core.ts#L199) ## Extends @@ -18,7 +18,7 @@ Defined in: [src/core.ts:199](../src/core.ts#L199) > **type**: `"upgrade"` -Defined in: [src/core.ts:200](../src/core.ts#L200) +Defined in: [src/core.ts:200](../core.ts#L200) #### Overrides @@ -30,4 +30,4 @@ Defined in: [src/core.ts:200](../src/core.ts#L200) > **upMigrations**: [`UpMigration`](core.Interface.UpMigration.md)[] -Defined in: [src/core.ts:201](../src/core.ts#L201) +Defined in: [src/core.ts:201](../core.ts#L201) diff --git a/packages/simplex-chat-nodejs/docs/core.MigrationError.Interface.MigrationError.md b/packages/simplex-chat-nodejs/docs/core.MigrationError.Interface.MigrationError.md index f3f7d9bc1f..b27f2a2f68 100644 --- a/packages/simplex-chat-nodejs/docs/core.MigrationError.Interface.MigrationError.md +++ b/packages/simplex-chat-nodejs/docs/core.MigrationError.Interface.MigrationError.md @@ -6,7 +6,7 @@ # Interface: MigrationError -Defined in: [src/core.ts:209](../src/core.ts#L209) +Defined in: [src/core.ts:209](../core.ts#L209) ## Extends @@ -18,7 +18,7 @@ Defined in: [src/core.ts:209](../src/core.ts#L209) > **mtrError**: [`MTRError`](core.TypeAlias.MTRError.md) -Defined in: [src/core.ts:211](../src/core.ts#L211) +Defined in: [src/core.ts:211](../core.ts#L211) *** @@ -26,7 +26,7 @@ Defined in: [src/core.ts:211](../src/core.ts#L211) > **type**: `"migrationError"` -Defined in: [src/core.ts:210](../src/core.ts#L210) +Defined in: [src/core.ts:210](../core.ts#L210) #### Overrides diff --git a/packages/simplex-chat-nodejs/docs/core.MigrationError.TypeAlias.Tag.md b/packages/simplex-chat-nodejs/docs/core.MigrationError.TypeAlias.Tag.md index 92f649e615..a7028e908f 100644 --- a/packages/simplex-chat-nodejs/docs/core.MigrationError.TypeAlias.Tag.md +++ b/packages/simplex-chat-nodejs/docs/core.MigrationError.TypeAlias.Tag.md @@ -8,4 +8,4 @@ > **Tag** = `"upgrade"` \| `"downgrade"` \| `"migrationError"` -Defined in: [src/core.ts:193](../src/core.ts#L193) +Defined in: [src/core.ts:193](../core.ts#L193) diff --git a/packages/simplex-chat-nodejs/docs/core.TypeAlias.Backend.md b/packages/simplex-chat-nodejs/docs/core.TypeAlias.Backend.md index 9b32c20d5b..5f95c3a0b5 100644 --- a/packages/simplex-chat-nodejs/docs/core.TypeAlias.Backend.md +++ b/packages/simplex-chat-nodejs/docs/core.TypeAlias.Backend.md @@ -8,4 +8,4 @@ > **Backend** = `"sqlite"` \| `"postgres"` -Defined in: [src/core.ts:5](../src/core.ts#L5) +Defined in: [src/core.ts:5](../core.ts#L5) diff --git a/packages/simplex-chat-nodejs/docs/core.TypeAlias.DBMigrationError.md b/packages/simplex-chat-nodejs/docs/core.TypeAlias.DBMigrationError.md index 159ce87391..58cefef41f 100644 --- a/packages/simplex-chat-nodejs/docs/core.TypeAlias.DBMigrationError.md +++ b/packages/simplex-chat-nodejs/docs/core.TypeAlias.DBMigrationError.md @@ -8,4 +8,4 @@ > **DBMigrationError** = [`InvalidConfirmation`](core.DBMigrationError.Interface.InvalidConfirmation.md) \| [`InvalidQueueSize`](core.DBMigrationError.Interface.InvalidQueueSize.md) \| [`ErrorNotADatabase`](core.DBMigrationError.Interface.ErrorNotADatabase.md) \| [`ErrorMigration`](core.DBMigrationError.Interface.ErrorMigration.md) \| [`ErrorSQL`](core.DBMigrationError.Interface.ErrorSQL.md) -Defined in: [src/core.ts:147](../src/core.ts#L147) +Defined in: [src/core.ts:147](../core.ts#L147) diff --git a/packages/simplex-chat-nodejs/docs/core.TypeAlias.MTRError.md b/packages/simplex-chat-nodejs/docs/core.TypeAlias.MTRError.md index 1c4ceb16e9..bb6e1b1650 100644 --- a/packages/simplex-chat-nodejs/docs/core.TypeAlias.MTRError.md +++ b/packages/simplex-chat-nodejs/docs/core.TypeAlias.MTRError.md @@ -8,4 +8,4 @@ > **MTRError** = [`MTRENoDown`](core.MTRError.Interface.MTRENoDown.md) \| [`MTREDifferent`](core.MTRError.Interface.MTREDifferent.md) -Defined in: [src/core.ts:220](../src/core.ts#L220) +Defined in: [src/core.ts:220](../core.ts#L220) diff --git a/packages/simplex-chat-nodejs/docs/core.TypeAlias.MigrationError.md b/packages/simplex-chat-nodejs/docs/core.TypeAlias.MigrationError.md index 485311f0de..1945816f6f 100644 --- a/packages/simplex-chat-nodejs/docs/core.TypeAlias.MigrationError.md +++ b/packages/simplex-chat-nodejs/docs/core.TypeAlias.MigrationError.md @@ -8,4 +8,4 @@ > **MigrationError** = [`MEUpgrade`](core.MigrationError.Interface.MEUpgrade.md) \| [`MEDowngrade`](core.MigrationError.Interface.MEDowngrade.md) \| [`MigrationError`](core.MigrationError.Interface.MigrationError.md) -Defined in: [src/core.ts:187](../src/core.ts#L187) +Defined in: [src/core.ts:187](../core.ts#L187) diff --git a/packages/simplex-chat-nodejs/docs/util.Function.botAddressSettings.md b/packages/simplex-chat-nodejs/docs/util.Function.botAddressSettings.md index 7fa5d81b7a..9c7e1c22ef 100644 --- a/packages/simplex-chat-nodejs/docs/util.Function.botAddressSettings.md +++ b/packages/simplex-chat-nodejs/docs/util.Function.botAddressSettings.md @@ -8,7 +8,7 @@ > **botAddressSettings**(`__namedParameters`): [`BotAddressSettings`](api.Interface.BotAddressSettings.md) -Defined in: [src/util.ts:48](../src/util.ts#L48) +Defined in: [src/util.ts:48](../util.ts#L48) ## Parameters diff --git a/packages/simplex-chat-nodejs/docs/util.Function.chatInfoName.md b/packages/simplex-chat-nodejs/docs/util.Function.chatInfoName.md index dd68403c40..614e164974 100644 --- a/packages/simplex-chat-nodejs/docs/util.Function.chatInfoName.md +++ b/packages/simplex-chat-nodejs/docs/util.Function.chatInfoName.md @@ -8,7 +8,7 @@ > **chatInfoName**(`cInfo`): `string` -Defined in: [src/util.ts:18](../src/util.ts#L18) +Defined in: [src/util.ts:18](../util.ts#L18) ## Parameters diff --git a/packages/simplex-chat-nodejs/docs/util.Function.chatInfoRef.md b/packages/simplex-chat-nodejs/docs/util.Function.chatInfoRef.md index dccb2a9d29..11e5272392 100644 --- a/packages/simplex-chat-nodejs/docs/util.Function.chatInfoRef.md +++ b/packages/simplex-chat-nodejs/docs/util.Function.chatInfoRef.md @@ -8,7 +8,7 @@ > **chatInfoRef**(`cInfo`): `ChatRef` \| `undefined` -Defined in: [src/util.ts:4](../src/util.ts#L4) +Defined in: [src/util.ts:4](../util.ts#L4) ## Parameters diff --git a/packages/simplex-chat-nodejs/docs/util.Function.ciBotCommand.md b/packages/simplex-chat-nodejs/docs/util.Function.ciBotCommand.md index bc7a66f163..4e880ea329 100644 --- a/packages/simplex-chat-nodejs/docs/util.Function.ciBotCommand.md +++ b/packages/simplex-chat-nodejs/docs/util.Function.ciBotCommand.md @@ -8,7 +8,7 @@ > **ciBotCommand**(`chatItem`): [`BotCommand`](util.Interface.BotCommand.md) \| `undefined` -Defined in: [src/util.ts:78](../src/util.ts#L78) +Defined in: [src/util.ts:78](../util.ts#L78) ## Parameters diff --git a/packages/simplex-chat-nodejs/docs/util.Function.ciContentText.md b/packages/simplex-chat-nodejs/docs/util.Function.ciContentText.md index 7ab0bc540c..624f88f45a 100644 --- a/packages/simplex-chat-nodejs/docs/util.Function.ciContentText.md +++ b/packages/simplex-chat-nodejs/docs/util.Function.ciContentText.md @@ -8,7 +8,7 @@ > **ciContentText**(`__namedParameters`): `string` \| `undefined` -Defined in: [src/util.ts:64](../src/util.ts#L64) +Defined in: [src/util.ts:64](../util.ts#L64) ## Parameters diff --git a/packages/simplex-chat-nodejs/docs/util.Function.contactAddressStr.md b/packages/simplex-chat-nodejs/docs/util.Function.contactAddressStr.md index 3f6c7b9562..4776fd7262 100644 --- a/packages/simplex-chat-nodejs/docs/util.Function.contactAddressStr.md +++ b/packages/simplex-chat-nodejs/docs/util.Function.contactAddressStr.md @@ -8,7 +8,7 @@ > **contactAddressStr**(`link`): `string` -Defined in: [src/util.ts:44](../src/util.ts#L44) +Defined in: [src/util.ts:44](../util.ts#L44) ## Parameters diff --git a/packages/simplex-chat-nodejs/docs/util.Function.fromLocalProfile.md b/packages/simplex-chat-nodejs/docs/util.Function.fromLocalProfile.md index c32081b6cf..ed9640b927 100644 --- a/packages/simplex-chat-nodejs/docs/util.Function.fromLocalProfile.md +++ b/packages/simplex-chat-nodejs/docs/util.Function.fromLocalProfile.md @@ -8,7 +8,7 @@ > **fromLocalProfile**(`__namedParameters`): `Profile` -Defined in: [src/util.ts:56](../src/util.ts#L56) +Defined in: [src/util.ts:56](../util.ts#L56) ## Parameters diff --git a/packages/simplex-chat-nodejs/docs/util.Function.reactionText.md b/packages/simplex-chat-nodejs/docs/util.Function.reactionText.md index 896dc74a12..48090064c3 100644 --- a/packages/simplex-chat-nodejs/docs/util.Function.reactionText.md +++ b/packages/simplex-chat-nodejs/docs/util.Function.reactionText.md @@ -8,7 +8,7 @@ > **reactionText**(`reaction`): `string` -Defined in: [src/util.ts:89](../src/util.ts#L89) +Defined in: [src/util.ts:89](../util.ts#L89) ## Parameters diff --git a/packages/simplex-chat-nodejs/docs/util.Function.senderName.md b/packages/simplex-chat-nodejs/docs/util.Function.senderName.md index 6bacad97f6..891ec75991 100644 --- a/packages/simplex-chat-nodejs/docs/util.Function.senderName.md +++ b/packages/simplex-chat-nodejs/docs/util.Function.senderName.md @@ -8,7 +8,7 @@ > **senderName**(`cInfo`, `chatDir`): `string` -Defined in: [src/util.ts:37](../src/util.ts#L37) +Defined in: [src/util.ts:37](../util.ts#L37) ## Parameters diff --git a/packages/simplex-chat-nodejs/docs/util.Interface.BotCommand.md b/packages/simplex-chat-nodejs/docs/util.Interface.BotCommand.md index 21c3ad72ba..f50e88a1d1 100644 --- a/packages/simplex-chat-nodejs/docs/util.Interface.BotCommand.md +++ b/packages/simplex-chat-nodejs/docs/util.Interface.BotCommand.md @@ -6,7 +6,7 @@ # Interface: BotCommand -Defined in: [src/util.ts:72](../src/util.ts#L72) +Defined in: [src/util.ts:72](../util.ts#L72) ## Properties @@ -14,7 +14,7 @@ Defined in: [src/util.ts:72](../src/util.ts#L72) > **keyword**: `string` -Defined in: [src/util.ts:73](../src/util.ts#L73) +Defined in: [src/util.ts:73](../util.ts#L73) *** @@ -22,4 +22,4 @@ Defined in: [src/util.ts:73](../src/util.ts#L73) > **params**: `string` -Defined in: [src/util.ts:74](../src/util.ts#L74) +Defined in: [src/util.ts:74](../util.ts#L74) diff --git a/packages/simplex-chat-nodejs/package.json b/packages/simplex-chat-nodejs/package.json index eaf9de68fb..68c4c63469 100644 --- a/packages/simplex-chat-nodejs/package.json +++ b/packages/simplex-chat-nodejs/package.json @@ -1,6 +1,6 @@ { "name": "simplex-chat", - "version": "7.1.0-beta.6.4", + "version": "7.1.0-beta.6.6", "main": "dist/index.js", "types": "dist/index.d.ts", "gypfile": false, @@ -24,16 +24,17 @@ "docs": "typedoc" }, "dependencies": { - "@simplex-chat/types": "^7.1.0-beta.6.1", - "extract-zip": "^2.0.1", - "fast-deep-equal": "^3.1.3" + "@simplex-chat/types": "^7.1.0-beta.6.2", + "fast-deep-equal": "^3.1.3", + "yauzl": "^3.4.0" }, "devDependencies": { "@types/jest": "^30.0.0", "@types/node": "^25.0.5", - "jest": "^30.2.0", + "@types/yauzl": "^3.4.0", + "jest": "^30.5.2", "node-addon-api": "^8.5.0", - "ts-jest": "^29.4.6", + "ts-jest": "^29.4.15", "typedoc": "^0.28.15", "typedoc-plugin-markdown": "^4.9.0", "typescript": "^5.9.3" diff --git a/packages/simplex-chat-nodejs/src/api.ts b/packages/simplex-chat-nodejs/src/api.ts index 4ee4e4b3b9..fc090d037c 100644 --- a/packages/simplex-chat-nodejs/src/api.ts +++ b/packages/simplex-chat-nodejs/src/api.ts @@ -273,9 +273,11 @@ export class ChatApi { } return new Promise((resolve, reject) => { let done = false + let timer: NodeJS.Timeout | undefined = undefined const cleanup = () => { done = true - this.off(event, subscriber) + clearTimeout(timer) + this.off(event, subscriber) } const subscriber: EventSubscriberFunc = async (evt: ChatEvent & {type: K}) => { if (done) return @@ -288,7 +290,7 @@ export class ChatApi { } this.on(event, subscriber) if (timeout > 0) { - setTimeout(() => { if (!done) { cleanup(); resolve(undefined) } }, timeout) + timer = setTimeout(() => { if (!done) { cleanup(); resolve(undefined) } }, timeout) } }) } @@ -476,9 +478,10 @@ export class ChatApi { /** * Update message. + * A live message stays live with `liveMessage = true` and is finalized with `liveMessage = false`. * Network usage: background. */ - async apiUpdateChatItem(chatType: T.ChatType, chatId: number, chatItemId: number, msgContent: T.MsgContent, liveMessage: false): Promise { + async apiUpdateChatItem(chatType: T.ChatType, chatId: number, chatItemId: number, msgContent: T.MsgContent, liveMessage = false): Promise { const r = await this.sendChatCmd( CC.APIUpdateChatItem.cmdString({ chatRef: {chatType, chatId}, @@ -995,3 +998,88 @@ export class ChatApi { throw new ChatCommandError("error sending member contact invitation", r) } } + +/** + * Text shown as a live message while it is written, e.g. a streamed reply. + */ +export class LiveMessage { + private tasks: Promise = Promise.resolve() + private text = "" + private writing = false + private timer: NodeJS.Timeout | undefined = undefined + private item: {itemId: number, text: string} | undefined = undefined + + /** + * @param chat - Chat API, or an object with its methods to send, update and delete messages. + * @param {T.ChatType} chatType - Chat type. + * @param {number} chatId - Contact or group ID. + * @param {number} [interval=3000] - Minimum interval between updates of the live message, in milliseconds. + * @param onError - Error handler for sending, updating and deleting messages. Errors are logged when it is omitted. + */ + constructor( + private readonly chat: Pick, + private readonly chatType: T.ChatType, + private readonly chatId: number, + private readonly interval = 3000, + private readonly onError: (error: unknown) => void = e => console.log("live message error", e) + ) {} + + /** + * Set the text of the live message. + * The first text is sent at once; later texts update the message at most once per interval. + */ + update(text: string): void { + this.text = text + if (!this.writing) { + this.writing = true + this.queueText(text, true) + } else if (this.timer === undefined) { + this.timer = setTimeout(() => { + this.timer = undefined + this.queueText(this.text, true) + }, this.interval) + } + } + + /** + * Finalize the live message with the text, or with the last text when omitted. + * A message without text is deleted. The next update starts a new live message. + * The returned promise resolves after all queued commands complete. + */ + end(text = this.text): Promise { + clearTimeout(this.timer) + this.timer = undefined + this.text = "" + this.writing = false + this.queueText(text, false) + return this.tasks + } + + private queueText(text: string, live: boolean): void { + this.tasks = this.tasks.then(() => this.showText(text, live)).catch(this.onError) + } + + private async showText(text: string, live: boolean): Promise { + try { + await this.showMessage(text, live) + } finally { + if (!live) this.item = undefined + } + } + + private async showMessage(text: string, live: boolean): Promise { + const item = this.item + if (item === undefined) { + if (live || text !== "") { + const [{chatItem}] = await this.chat.apiSendMessages([this.chatType, this.chatId], [{msgContent: {type: "text", text}, mentions: {}}], live) + this.item = live ? {itemId: chatItem.meta.itemId, text} : undefined + } + } else if (!live && text === "") { + await this.chat.apiDeleteChatItems(this.chatType, this.chatId, [item.itemId], T.CIDeleteMode.Broadcast) + this.item = undefined + } else if (!live || text !== item.text) { + await this.chat.apiUpdateChatItem(this.chatType, this.chatId, item.itemId, {type: "text", text}, live) + this.item = live ? {itemId: item.itemId, text} : undefined + } + } +} diff --git a/packages/simplex-chat-nodejs/src/libs.ts b/packages/simplex-chat-nodejs/src/libs.ts index 013e49f5d0..ce0063f183 100644 --- a/packages/simplex-chat-nodejs/src/libs.ts +++ b/packages/simplex-chat-nodejs/src/libs.ts @@ -5,7 +5,7 @@ import * as https from "https" import * as os from "os" import * as path from "path" import {pipeline} from "stream/promises" -import extract = require("extract-zip") +import * as yauzl from "yauzl" import type {Backend} from "./core" import {ADDON_RELEASE, LIBS_RELEASE, SHA256} from "./release" @@ -19,6 +19,8 @@ const PLATFORMS: {[platform: string]: {name: string, lib: string} | undefined} = } const ARCHS: {[arch: string]: string | undefined} = {x64: "x86_64", arm64: "aarch64"} const SUPPORTED = ["linux-x86_64", "linux-aarch64", "macos-x86_64", "macos-aarch64", "windows-x86_64"] +const S_IFMT = 0o170000 +const S_IFLNK = 0o120000 function unsupported(platform: string, arch: string): Error { return new Error(`Unsupported platform: ${platform}/${arch}; supported: ${SUPPORTED.join(", ")}`) @@ -115,7 +117,7 @@ export function installLibs(url: string, sha256: string, target: string, lib: st const zipPath = path.join(tmp, "libs.zip") await download(url, zipPath, timeoutMs) await verifySha256(zipPath, sha256, url) - await extract(zipPath, {dir: tmp}) + await unzip(zipPath, tmp) const extracted = path.join(tmp, "libs") if (!fs.existsSync(path.join(extracted, lib))) throw new Error(`libs/${lib} missing from ${url}`) await renameInto(extracted, target, path.join(target, lib)) @@ -132,6 +134,44 @@ export function installAddon(url: string, sha256: string, target: string, timeou }) } +function unzip(zipPath: string, dir: string): Promise { + const root = path.resolve(dir) + return new Promise((resolve, reject) => { + yauzl.open(zipPath, {lazyEntries: true}, (err, zip) => { + if (err || !zip) return reject(err ?? new Error(`cannot open ${zipPath}`)) + let failed = false + const fail = (e: unknown) => { + if (failed) return + failed = true + zip.close() + reject(e) + } + zip.on("error", e => { + failed = true + reject(e) + }) + zip.on("end", () => resolve()) + zip.on("entry", (entry: yauzl.Entry) => { + const dest = path.resolve(root, entry.fileName) + if (!dest.startsWith(root + path.sep)) return fail(new Error(`zip entry outside the target folder: ${entry.fileName}`)) + const mode = entry.externalFileAttributes >>> 16 + if ((mode & S_IFMT) === S_IFLNK) return fail(new Error(`zip entry is a symlink: ${entry.fileName}`)) + if (entry.fileName.endsWith("/")) { + fs.promises.mkdir(dest, {recursive: true}).then(() => zip.readEntry(), fail) + return + } + zip.openReadStream(entry, (e, stream) => { + if (e || !stream) return fail(e ?? new Error(`cannot read ${entry.fileName}`)) + fs.promises.mkdir(path.dirname(dest), {recursive: true}) + .then(() => pipeline(stream, fs.createWriteStream(dest, {mode: (mode & 0o777) || 0o644}))) + .then(() => zip.readEntry(), fail) + }) + }) + zip.readEntry() + }) + }) +} + async function verifySha256(file: string, expected: string, url: string): Promise { const hash = crypto.createHash("sha256") for await (const chunk of fs.createReadStream(file)) hash.update(chunk) diff --git a/packages/simplex-chat-nodejs/src/release.ts b/packages/simplex-chat-nodejs/src/release.ts index ad9dfa4c78..cc96e5b9f6 100644 --- a/packages/simplex-chat-nodejs/src/release.ts +++ b/packages/simplex-chat-nodejs/src/release.ts @@ -1,16 +1,16 @@ // Written by scripts/ci/packages_release.py from the simplex-chat-libs releases. -export const LIBS_RELEASE = "core-7.1.0.12" +export const LIBS_RELEASE = "core-7.1.0.14" export const ADDON_RELEASE: string | undefined = "nodejs-addon-308c0960c707" export const SHA256: {readonly [asset: string]: string} = { - "simplex-chat-libs-linux-aarch64.zip": "924b8e14eaba5e309fa584c4bfe598421cf64983d63790c1c45247f592f60055", - "simplex-chat-libs-linux-x86_64-postgres.zip": "323f908b45dde751d6082f8885d07d81fba8a5ab8b2df7d3b8ab3a87e56299e5", - "simplex-chat-libs-linux-x86_64.zip": "995409e963db408bcf896361b669944484f845b6744fefe479aebb0ca2e7c5cd", - "simplex-chat-libs-macos-aarch64.zip": "64464aa48b996e5a96b4aa651bc57bc69e412d3c3804b589c67bd9dc52faddbf", - "simplex-chat-libs-macos-x86_64.zip": "94664f96ff7a2cd5a419096e7e843928faa31cb3addb8869fa1e4b5a0d0b3629", - "simplex-chat-libs-windows-x86_64.zip": "2e10844e5c119921139c373ee2971341aceef7dc4198429d3be6b1570a009d15", + "simplex-chat-libs-linux-aarch64.zip": "375a6021f0c2e5d79b2cd192e17c6c22c8eae73f46e3758fba24ee5c26a671aa", + "simplex-chat-libs-linux-x86_64-postgres.zip": "c3e8a54d37801e685c6361c4e7e53e1f5163d3aa9195dcfe686b6cf90e668ef3", + "simplex-chat-libs-linux-x86_64.zip": "cc41ab9049d61be3b386d799fa9394f2870e8ae3b2b9b8745dd3f53ba99f211e", + "simplex-chat-libs-macos-aarch64.zip": "ac3a46004f25976f71b3ead8a20b9a01c170fe12f125a0a6799ae97ea990f234", + "simplex-chat-libs-macos-x86_64.zip": "0d85ffcccc3a6dfe3e0da18d0b7be9ef36ad27381f047ef66cfde67ee4a7e882", + "simplex-chat-libs-windows-x86_64.zip": "f1d1c7efc333a5ec5328a5843c9d3c9ef038fb2886c0b60ac12e87a5c1e85bbd", "simplex-chat-nodejs-linux-aarch64.node": "3636a82c56c48229e0326fa03d6ceb5c16dd3afbcc1e86337783dc53a7328d67", "simplex-chat-nodejs-linux-x86_64.node": "55f3479b03fdb57591179ed730da744f1aa362e7529df55221b60028415746e9", "simplex-chat-nodejs-macos-aarch64.node": "a3472872fb0816f435f52ee5471088e6545ebffb95e89fbf8e1ab82275f2d56e", diff --git a/packages/simplex-chat-nodejs/tests/api.test.ts b/packages/simplex-chat-nodejs/tests/api.test.ts index e3ddca2440..5ff298d33f 100644 --- a/packages/simplex-chat-nodejs/tests/api.test.ts +++ b/packages/simplex-chat-nodejs/tests/api.test.ts @@ -2,17 +2,25 @@ import * as path from "path" import * as fs from "fs" import {CEvt, T} from "@simplex-chat/types" import {api} from ".." +import {SmpServer, describeIfSmpServerAvailable, startSmpServer} from "./smpServer" const CT = T.ChatType -describe("API tests (use preset servers)", () => { +describeIfSmpServerAvailable("API tests (use local SMP server)", () => { const tmpDir = "./tests/tmp" const alicePath = path.join(tmpDir, "alice") const bobPath = path.join(tmpDir, "bob") + let smpServer: SmpServer + beforeAll(async () => { smpServer = await startSmpServer() }, 120000) + afterAll(() => smpServer?.stop()) beforeEach(() => fs.mkdirSync(tmpDir, {recursive: true})) afterEach(() => fs.rmSync(tmpDir, {recursive: true, force: true})) + async function useSmpServer(chat: api.ChatApi): Promise { + expect(await chat.sendChatCmd(`/smp ${smpServer.address}`)).toMatchObject({type: "cmdOk"}) + } + it("should send/receive message", async () => { // create users and start chat controllers const alice = await api.ChatApi.init({type: "sqlite", filePrefix: alicePath}) @@ -27,32 +35,37 @@ describe("API tests (use preset servers)", () => { await bob.apiCreateActiveUser({displayName: "bob", fullName: ""}) await alice.startChat() await bob.startChat() + await useSmpServer(alice) + await useSmpServer(bob) // connect via link const link = await alice.apiCreateLink(aliceUser.userId) + const connected = Promise.all([alice.wait("contactConnected"), bob.wait("contactConnected")]) await expect(bob.apiConnectActiveUser(link)).resolves.toBe(api.ConnReqType.Invitation) - const [bobContact, aliceContact] = await Promise.all([ - (await alice.wait("contactConnected")).contact, - (await bob.wait("contactConnected")).contact - ]) + const [{contact: bobContact}, {contact: aliceContact}] = await connected expect(bobContact).toMatchObject({profile: {displayName: "bob"}}) expect(aliceContact).toMatchObject({profile: {displayName: "alice"}}) // exchange messages const isMessage = ({contactId}: T.Contact, msg: string) => (evt: CEvt.NewChatItems) => evt.chatItems.some(ci => ci.chatInfo.type === CT.Direct && ci.chatInfo.contact.contactId === contactId && ci.chatItem.meta.itemText === msg) + const hello = bob.wait("newChatItems", isMessage(aliceContact, "hello")) await alice.apiSendTextMessage([CT.Direct, bobContact.contactId], "hello") - await bob.wait("newChatItems", isMessage(aliceContact, "hello")) + await hello + const helloToo = alice.wait("newChatItems", isMessage(bobContact, "hello too"), 10000) await bob.apiSendTextMessage([CT.Direct, aliceContact.contactId], "hello too") - await alice.wait("newChatItems", isMessage(bobContact, "hello too"), 10000) + await expect(helloToo).resolves.toBeDefined() + const howAreYou = bob.wait("newChatItems", isMessage(aliceContact, "how are you?")) await alice.apiSendTextMessage([CT.Direct, bobContact.contactId], "how are you?") - await bob.wait("newChatItems", isMessage(aliceContact, "how are you?")) + await howAreYou + const okAndYou = alice.wait("newChatItems", isMessage(bobContact, "ok, and you?"), 10000) await bob.apiSendTextMessage([CT.Direct, aliceContact.contactId], "ok, and you?") - await alice.wait("newChatItems", isMessage(bobContact, "ok, and you?"), 10000) + await expect(okAndYou).resolves.toBeDefined() // no more messages await expect(alice.wait("newChatItems", 500)).resolves.toBeUndefined() await expect(bob.wait("newChatItems", 500)).resolves.toBeUndefined() // delete contacts, stop chat controllers and close databases + const deleted = bob.wait("contactDeletedByContact") await alice.apiDeleteChat(CT.Direct, bobContact.contactId) - await bob.wait("contactDeletedByContact") + await deleted await bob.apiDeleteChat(CT.Direct, aliceContact.contactId) await alice.stopChat() await bob.stopChat() @@ -60,8 +73,7 @@ describe("API tests (use preset servers)", () => { await bob.close() await expect(alice.startChat()).rejects.toThrow("chat api controller not initialized") await expect(bob.startChat()).rejects.toThrow("chat api controller not initialized") - expect(servers.length).toBe(2) - expect(servers[0] !== servers[1]).toBe(true) + expect([...new Set(servers)]).toEqual(["localhost"]) expect(eventCount > 0).toBe(true) }, 30000) @@ -77,20 +89,19 @@ describe("API tests (use preset servers)", () => { await alice.startChat() await bob.startChat() await carol.startChat() + await useSmpServer(alice) + await useSmpServer(bob) + await useSmpServer(carol) // connect alice <-> bob const aliceLink1 = await alice.apiCreateLink(aliceUser.userId) + const bobConnected = Promise.all([alice.wait("contactConnected"), bob.wait("contactConnected")]) await expect(bob.apiConnectActiveUser(aliceLink1)).resolves.toBe(api.ConnReqType.Invitation) - const [bobContact] = await Promise.all([ - (await alice.wait("contactConnected")).contact, - (await bob.wait("contactConnected")).contact - ]) + const [{contact: bobContact}] = await bobConnected // connect alice <-> carol const aliceLink2 = await alice.apiCreateLink(aliceUser.userId) + const carolConnected = Promise.all([alice.wait("contactConnected"), carol.wait("contactConnected")]) await expect(carol.apiConnectActiveUser(aliceLink2)).resolves.toBe(api.ConnReqType.Invitation) - const [carolContact] = await Promise.all([ - (await alice.wait("contactConnected")).contact, - (await carol.wait("contactConnected")).contact - ]) + const [{contact: carolContact}] = await carolConnected // create group with direct messages enabled const group = await alice.apiNewGroup(aliceUser.userId, { displayName: "test-group", @@ -105,10 +116,10 @@ describe("API tests (use preset servers)", () => { await alice.apiAddMember(groupId, bobContact.contactId, T.GroupMemberRole.Member) const bobInvEvt = await bobInvP expect(bobInvEvt).toBeDefined() - const aliceBobConnP = alice.wait("connectedToGroupMember", 15000) - const bobAliceConnP = bob.wait("connectedToGroupMember", 15000) + const aliceBobConnP = alice.wait("joinedGroupMember", 15000) + const bobAliceConnP = bob.wait("userJoinedGroup", 15000) await bob.apiJoinGroup(bobInvEvt!.groupInfo.groupId) - await Promise.all([aliceBobConnP, bobAliceConnP]) + expect(await Promise.all([aliceBobConnP, bobAliceConnP])).not.toContain(undefined) // add carol to the group const carolInvP = carol.wait("receivedGroupInvitation", 30000) await alice.apiAddMember(groupId, carolContact.contactId, T.GroupMemberRole.Member) @@ -117,14 +128,13 @@ describe("API tests (use preset servers)", () => { // wait for carol to connect to both alice and bob (and vice versa) const bobCarolConnP = bob.wait("connectedToGroupMember", (evt: CEvt.ConnectedToGroupMember) => evt.member.memberProfile.displayName === "carol", 30000) - const carolAliceConnP = carol.wait("connectedToGroupMember", - (evt: CEvt.ConnectedToGroupMember) => evt.member.memberProfile.displayName === "alice", 30000) + const carolAliceConnP = carol.wait("userJoinedGroup", 30000) const carolBobConnP = carol.wait("connectedToGroupMember", (evt: CEvt.ConnectedToGroupMember) => evt.member.memberProfile.displayName === "bob", 30000) - const aliceCarolConnP = alice.wait("connectedToGroupMember", - (evt: CEvt.ConnectedToGroupMember) => evt.member.memberProfile.displayName === "carol", 30000) + const aliceCarolConnP = alice.wait("joinedGroupMember", + (evt: CEvt.JoinedGroupMember) => evt.member.memberProfile.displayName === "carol", 30000) await carol.apiJoinGroup(carolInvEvt!.groupInfo.groupId) - await Promise.all([bobCarolConnP, carolAliceConnP, carolBobConnP, aliceCarolConnP]) + expect(await Promise.all([bobCarolConnP, carolAliceConnP, carolBobConnP, aliceCarolConnP])).not.toContain(undefined) // find carol's memberId from bob's perspective const members = await bob.apiListMembers(groupId) const carolMember = members.find(m => m.memberProfile.displayName === "carol") diff --git a/packages/simplex-chat-nodejs/tests/api.unit.test.ts b/packages/simplex-chat-nodejs/tests/api.unit.test.ts index b4ae456af9..14be90f5bf 100644 --- a/packages/simplex-chat-nodejs/tests/api.unit.test.ts +++ b/packages/simplex-chat-nodejs/tests/api.unit.test.ts @@ -26,6 +26,17 @@ describe("documented success responses", () => { await expect(chat.apiUpdateChatItem(T.ChatType.Direct, 1, 2, {type: "text", text: "same"}, false)).resolves.toEqual(chatItem) }) + it("apiUpdateChatItem updates and finalizes a live message", async () => { + const chatItem = {meta: {itemId: 2}} + const chat = await chatWithResponse({type: "chatItemUpdated", user, chatItem: {chatItem}}) + await expect(chat.apiUpdateChatItem(T.ChatType.Group, 1, 2, {type: "text", text: "partial"}, true)).resolves.toEqual(chatItem) + await expect(chat.apiUpdateChatItem(T.ChatType.Group, 1, 2, {type: "text", text: "final"})).resolves.toEqual(chatItem) + expect(jest.mocked(core.chatSendCmd).mock.calls.map(([, cmd]) => cmd)).toEqual([ + '/_update item #1 2 live=on json {"msgContent":{"type":"text","text":"partial"},"mentions":{}}', + '/_update item #1 2 json {"msgContent":{"type":"text","text":"final"},"mentions":{}}', + ]) + }) + it("apiSetProfileAddress accepts userProfileNoChange", async () => { const chat = await chatWithResponse({type: "userProfileNoChange", user}) await expect(chat.apiSetProfileAddress(1, true)).resolves.toEqual({updateSuccesses: 0, updateFailures: 0, changedContacts: []}) @@ -129,6 +140,122 @@ describe("startChat lifecycle", () => { }) }) +describe("LiveMessage", () => { + const interval = 50 + const json = JSON.stringify + + function recordingChat() { + const calls: string[] = [] + let itemId = 0 + const ref = (chatType: T.ChatType, chatId: number) => T.ChatType.cmdString(chatType) + chatId + const chat = { + apiSendMessages: async ([chatType, chatId]: [T.ChatType, number], [{msgContent}]: T.ComposedMessage[], live: boolean) => { + calls.push(`send ${ref(chatType, chatId)} ${json(msgContent.text)} live=${live}`) + return [{chatItem: {meta: {itemId: ++itemId}}}] as unknown as T.AChatItem[] + }, + apiUpdateChatItem: async (chatType: T.ChatType, chatId: number, chatItemId: number, msgContent: T.MsgContent, live: boolean) => { + calls.push(`update ${ref(chatType, chatId)} ${chatItemId} ${json(msgContent.text)} live=${live}`) + return {} as T.ChatItem + }, + apiDeleteChatItems: async (chatType: T.ChatType, chatId: number, chatItemIds: number[], deleteMode: T.CIDeleteMode) => { + calls.push(`delete ${ref(chatType, chatId)} ${chatItemIds} ${deleteMode}`) + return [] as T.ChatItemDeletion[] + }, + } + return {chat, calls} + } + + function recordingLiveMessage(): {live: api.LiveMessage, calls: string[]} { + const {chat, calls} = recordingChat() + return {live: new api.LiveMessage(chat, T.ChatType.Group, 5, interval), calls} + } + + function wait(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)) + } + + it("sends the first text at once, updates at most once per interval and finalizes", async () => { + const {live, calls} = recordingLiveMessage() + live.update("") + live.update("Hel") + live.update("Hello") + await wait(interval / 5) + expect(calls).toEqual(['send #5 "" live=true']) + await wait(interval * 2) + expect(calls).toEqual(['send #5 "" live=true', 'update #5 1 "Hello" live=true']) + await live.end("Hello world") + expect(calls).toEqual(['send #5 "" live=true', 'update #5 1 "Hello" live=true', 'update #5 1 "Hello world" live=false']) + }) + + it("does not repeat unchanged text", async () => { + const {live, calls} = recordingLiveMessage() + live.update("Hello") + live.update("Hello") + await wait(interval * 2) + await live.end() + expect(calls).toEqual(['send #5 "Hello" live=true', 'update #5 1 "Hello" live=false']) + }) + + it("deletes a live message without text", async () => { + const {live, calls} = recordingLiveMessage() + live.update("") + await live.end() + expect(calls).toEqual(['send #5 "" live=true', "delete #5 1 broadcast"]) + }) + + it("sends the final text without a live message", async () => { + const {live, calls} = recordingLiveMessage() + await live.end() + await live.end("Done") + expect(calls).toEqual(['send #5 "Done" live=false']) + }) + + it("finalizes the last text", async () => { + const {live, calls} = recordingLiveMessage() + live.update("") + live.update("partial") + await live.end() + expect(calls).toEqual(['send #5 "" live=true', 'update #5 1 "partial" live=false']) + }) + + it("starts the next live message after the end", async () => { + const {live, calls} = recordingLiveMessage() + live.update("") + void live.end("first") + live.update("") + await live.end("second") + expect(calls).toEqual([ + 'send #5 "" live=true', + 'update #5 1 "first" live=false', + 'send #5 "" live=true', + 'update #5 2 "second" live=false', + ]) + }) + + it("passes errors to the handler and repeats the change with the next text", async () => { + const {chat, calls} = recordingChat() + const error = new Error("update failed") + chat.apiUpdateChatItem = jest.fn(chat.apiUpdateChatItem).mockRejectedValueOnce(error) + const errors: unknown[] = [] + const live = new api.LiveMessage(chat, T.ChatType.Group, 5, interval, e => errors.push(e)) + live.update("") + live.update("Hello") + await wait(interval * 2) + await live.end("Hello world") + expect(errors).toEqual([error]) + expect(calls).toEqual(['send #5 "" live=true', 'update #5 1 "Hello world" live=false']) + }) + + it("logs errors by default", async () => { + const {chat} = recordingChat() + const error = new Error("send failed") + chat.apiSendMessages = jest.fn(chat.apiSendMessages).mockRejectedValueOnce(error) + const log = jest.spyOn(console, "log").mockImplementation(() => {}) + await new api.LiveMessage(chat, T.ChatType.Group, 5).end("Hello") + expect(log).toHaveBeenCalledWith("live message error", error) + }) +}) + describe("ChatApi.init", () => { it("loads the library for the configured backend before opening the database", async () => { let loaded!: () => void diff --git a/packages/simplex-chat-nodejs/tests/bot.test.ts b/packages/simplex-chat-nodejs/tests/bot.test.ts index 5a7faa663f..41d3f054c5 100644 --- a/packages/simplex-chat-nodejs/tests/bot.test.ts +++ b/packages/simplex-chat-nodejs/tests/bot.test.ts @@ -3,18 +3,35 @@ import * as fs from "fs" import * as assert from "assert" import {CEvt, T} from "@simplex-chat/types" import {api, bot, util} from ".." +import {SmpServer, describeIfSmpServerAvailable, startSmpServer} from "./smpServer" const CT = T.ChatType -describe("Bot tests (use preset servers)", () => { +describeIfSmpServerAvailable("Bot tests (use local SMP server)", () => { const tmpDir = "./tests/tmp" const botPath = path.join(tmpDir, "bot") const alicePath = path.join(tmpDir, "alice") + let smpServer: SmpServer + beforeAll(async () => { smpServer = await startSmpServer() }, 120000) + afterAll(() => smpServer?.stop()) beforeEach(() => fs.mkdirSync(tmpDir, {recursive: true})) afterEach(() => fs.rmSync(tmpDir, {recursive: true, force: true})) + async function useSmpServer(chat: api.ChatApi): Promise { + expect(await chat.sendChatCmd(`/smp ${smpServer.address}`)).toMatchObject({type: "cmdOk"}) + } + + async function prepareBotDatabase(): Promise { + const chat = await api.ChatApi.init({type: "sqlite", filePrefix: botPath}) + await chat.apiCreateActiveUser({displayName: "Squaring bot", fullName: ""}) + await chat.startChat() + await useSmpServer(chat) + await chat.close() + } + it("should reply to messages", async () => { + await prepareBotDatabase() // run bot const [chat, botUser, botAddress] = await bot.run({ profile: {displayName: "Squaring bot", fullName: ""}, @@ -33,24 +50,25 @@ describe("Bot tests (use preset servers)", () => { const alice = await api.ChatApi.init({type: "sqlite", filePrefix: alicePath}) const aliceUser = await alice.apiCreateActiveUser({displayName: "alice", fullName: ""}) await alice.startChat() + await useSmpServer(alice) // connect to bot const [plan, link] = await alice.apiConnectPlan(aliceUser.userId, util.contactAddressStr(botAddress.connLinkContact)) assert(plan.type === "contactAddress") + const connected = Promise.all([alice.wait("contactConnected"), chat.wait("contactConnected")]) await expect(alice.apiConnect(aliceUser.userId, false, link)).resolves.toBe(api.ConnReqType.Contact) - const [botContact, aliceContact] = await Promise.all([ - (await alice.wait("contactConnected")).contact, - (await chat.wait("contactConnected")).contact - ]) + const [{contact: botContact}, {contact: aliceContact}] = await connected expect(botContact.profile.displayName).toBe("Squaring bot") // send message to bot const isMessage = ({contactId}: T.Contact, msg: string) => (evt: CEvt.NewChatItems) => evt.chatItems.some(ci => ci.chatInfo.type === CT.Direct && ci.chatInfo.contact.contactId === contactId && ci.chatItem.meta.itemText === msg) + const reply = alice.wait("newChatItems", isMessage(botContact, "2 * 2 = 4"), 5000) await alice.apiSendTextMessage([CT.Direct, botContact.contactId], "2") console.log("after sending message") - await alice.wait("newChatItems", isMessage(botContact, "2 * 2 = 4"), 5000) + await expect(reply).resolves.toBeDefined() // cleanup + const deleted = chat.wait("contactDeletedByContact", ({contact}) => contact.contactId === aliceContact.contactId) await alice.apiDeleteChat(CT.Direct, botContact.contactId) - await chat.wait("contactDeletedByContact", ({contact}) => contact.contactId === aliceContact.contactId) + await deleted await chat.apiDeleteUserAddress(botUser.userId) await chat.stopChat() await chat.close() diff --git a/packages/simplex-chat-nodejs/tests/core.test.ts b/packages/simplex-chat-nodejs/tests/core.test.ts index 608c3939cd..d8a87c82f6 100644 --- a/packages/simplex-chat-nodejs/tests/core.test.ts +++ b/packages/simplex-chat-nodejs/tests/core.test.ts @@ -17,8 +17,18 @@ describe("Core tests", () => { }, 300000); const tmpDir = "./tests/tmp"; const dbPath = path.join(tmpDir, "simplex_v1"); + const templateDir = "./tests/template"; + const templateDbPath = path.join(templateDir, "simplex_v1"); - beforeEach(() => fs.mkdirSync(tmpDir, {recursive: true})); + beforeAll(async () => { + fs.mkdirSync(templateDir, {recursive: true}); + await stopAndClose(await core.chatMigrateInit(templateDbPath, "key", core.MigrationConfirmation.YesUp)); + }, 30000); + afterAll(() => fs.rmSync(templateDir, {recursive: true, force: true})); + beforeEach(() => { + fs.mkdirSync(tmpDir, {recursive: true}); + copyTemplateDb(dbPath); + }); afterEach(() => fs.rmSync(tmpDir, {recursive: true, force: true})); async function stopAndClose(ctrl: bigint): Promise { @@ -26,6 +36,10 @@ describe("Core tests", () => { await core.chatCloseStore(ctrl); } + function copyTemplateDb(dbFilePrefix: string): void { + for (const suffix of ["_chat.db", "_agent.db"]) fs.copyFileSync(templateDbPath + suffix, dbFilePrefix + suffix); + } + it("should initialize chat controller", async () => { const ctrl = await core.chatMigrateInit(dbPath, "key", core.MigrationConfirmation.YesUp); expect(typeof ctrl).toBe("bigint"); @@ -132,7 +146,7 @@ describe("Core tests", () => { it("should not block the libuv pool while receiving", async () => { const ctrl = await core.chatMigrateInit(dbPath, "key", core.MigrationConfirmation.YesUp); - const receives = [1, 2, 3, 4].map(() => core.chatRecvMsgWait(ctrl, 2_000_000)); + const receives = [1, 2, 3, 4].map(() => core.chatRecvMsgWait(ctrl, 500_000)); const start = Date.now(); await fs.promises.stat(tmpDir); expect(Date.now() - start).toBeLessThan(200); @@ -159,8 +173,10 @@ describe("Core tests", () => { }, 30000); it("should receive on two controllers concurrently", async () => { - const ctrlA = await core.chatMigrateInit(path.join(tmpDir, "simplex_a"), "key", core.MigrationConfirmation.YesUp); - const ctrlB = await core.chatMigrateInit(path.join(tmpDir, "simplex_b"), "key", core.MigrationConfirmation.YesUp); + const dbPathB = path.join(tmpDir, "simplex_b"); + copyTemplateDb(dbPathB); + const ctrlA = await core.chatMigrateInit(dbPath, "key", core.MigrationConfirmation.YesUp); + const ctrlB = await core.chatMigrateInit(dbPathB, "key", core.MigrationConfirmation.YesUp); const start = Date.now(); await expect(Promise.all([core.chatRecvMsgWait(ctrlA, 1_000_000), core.chatRecvMsgWait(ctrlB, 1_000_000)])) .resolves.toEqual([undefined, undefined]); @@ -235,7 +251,7 @@ describe("Core tests", () => { }, 35000); it("should let the process exit while a receiver is idle", () => { - const childDbPath = path.resolve(tmpDir, "simplex_child"); + const childDbPath = path.resolve(dbPath); const script = ` const simplex = require("./build/Release/simplex.node"); simplex.load(${JSON.stringify(libPath)}); diff --git a/packages/simplex-chat-nodejs/tests/fixtures/tls/ca.crt b/packages/simplex-chat-nodejs/tests/fixtures/tls/ca.crt new file mode 100644 index 0000000000..e9902529bc --- /dev/null +++ b/packages/simplex-chat-nodejs/tests/fixtures/tls/ca.crt @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE----- +MIIBtjCCATagAwIBAgIUe2PryrWo0xXX9vcA3WfbCzcdmgAwBQYDK2VxMCoxFjAU +BgNVBAMMDVNNUCBzZXJ2ZXIgQ0ExEDAOBgNVBAoMB1NpbXBsZVgwIBcNMjIwMTEx +MTExNjM5WhgPNDc1OTEyMDgxMTE2MzlaMCoxFjAUBgNVBAMMDVNNUCBzZXJ2ZXIg +Q0ExEDAOBgNVBAoMB1NpbXBsZVgwQzAFBgMrZXEDOgCAcvFwVicR+RLZpiEWPFNR +XYTbf+mFcX1NHIyPQDugFwOCgqJAW1fsjYgFhtQJSMH/lc1N7clfm4CjUzBRMB0G +A1UdDgQWBBQcUJvR7mm26yxMQfCsWgbnwMmJVDAfBgNVHSMEGDAWgBQcUJvR7mm2 +6yxMQfCsWgbnwMmJVDAPBgNVHRMBAf8EBTADAQH/MAUGAytlcQNzAAAP/hMPNxyW +fyJi+iJViodU+C/aklnvHtjh5P3AbiVCSUfY6+PEdvkC8Ov0pBAYpYi5ukSNNVXl +ABVRlipB+vOcLQStNyaZ7kXzQ2IO/0btmIidh+G6SP8I4aytYIYYcV5pEUZpG1L1 +57g8P29SDv81AA== +-----END CERTIFICATE----- diff --git a/packages/simplex-chat-nodejs/tests/fixtures/tls/server.crt b/packages/simplex-chat-nodejs/tests/fixtures/tls/server.crt new file mode 100644 index 0000000000..68234a19a8 --- /dev/null +++ b/packages/simplex-chat-nodejs/tests/fixtures/tls/server.crt @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE----- +MIIBvDCCATygAwIBAgIUUgqht3ibEJ9jp5JuroML5Baxa/IwBQYDK2VxMCoxFjAU +BgNVBAMMDVNNUCBzZXJ2ZXIgQ0ExEDAOBgNVBAoMB1NpbXBsZVgwIBcNMjIwMTEx +MTYzOTQ2WhgPNDc1OTEyMDgxNjM5NDZaMBQxEjAQBgNVBAMMCWxvY2FsaG9zdDBD +MAUGAytlcQM6AFj+fmjmgOMBFgQ7lXfICSlSaB5sYp1wzhM2IaqgmVhWbDs3Tw7W +oUXFJsWpKku1kFjLH2yhqIInAKNvMG0wCQYDVR0TBAIwADALBgNVHQ8EBAMCA8gw +EwYDVR0lBAwwCgYIKwYBBQUHAwEwHQYDVR0OBBYEFMjbHbxESDI8YDPjJ8kXWjkg +jHxmMB8GA1UdIwQYMBaAFBxQm9HuabbrLExB8KxaBufAyYlUMAUGAytlcQNzAP98 +RpFacmjxsc07GwC07uAxUxNgxX4R2nA+0St866uFbdyxApiyn44giG+m+YXCXLjh +1ffKEhhqAPdlpzByLarx8EWgmOsisBCRWrmraksOV15FKxjPqm7OYKp4f9GvieAQ +o/SKOyZVVdcZX7NcuDUxAA== +-----END CERTIFICATE----- diff --git a/packages/simplex-chat-nodejs/tests/fixtures/tls/server.key b/packages/simplex-chat-nodejs/tests/fixtures/tls/server.key new file mode 100644 index 0000000000..27e0b7caaa --- /dev/null +++ b/packages/simplex-chat-nodejs/tests/fixtures/tls/server.key @@ -0,0 +1,4 @@ +-----BEGIN PRIVATE KEY----- +MEcCAQAwBQYDK2VxBDsEOZcXSjKQ3FQ9EBfqHmFIidOu4LecpUgi5NVPMhx7mS2d +QNQ0lFasp+EfvLErbs1Sd9xLVxA1hnZJng== +-----END PRIVATE KEY----- diff --git a/packages/simplex-chat-nodejs/tests/libs.test.ts b/packages/simplex-chat-nodejs/tests/libs.test.ts index c8a65f6bdd..175eadb880 100644 --- a/packages/simplex-chat-nodejs/tests/libs.test.ts +++ b/packages/simplex-chat-nodejs/tests/libs.test.ts @@ -249,6 +249,35 @@ describe("installLibs", () => { expect(leftovers()).toEqual([]) }) + it("rejects a symlink entry and cleans up", async () => { + const zip = storedZip({[`libs/${LIB}`]: "lib", "libs/link": {content: "../../outside", mode: 0o120777}}) + routes["/symlink.zip"] = res => res.writeHead(200).end(zip) + const target = path.join(tmp, "sqlite") + await expect(installLibs(`${base}/symlink.zip`, sha256(zip), target, LIB)).rejects.toThrow("zip entry is a symlink: libs/link") + expect(leftovers()).toEqual([]) + expect(fs.existsSync(target)).toBe(false) + }) + + it("rejects an entry outside the target folder and cleans up", async () => { + const zip = storedZip({[`libs/${LIB}`]: "lib", "libs/../../outside": "x"}) + routes["/outside.zip"] = res => res.writeHead(200).end(zip) + await expect(installLibs(`${base}/outside.zip`, sha256(zip), path.join(tmp, "sqlite"), LIB)).rejects.toThrow("libs/../../outside") + expect(leftovers()).toEqual([]) + expect(fs.existsSync(path.join(tmp, "outside"))).toBe(false) + expect(fs.existsSync(path.join(path.dirname(tmp), "outside"))).toBe(false) + }) + + const itOnPosix = process.platform === "win32" ? it.skip : it + + itOnPosix("keeps the file mode of an entry", async () => { + const zip = storedZip({[`libs/${LIB}`]: {content: "lib", mode: 0o100755}, "libs/data": {content: "x", mode: 0o100644}}) + routes["/modes.zip"] = res => res.writeHead(200).end(zip) + const target = path.join(tmp, "sqlite") + await installLibs(`${base}/modes.zip`, sha256(zip), target, LIB) + expect(fs.statSync(path.join(target, LIB)).mode & 0o777).toBe(0o755) + expect(fs.statSync(path.join(target, "data")).mode & 0o777).toBe(0o644) + }) + it("times out a request without a response", async () => { await expect(installLibs(`${base}/hang.zip`, GOOD_SHA, path.join(tmp, "sqlite"), LIB, 200)).rejects.toThrow("timeout") expect(leftovers()).toEqual([]) diff --git a/packages/simplex-chat-nodejs/tests/live.test.ts b/packages/simplex-chat-nodejs/tests/live.test.ts new file mode 100644 index 0000000000..aef27e3026 --- /dev/null +++ b/packages/simplex-chat-nodejs/tests/live.test.ts @@ -0,0 +1,68 @@ +import * as path from "path" +import * as fs from "fs" +import {T} from "@simplex-chat/types" +import {api} from ".." +import {SmpServer, describeIfSmpServerAvailable, startSmpServer} from "./smpServer" + +const CT = T.ChatType + +describeIfSmpServerAvailable("Live message tests (use local SMP server)", () => { + const tmpDir = "./tests/tmp" + const alicePath = path.join(tmpDir, "alice") + const bobPath = path.join(tmpDir, "bob") + let smpServer: SmpServer + + beforeAll(async () => { smpServer = await startSmpServer() }, 120000) + afterAll(() => smpServer?.stop()) + beforeEach(() => fs.mkdirSync(tmpDir, {recursive: true})) + afterEach(() => fs.rmSync(tmpDir, {recursive: true, force: true})) + + async function useSmpServer(chat: api.ChatApi): Promise { + expect(await chat.sendChatCmd(`/smp ${smpServer.address}`)).toMatchObject({type: "cmdOk"}) + } + + it("should show, update, finalize and delete live messages", async () => { + const alice = await api.ChatApi.init({type: "sqlite", filePrefix: alicePath}) + const bob = await api.ChatApi.init({type: "sqlite", filePrefix: bobPath}) + const aliceUser = await alice.apiCreateActiveUser({displayName: "alice", fullName: ""}) + await bob.apiCreateActiveUser({displayName: "bob", fullName: ""}) + await alice.startChat() + await bob.startChat() + await useSmpServer(alice) + await useSmpServer(bob) + const aliceConnected = alice.wait("contactConnected", 30000) + const bobConnected = bob.wait("contactConnected", 30000) + await bob.apiConnectActiveUser(await alice.apiCreateLink(aliceUser.userId)) + const bobContact = (await aliceConnected)!.contact + expect(await bobConnected).toBeDefined() + + const timeout = 10000 + const received = (check: (meta: T.CIMeta) => boolean) => + bob.wait("newChatItems", ({chatItems}) => chatItems.some(({chatItem: {meta}}) => check(meta)), timeout) + const updated = (check: (meta: T.CIMeta) => boolean) => + bob.wait("chatItemUpdated", ({chatItem: {chatItem: {meta}}}) => check(meta), timeout) + const live = new api.LiveMessage(alice, CT.Direct, bobContact.contactId, 500) + + const started = received(meta => meta.itemLive === true && meta.itemText === "") + live.update("") + const itemId = (await started)!.chatItems[0].chatItem.meta.itemId + const updatedLive = updated(meta => meta.itemId === itemId && meta.itemLive === true && meta.itemText === "Hello") + live.update("Hel") + live.update("Hello") + expect(await updatedLive).toBeDefined() + const finalized = updated(meta => meta.itemId === itemId && meta.itemLive === false && meta.itemText === "Hello world") + await live.end("Hello world") + expect((await finalized)?.chatItem.chatItem.meta.itemEdited).toBe(false) + + const emptyStarted = received(meta => meta.itemLive === true && meta.itemText === "") + live.update("") + const emptyItemId = (await emptyStarted)!.chatItems[0].chatItem.meta.itemId + const deleted = bob.wait("chatItemsDeleted", ({chatItemDeletions}) => + chatItemDeletions.some(({deletedChatItem}) => deletedChatItem.chatItem.meta.itemId === emptyItemId), timeout) + await live.end() + expect(await deleted).toBeDefined() + + await alice.close() + await bob.close() + }, 90000) +}) diff --git a/packages/simplex-chat-nodejs/tests/smpServer.ts b/packages/simplex-chat-nodejs/tests/smpServer.ts new file mode 100644 index 0000000000..dccd2e4ea6 --- /dev/null +++ b/packages/simplex-chat-nodejs/tests/smpServer.ts @@ -0,0 +1,118 @@ +import {ChildProcess, spawn} from "child_process" +import {createHash} from "crypto" +import {once} from "events" +import {copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, writeFileSync} from "fs" +import {AddressInfo, createConnection, createServer} from "net" +import {tmpdir} from "os" +import {dirname, join} from "path" + +const tlsFixtures = join(__dirname, "fixtures/tls") +const smpServerRelease = "v7.1.0-beta.3" +const releaseArch: Record = {x64: "x86-64", arm64: "aarch64"} +const downloadArch = process.platform === "linux" ? releaseArch[process.arch] : undefined +const downloadAttempts = 4 + +export const describeIfSmpServerAvailable = process.env.SMP_SERVER || downloadArch ? describe : describe.skip + +export interface SmpServer { + address: string + stop: () => Promise +} + +export async function startSmpServer(): Promise { + const port = await freePort() + const dir = mkdtempSync(join(tmpdir(), "smp-server-")) + const configDir = join(dir, "config") + mkdirSync(configDir) + for (const file of ["ca.crt", "server.crt", "server.key"]) copyFileSync(join(tlsFixtures, file), join(configDir, file)) + const fingerprint = caFingerprint(join(configDir, "ca.crt")) + writeFileSync(join(configDir, "fingerprint"), `${fingerprint}\n`) + writeFileSync(join(configDir, "smp-server.ini"), `[STORE_LOG]\nenable: off\n\n[TRANSPORT]\nhost: localhost\nport: ${port}\nwebsockets: off\n`) + const server = spawn(await smpServerExecutable(), ["start"], { + env: {...process.env, SMP_SERVER_CFG_PATH: configDir, SMP_SERVER_LOG_PATH: join(dir, "logs")}, + stdio: ["ignore", "ignore", "inherit"], + }) + const stop = async () => { + if (server.exitCode === null) { + const exited = once(server, "exit") + server.kill() + await exited + } + rmSync(dir, {recursive: true, force: true}) + } + try { + await once(server, "spawn") + await waitForServer(server, port) + } catch (e) { + await stop() + throw e + } + return {address: `smp://${fingerprint}@localhost:${port}`, stop} +} + +async function smpServerExecutable(): Promise { + if (process.env.SMP_SERVER) return process.env.SMP_SERVER + const path = join(__dirname, "../node_modules/.cache/smp-server", `smp-server-${smpServerRelease}`) + if (!existsSync(path)) await downloadSmpServer(path) + return path +} + +async function downloadSmpServer(path: string): Promise { + if (!downloadArch) { + throw new Error(`smp-server release binaries are not available for ${process.platform} ${process.arch}, set SMP_SERVER to the smp-server path`) + } + const url = `https://github.com/simplex-chat/simplexmq/releases/download/${smpServerRelease}/smp-server-ubuntu-22_04-${downloadArch}` + const data = await downloadWithRetries(url) + mkdirSync(dirname(path), {recursive: true}) + writeFileSync(`${path}.download`, data, {mode: 0o755}) + renameSync(`${path}.download`, path) +} + +async function downloadWithRetries(url: string): Promise { + for (let attempt = 1; attempt < downloadAttempts; attempt++) { + const data = await download(url).catch(() => undefined) + if (data) return data + await new Promise(resolve => setTimeout(resolve, attempt * 5000)) + } + return download(url) +} + +async function download(url: string): Promise { + const response = await fetch(url) + if (!response.ok) throw new Error(`error downloading ${url}: ${response.status}`) + return Buffer.from(await response.arrayBuffer()) +} + +function caFingerprint(caFile: string): string { + const der = Buffer.from(readFileSync(caFile, "utf-8").replace(/-----[^-]+-----|\s/g, ""), "base64") + return createHash("sha256").update(der).digest("base64").replace(/\+/g, "-").replace(/\//g, "_") +} + +function freePort(): Promise { + return new Promise((resolve, reject) => { + const probe = createServer() + probe.on("error", reject) + probe.listen(0, "127.0.0.1", () => { + const {port} = probe.address() as AddressInfo + probe.close(() => resolve(port)) + }) + }) +} + +async function waitForServer(server: ChildProcess, port: number): Promise { + const deadline = Date.now() + 15_000 + while (!(await canConnect(port))) { + if (server.exitCode !== null || Date.now() > deadline) throw new Error(`smp-server did not start on port ${port}`) + await new Promise(resolve => setTimeout(resolve, 100)) + } +} + +function canConnect(port: number): Promise { + return new Promise(resolve => { + const socket = createConnection({host: "localhost", port}, () => { + socket.destroy() + resolve(true) + }) + socket.on("error", () => resolve(false)) + }) +} diff --git a/packages/simplex-chat-nodejs/tests/zip.ts b/packages/simplex-chat-nodejs/tests/zip.ts index 547c8c211c..49b5c22747 100644 --- a/packages/simplex-chat-nodejs/tests/zip.ts +++ b/packages/simplex-chat-nodejs/tests/zip.ts @@ -4,15 +4,17 @@ const LOCAL_FILE_HEADER = 0x04034b50 const CENTRAL_DIRECTORY_HEADER = 0x02014b50 const END_OF_CENTRAL_DIRECTORY = 0x06054b50 const ZIP_VERSION = 20 +const MADE_BY_UNIX = 3 << 8 const LOCAL_HEADER_SIZE = 30 const CENTRAL_ENTRY_SIZE = 46 const END_RECORD_SIZE = 22 -export function storedZip(files: {[name: string]: string}): Buffer { +export function storedZip(files: {[name: string]: string | {content: string, mode: number}}): Buffer { const local: Buffer[] = [] const central: Buffer[] = [] let offset = 0 - for (const [name, content] of Object.entries(files)) { + for (const [name, file] of Object.entries(files)) { + const {content, mode} = typeof file === "string" ? {content: file, mode: 0} : file const nameBuf = Buffer.from(name) const data = Buffer.from(content) const crc = zlib.crc32(data) @@ -26,12 +28,13 @@ export function storedZip(files: {[name: string]: string}): Buffer { local.push(header, nameBuf, data) const entry = Buffer.alloc(CENTRAL_ENTRY_SIZE) entry.writeUInt32LE(CENTRAL_DIRECTORY_HEADER, 0) - entry.writeUInt16LE(ZIP_VERSION, 4) + entry.writeUInt16LE(mode ? MADE_BY_UNIX | ZIP_VERSION : ZIP_VERSION, 4) entry.writeUInt16LE(ZIP_VERSION, 6) entry.writeUInt32LE(crc, 16) entry.writeUInt32LE(data.length, 20) entry.writeUInt32LE(data.length, 24) entry.writeUInt16LE(nameBuf.length, 28) + entry.writeUInt32LE(mode * 0x10000, 38) entry.writeUInt32LE(offset, 42) central.push(entry, nameBuf) offset += header.length + nameBuf.length + data.length diff --git a/packages/simplex-chat-python/src/simplex_chat/_version.py b/packages/simplex-chat-python/src/simplex_chat/_version.py index e5075096d4..0c2e45a340 100644 --- a/packages/simplex-chat-python/src/simplex_chat/_version.py +++ b/packages/simplex-chat-python/src/simplex_chat/_version.py @@ -4,13 +4,13 @@ Written by scripts/ci/packages_release.py prepare: it bumps the version and pins new library releases with the SHA-256 of their assets. """ -__version__ = "7.1.0b6.post1" # PEP 440 — read by hatchling for wheel metadata -LIBS_RELEASE = "core-7.1.0.12" # simplex-chat-libs release tag +__version__ = "7.1.0b6.post2" # PEP 440 — read by hatchling for wheel metadata +LIBS_RELEASE = "core-7.1.0.14" # simplex-chat-libs release tag LIBS_SHA256 = { - "simplex-chat-libs-linux-aarch64.zip": "924b8e14eaba5e309fa584c4bfe598421cf64983d63790c1c45247f592f60055", - "simplex-chat-libs-linux-x86_64-postgres.zip": "323f908b45dde751d6082f8885d07d81fba8a5ab8b2df7d3b8ab3a87e56299e5", - "simplex-chat-libs-linux-x86_64.zip": "995409e963db408bcf896361b669944484f845b6744fefe479aebb0ca2e7c5cd", - "simplex-chat-libs-macos-aarch64.zip": "64464aa48b996e5a96b4aa651bc57bc69e412d3c3804b589c67bd9dc52faddbf", - "simplex-chat-libs-macos-x86_64.zip": "94664f96ff7a2cd5a419096e7e843928faa31cb3addb8869fa1e4b5a0d0b3629", - "simplex-chat-libs-windows-x86_64.zip": "2e10844e5c119921139c373ee2971341aceef7dc4198429d3be6b1570a009d15", + "simplex-chat-libs-linux-aarch64.zip": "375a6021f0c2e5d79b2cd192e17c6c22c8eae73f46e3758fba24ee5c26a671aa", + "simplex-chat-libs-linux-x86_64-postgres.zip": "c3e8a54d37801e685c6361c4e7e53e1f5163d3aa9195dcfe686b6cf90e668ef3", + "simplex-chat-libs-linux-x86_64.zip": "cc41ab9049d61be3b386d799fa9394f2870e8ae3b2b9b8745dd3f53ba99f211e", + "simplex-chat-libs-macos-aarch64.zip": "ac3a46004f25976f71b3ead8a20b9a01c170fe12f125a0a6799ae97ea990f234", + "simplex-chat-libs-macos-x86_64.zip": "0d85ffcccc3a6dfe3e0da18d0b7be9ef36ad27381f047ef66cfde67ee4a7e882", + "simplex-chat-libs-windows-x86_64.zip": "f1d1c7efc333a5ec5328a5843c9d3c9ef038fb2886c0b60ac12e87a5c1e85bbd", } diff --git a/scripts/ci/package-tests.sh b/scripts/ci/package-tests.sh index 33d83c4986..48fa9a889c 100755 --- a/scripts/ci/package-tests.sh +++ b/scripts/ci/package-tests.sh @@ -26,7 +26,7 @@ build_package() { } test_nodejs() { - (cd "$nodejs" && npx jest --forceExit --testNamePattern '^(?!.*use preset servers)') + (cd "$nodejs" && npx jest --forceExit) } test_python() { diff --git a/scripts/flatpak/chat.simplex.simplex.metainfo.xml b/scripts/flatpak/chat.simplex.simplex.metainfo.xml index 71192f34e2..51bd7b33ae 100644 --- a/scripts/flatpak/chat.simplex.simplex.metainfo.xml +++ b/scripts/flatpak/chat.simplex.simplex.metainfo.xml @@ -38,6 +38,23 @@ + + https://simplex.chat/blog/20260722-simplex-public-names.html + +

New in v7.0-v7.0.4.

+

SimpleX public names for channels and businesses (BETA).

+

Better channels:

+
    +
  • Promote subscribers to contributors.
  • +
  • Publish your channel on your website.
  • +
  • Verify security code with contributors.
  • +
  • Wider messages, easier to read.
  • +
  • Host your own chat relays.
  • +
+

Add a longer description to your profile.

+

Simplified app settings.

+
+
https://simplex.chat/blog/20260722-simplex-public-names.html diff --git a/website/src/.well-known/simplex.json b/website/src/.well-known/simplex.json new file mode 100644 index 0000000000..b7a1ba102b --- /dev/null +++ b/website/src/.well-known/simplex.json @@ -0,0 +1,33 @@ +{ + "channels": [ + { + "link": "https://smp18.simplex.im/c#grcfG3ulVI4Sh6ow33qBsmSk7uEy3gRSl2KkJ5ER6tA", + "title": "SimpleX Network News", + "changeFrequency": "weekly" + }, + { + "link": "https://smp10.simplex.im/c#q09nMBmWFGz1m2TvgfZFaEOG5D2a7Ma9mSkl6pHXEsg", + "title": "SimpleX Crowdfunding News", + "changeFrequency": "weekly" + } + ], + "groups": [ + { + "link": "https://smp4.simplex.im/g#hr4lvFeBmndWMKTwqiodPz3VBo_6UmdGWocXd1SupsM", + "title": "SimpleX users group" + } + ], + "contacts": [ + { + "link": "https://smp6.simplex.im/a#lrdvu2d8A1GumSmoKb2krQmtKhWXq-tyGpHuM7aMwsw", + "title": "Ask SimpleX Team", + "audience": ["people", "agents"], + "business": true + }, + { + "link": "https://smp4.simplex.im/a#lXUjJW5vHYQzoLYgmi8GbxkGP41_kjefFvBrdwg-0Ok", + "title": "SimpleX Directory", + "audience": ["people", "agents"] + } + ] +} diff --git a/website/src/_includes/markdown-alternate.html b/website/src/_includes/markdown-alternate.html index ad8301359c..f8367dd2ac 100644 --- a/website/src/_includes/markdown-alternate.html +++ b/website/src/_includes/markdown-alternate.html @@ -1,3 +1,8 @@ {% set markdownAlternateUrl = page | markdownAlternate %} {% if markdownAlternateUrl %}{% endif %} + + + +{% if active_directory %} +{% endif %} diff --git a/website/src/crowdfunding-news.html b/website/src/crowdfunding-news.html index 40294ee881..dffcc9c5b3 100644 --- a/website/src/crowdfunding-news.html +++ b/website/src/crowdfunding-news.html @@ -11,4 +11,4 @@ channel: - relay3.simplex.chat --- -

The news about SimpleX Chat equity crowdfunding on Wefunder, published as a SimpleX channel. Join it in the SimpleX Chat app: SimpleX Crowdfunding News. The crowdfunding memo: simplex.chat/wefunder.md.

+

The news about SimpleX Chat equity crowdfunding on Wefunder, published as a SimpleX channel. Join it in the SimpleX Chat app: SimpleX Crowdfunding News. The crowdfunding memo: simplex.chat/wefunder.md.

diff --git a/website/src/directory.html b/website/src/directory.html index 41dea04862..8ab3166dcd 100644 --- a/website/src/directory.html +++ b/website/src/directory.html @@ -318,7 +318,7 @@ active_directory: true

SimpleX Directory

Welcome to the selected users' communities that you can join via SimpleX Chat app.

-

SimpleX Directory is also available as a SimpleX chat bot.

+

SimpleX Directory is also available as a SimpleX chat bot.

Read about how to add your community.

diff --git a/website/src/index.html b/website/src/index.html index 31e6e086f8..2925915a64 100644 --- a/website/src/index.html +++ b/website/src/index.html @@ -273,7 +273,7 @@ active_home: true - + diff --git a/website/src/js/directory.jsc b/website/src/js/directory.jsc index dd07735260..f12f1108d9 100644 --- a/website/src/js/directory.jsc +++ b/website/src/js/directory.jsc @@ -339,7 +339,7 @@ function displayEntries(entries) { imgLinkElement.href = groupLinkUri; } imgLinkElement.target = "_blank"; - imgLinkElement.rel = "noopener"; + imgLinkElement.rel = groupLink.connShortLink ? "noopener simplex" : "noopener"; imgLinkElement.title = `Join ${displayName}`; const imgElement = document.createElement('img'); diff --git a/website/src/llms/llms-txt.njk b/website/src/llms/llms-txt.njk index 9004a88675..acb87df202 100644 --- a/website/src/llms/llms-txt.njk +++ b/website/src/llms/llms-txt.njk @@ -57,6 +57,14 @@ Every page of simplex.chat is also available as Markdown: replace `.html` with ` - [SimpleX for business](https://simplex.chat/docs/business.md) - [Host your own messaging server](https://simplex.chat/docs/server.md) and [file server](https://simplex.chat/docs/xftp-server.md) +## Communications + +- [SimpleX Network News](https://smp18.simplex.im/c#grcfG3ulVI4Sh6ow33qBsmSk7uEy3gRSl2KkJ5ER6tA): SimpleX updates for people, weekly. Network technology, governance and development. +- [SimpleX Crowdfunding News](https://smp10.simplex.im/c#q09nMBmWFGz1m2TvgfZFaEOG5D2a7Ma9mSkl6pHXEsg): SimpleX updates for people, weekly. The equity crowdfunding on Wefunder. +- [SimpleX users group](https://smp4.simplex.im/g#hr4lvFeBmndWMKTwqiodPz3VBo_6UmdGWocXd1SupsM): SimpleX group for people. Users of SimpleX Chat. +- [Ask SimpleX Team](https://smp6.simplex.im/a#lrdvu2d8A1GumSmoKb2krQmtKhWXq-tyGpHuM7aMwsw): SimpleX business contact for people and agents. Questions to the SimpleX Chat team. +- [SimpleX Directory](https://smp4.simplex.im/a#lXUjJW5vHYQzoLYgmi8GbxkGP41_kjefFvBrdwg-0Ok): SimpleX contact for people and agents. Search for public groups and channels. + ## For AI agents and developers - [SimpleX for AI agents and developers](https://simplex.chat/llms/agents.md): a bot in five minutes, the API, building from source, OpenClaw, asking the team diff --git a/website/src/news.html b/website/src/news.html index 1c0558ff81..3c5bd5fdfb 100644 --- a/website/src/news.html +++ b/website/src/news.html @@ -12,4 +12,4 @@ channel: - relay3.simplex.chat --- -

The news about SimpleX Network technology, governance and anything related to its development, published as a SimpleX channel. Join it in the SimpleX Chat app: SimpleX Network News.

+

The news about SimpleX Network technology, governance and anything related to its development, published as a SimpleX channel. Join it in the SimpleX Chat app: SimpleX Network News.